EdgeAIG's picture
download
raw
8.36 kB
/**
* Schema-aware `RequestResolver` helpers for SQL-backed data loading.
*
* This module represents each lookup or mutation as a `SqlRequest` and batches
* concurrent requests into SQL operations. Request payloads are encoded with the
* request schema before `execute` is called, and returned rows are decoded with
* the result schema before requests are completed. It provides ordered,
* grouped, id-based, and side-effect-only resolver constructors, and keeps
* batches separated by the active SQL transaction connection.
*
* @since 4.0.0
*/
import * as Arr from "../../Array.js";
import * as Cause from "../../Cause.js";
import * as Effect from "../../Effect.js";
import * as Equal from "../../Equal.js";
import * as Exit from "../../Exit.js";
import * as Hash from "../../Hash.js";
import * as MutableHashMap from "../../MutableHashMap.js";
import * as Request from "../../Request.js";
import * as RequestResolver from "../../RequestResolver.js";
import * as Schema from "../../Schema.js";
import * as SqlClient from "./SqlClient.js";
import { ResultLengthMismatch } from "./SqlError.js";
const SqlRequestProto = {
...Request.Class.prototype,
[Equal.symbol](that) {
return Equal.equals(this.payload, that.payload);
},
[Hash.symbol]() {
return Hash.hash(this.payload);
}
};
/**
* Runs a payload as a `SqlRequest` through a request resolver, either directly
* with a payload and resolver or curried by resolver.
*
* @category requests
* @since 4.0.0
*/
export const request = function () {
if (arguments.length === 1) {
const resolver = arguments[0];
return payload => Effect.request(SqlRequest(payload), resolver);
}
return Effect.request(SqlRequest(arguments[0]), arguments[1]);
};
/**
* Constructs a `SqlRequest` from a payload. Equality and hashing are based on
* the payload so equal requests can be batched and deduplicated.
*
* @category requests
* @since 4.0.0
*/
export const SqlRequest = payload => {
const self = Object.create(SqlRequestProto);
self.payload = payload;
return self;
};
/**
* Creates a resolver for a SQL query with a request schema and a result schema.
*
* **Details**
*
* The request schema is used to validate the input of the query, and the result
* schema is used to validate the output of the query. Results are mapped to the
* requests in order, so the length of the results must match the length of the
* requests.
*
* @category resolvers
* @since 4.0.0
*/
export const ordered = options => {
const decodeArray = Schema.decodeUnknownEffect(Schema.Array(options.Result));
return RequestResolver.makeGrouped({
key: transactionKey,
resolver: Effect.fnUntraced(function* (entries) {
const inputs = yield* partitionRequests(entries, options.Request);
const results = yield* options.execute(inputs).pipe(Effect.provideContext(entries[0].context));
if (results.length !== inputs.length) {
return yield* new ResultLengthMismatch({
expected: inputs.length,
actual: results.length
});
}
const decodedResults = yield* decodeArray(results).pipe(Effect.provideContext(entries[0].context));
for (let i = 0; i < entries.length; i++) {
entries[i].completeUnsafe(Exit.succeed(decodedResults[i]));
}
})
});
};
/**
* Creates a batched SQL request resolver that encodes requests, decodes result
* rows, groups decoded results by matching request and result keys, and fails a
* request with `NoSuchElementError` when no result group exists.
*
* @category resolvers
* @since 4.0.0
*/
export const grouped = options => {
const decodeResults = Schema.decodeUnknownEffect(Schema.Array(options.Result));
return RequestResolver.makeGrouped({
key: transactionKey,
resolver: Effect.fnUntraced(function* (entries) {
const inputs = yield* partitionRequests(entries, options.Request);
const resultMap = MutableHashMap.empty();
const results = yield* options.execute(inputs).pipe(Effect.provideContext(entries[0].context));
const decodedResults = yield* decodeResults(results).pipe(Effect.provideContext(entries[0].context));
for (let i = 0, len = decodedResults.length; i < len; i++) {
const result = decodedResults[i];
const key = options.ResultGroupKey(result, results[i]);
const group = MutableHashMap.get(resultMap, key);
if (group._tag === "None") {
MutableHashMap.set(resultMap, key, [result]);
} else {
group.value.push(result);
}
}
for (let i = 0, len = entries.length; i < len; i++) {
const entry = entries[i];
const key = options.RequestGroupKey(entry.request.payload);
const result = MutableHashMap.get(resultMap, key);
entry.completeUnsafe(result._tag === "None" ? constNoSuchElement : Exit.succeed(result.value));
}
})
});
};
/**
* Creates a batched resolver that fetches rows for encoded ids, decodes
* results, completes each matching request using `ResultId`, and fails missing
* ids with `NoSuchElementError`.
*
* @category resolvers
* @since 4.0.0
*/
export const findById = options => {
const decodeResults = Schema.decodeUnknownEffect(Schema.Array(options.Result));
return RequestResolver.makeGrouped({
key: transactionKey,
resolver: Effect.fnUntraced(function* (entries) {
const [inputs, idMap] = yield* partitionRequestsById(entries, options.Id);
const results = yield* options.execute(inputs).pipe(Effect.provideContext(entries[0].context));
const decodedResults = yield* decodeResults(results).pipe(Effect.provideContext(entries[0].context));
for (let i = 0; i < decodedResults.length; i++) {
const result = decodedResults[i];
const id = options.ResultId(result, results[i]);
const request = MutableHashMap.get(idMap, id);
if (request._tag === "None") {
continue;
}
MutableHashMap.remove(idMap, id);
request.value.completeUnsafe(Exit.succeed(result));
}
if (MutableHashMap.isEmpty(idMap)) {
return;
}
MutableHashMap.forEach(idMap, request => {
request.completeUnsafe(constNoSuchElement);
});
})
});
};
const void_ = options => RequestResolver.makeGrouped({
key: transactionKey,
resolver: Effect.fnUntraced(function* (entries) {
const inputs = yield* partitionRequests(entries, options.Request);
yield* options.execute(inputs).pipe(Effect.provideContext(entries[0].context));
for (let i = 0; i < entries.length; i++) {
entries[i].completeUnsafe(Exit.void);
}
})
});
export {
/**
* Create a resolver that performs side effects.
*
* @category resolvers
* @since 4.0.0
*/
void_ as void };
const constNoSuchElement = /*#__PURE__*/Exit.fail(/*#__PURE__*/new Cause.NoSuchElementError());
const partitionRequests = function* (requests, schema) {
const len = requests.length;
const inputs = Arr.empty();
let entry;
const encode = Schema.encodeEffect(schema);
const handle = Effect.matchCauseEager({
onFailure(cause) {
entry.completeUnsafe(Exit.failCause(cause));
},
onSuccess(value) {
inputs.push(value);
}
});
for (let i = 0; i < len; i++) {
entry = requests[i];
yield Effect.provideContext(handle(encode(entry.request.payload)), entry.context);
}
return inputs;
};
const partitionRequestsById = function* (requests, schema) {
const len = requests.length;
const inputs = Arr.empty();
const byIdMap = MutableHashMap.empty();
let entry;
const encode = Schema.encodeEffect(schema);
const handle = Effect.matchCauseEager({
onFailure(cause) {
entry.completeUnsafe(Exit.failCause(cause));
},
onSuccess(value) {
inputs.push(value);
}
});
for (let i = 0; i < len; i++) {
entry = requests[i];
yield Effect.provideContext(handle(encode(entry.request.payload)), entry.context);
MutableHashMap.set(byIdMap, entry.request.payload, entry);
}
return [inputs, byIdMap];
};
function transactionKey(entry) {
const client = entry.context.mapUnsafe.get(SqlClient.SqlClient.key);
if (!client) return undefined;
const conn = entry.context.mapUnsafe.get(client.transactionService.key);
if (!conn) return undefined;
return Equal.byReferenceUnsafe(conn);
}
//# sourceMappingURL=SqlResolver.js.map

Xet Storage Details

Size:
8.36 kB
·
Xet hash:
8c5cde3ffdd333b82a05274b09b613e4a55240722db26edd3126415c513086e2

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.