content-query 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -2
- package/dist/{build-C5ef0kkR.mjs → build-CI4s0oFP.mjs} +122 -30
- package/dist/build-jKnjDjQE.d.mts +48 -0
- package/dist/build.d.mts +2 -2
- package/dist/build.mjs +1 -2
- package/dist/cli.d.mts +1 -1
- package/dist/cli.mjs +3 -3
- package/dist/computed.d.mts +19 -13
- package/dist/computed.mjs +6 -5
- package/dist/{content-Dj2fQCJ5.mjs → content-N1gYshjg.mjs} +203 -77
- package/dist/{content-Crag1Uch.d.mts → content-lwPxE9Yu.d.mts} +40 -30
- package/dist/{descriptor-Xbo9u5eR.d.mts → descriptor-BUPT2J7X.d.mts} +1 -2
- package/dist/{diagnostics-Dol6ANtq.mjs → diagnostics-Cu94mZhl.mjs} +16 -15
- package/dist/{dist-BQGVLZyl.mjs → dist-C7_FOzY_.mjs} +21 -12
- package/dist/{glob-DJmebCMM.mjs → glob-Bv0dpvCQ.mjs} +3 -3
- package/dist/index.d.mts +35 -16
- package/dist/index.mjs +7 -4
- package/dist/internal/markdown.d.mts +3 -5
- package/dist/internal/markdown.mjs +1 -2
- package/dist/internal.d.mts +12 -12
- package/dist/internal.mjs +7 -7
- package/dist/loaders.d.mts +5 -6
- package/dist/loaders.mjs +6 -5
- package/dist/{markdown-CZBL4Ux_.mjs → markdown-CHvBBJNa.mjs} +2 -2
- package/dist/next.d.mts +3 -4
- package/dist/next.mjs +2 -2
- package/dist/{operators-BQpLfJlx.mjs → operators-UqWUhKQJ.mjs} +2 -2
- package/dist/query.d.mts +22 -23
- package/dist/query.mjs +1 -2
- package/dist/{relational-types-DLniKi5S.d.mts → relational-types-B45zM34W.d.mts} +62 -29
- package/dist/{chunk-CJs4Fogs.mjs → rolldown-runtime-Bg1v5YCA.mjs} +2 -2
- package/dist/runtime.d.mts +2 -2
- package/dist/runtime.mjs +3 -4
- package/dist/{schema-CnCbMosm.mjs → schema-C5Str-E8.mjs} +1 -1
- package/dist/schema.d.mts +0 -2
- package/dist/schema.mjs +1 -2
- package/dist/{types-BPh2cBPs.d.mts → types-BC8gVJ6u.d.mts} +27 -6
- package/dist/{types-hmDOw87q.d.mts → types-FmUJ3cH1.d.mts} +23 -6
- package/dist/{validation-BvvjSUFc.mjs → validation-D_XdE1pF.mjs} +2 -2
- package/dist/{values-B7lo_2_x.mjs → values-BbTV4QT-.mjs} +109 -108
- package/dist/vite.d.mts +14 -4
- package/dist/vite.mjs +28 -5
- package/package.json +7 -5
- package/dist/build-DSyoRxkZ.d.mts +0 -24
- /package/dist/{errors-C8lWyiy2.mjs → errors-C2iuBM0G.mjs} +0 -0
- /package/dist/{github-slugger-BCAkwEOL.mjs → github-slugger-BMgR_Q0I.mjs} +0 -0
- /package/dist/{server-only-CIE-FHx0.mjs → server-only-CIJUlyL-.mjs} +0 -0
- /package/dist/{types-DcvHRmxO.d.mts → types-Dh-Sydyw.d.mts} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as invalidQuery, n as contentError, t as ContentError } from "./errors-
|
|
2
|
-
import { a as detachSelection, c as isScalar, i as detach, n as compareStrings, s as equal, t as compare } from "./values-
|
|
3
|
-
import { t as assertServerOnly } from "./server-only-
|
|
4
|
-
import { _ as not, t as and, x as validateExpression, y as or } from "./operators-
|
|
1
|
+
import { a as invalidQuery, n as contentError, t as ContentError } from "./errors-C2iuBM0G.mjs";
|
|
2
|
+
import { a as detachSelection, c as isScalar, i as detach, n as compareStrings, s as equal, t as compare } from "./values-BbTV4QT-.mjs";
|
|
3
|
+
import { t as assertServerOnly } from "./server-only-CIJUlyL-.mjs";
|
|
4
|
+
import { _ as not, t as and, x as validateExpression, y as or } from "./operators-UqWUhKQJ.mjs";
|
|
5
5
|
//#region src/artifacts/types.ts
|
|
6
6
|
const CODEC_VERSION = "devalue-6.0.2";
|
|
7
7
|
//#endregion
|
|
@@ -144,11 +144,42 @@ function isRelationship(value) {
|
|
|
144
144
|
].includes(String(value.referenceSide));
|
|
145
145
|
}
|
|
146
146
|
//#endregion
|
|
147
|
+
//#region src/shared/concurrency.ts
|
|
148
|
+
/**
|
|
149
|
+
* Maps items through an asynchronous callback with at most `limit` callbacks in flight.
|
|
150
|
+
* Results keep input order; the first rejection rejects the whole map after started work settles.
|
|
151
|
+
*
|
|
152
|
+
* @param items - Values to process.
|
|
153
|
+
* @param limit - Maximum number of concurrent callbacks; values below one are treated as one.
|
|
154
|
+
* @param callback - Asynchronous work for one item and its index.
|
|
155
|
+
* @returns The callback results in input order.
|
|
156
|
+
*/
|
|
157
|
+
async function mapConcurrent(items, limit, callback) {
|
|
158
|
+
const results = Array.from({ length: items.length });
|
|
159
|
+
let next = 0;
|
|
160
|
+
let failure;
|
|
161
|
+
/** Claims the next unprocessed item until the list is exhausted or work has failed. */
|
|
162
|
+
async function worker() {
|
|
163
|
+
while (next < items.length && !failure) {
|
|
164
|
+
const index = next++;
|
|
165
|
+
try {
|
|
166
|
+
results[index] = await callback(items[index], index);
|
|
167
|
+
} catch (cause) {
|
|
168
|
+
failure ??= { cause };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const workers = Array.from({ length: Math.min(Math.max(1, limit), items.length) }, worker);
|
|
173
|
+
await Promise.all(workers);
|
|
174
|
+
if (failure) throw failure.cause;
|
|
175
|
+
return results;
|
|
176
|
+
}
|
|
177
|
+
//#endregion
|
|
147
178
|
//#region src/query/clauses.ts
|
|
148
179
|
/** Checks scalar field selection for both public query frontends without a schema field catalog. */
|
|
149
180
|
function checkedFields(registry, collection, fields) {
|
|
150
181
|
const scope = registry[collection];
|
|
151
|
-
if (!scope || fields.some((field) => typeof field !== "string" || !field || field === "_adjacent" || Object.hasOwn(scope.relationships, field)) || new Set(fields).size !== fields.length) invalidQuery("Selection requires distinct prepared fields; use relationship or adjacency selection explicitly.");
|
|
182
|
+
if (!scope || fields.some((field) => typeof field !== "string" || !field || field === "_adjacent" || field === "_payloads" || Object.hasOwn(scope.relationships, field)) || new Set(fields).size !== fields.length) invalidQuery("Selection requires distinct prepared fields; use relationship or adjacency selection explicitly.");
|
|
152
183
|
return [...fields];
|
|
153
184
|
}
|
|
154
185
|
/** Checks one sort clause with the same cardinality and duplicate rules in either API. */
|
|
@@ -164,6 +195,35 @@ function checkedPagination(value, many) {
|
|
|
164
195
|
if (!many || !Number.isSafeInteger(value) || value < 0) invalidQuery("Pagination requires a nonnegative safe integer and a many-valued scope.");
|
|
165
196
|
return value;
|
|
166
197
|
}
|
|
198
|
+
/** Finds a payload declaration that queries may request, rejecting unknown names. */
|
|
199
|
+
function payloadDescriptor(registry, collection, name) {
|
|
200
|
+
const payloads = registry[collection]?.payloads ?? {};
|
|
201
|
+
if (typeof name !== "string" || !Object.hasOwn(payloads, name)) invalidQuery(`Unknown payload "${String(name)}" for collection "${collection}".`);
|
|
202
|
+
return payloads[name];
|
|
203
|
+
}
|
|
204
|
+
/** Checks one payload request against the collection's payload names and variant keys. */
|
|
205
|
+
function checkedPayload(registry, collection, name, request) {
|
|
206
|
+
const { variants } = payloadDescriptor(registry, collection, name);
|
|
207
|
+
if (request === true || request === void 0) {
|
|
208
|
+
if (variants.length) invalidQuery(`Payload "${name}" requires variant keys: ${variants.join(", ")}.`);
|
|
209
|
+
return { variant: {} };
|
|
210
|
+
}
|
|
211
|
+
const variant = request;
|
|
212
|
+
if (!variant || typeof variant !== "object" || Array.isArray(variant) || Object.keys(variant).length !== variants.length || variants.some((key) => !Object.hasOwn(variant, key) || typeof variant[key] !== "string")) invalidQuery(`Payload "${name}" requires exactly these string variant keys: ${variants.join(", ") || "none"}.`);
|
|
213
|
+
return { variant: Object.fromEntries([...variants].sort().map((key) => [key, variant[key]])) };
|
|
214
|
+
}
|
|
215
|
+
/** Compiles a payloads option into plan requests, omitting payloads set to false. */
|
|
216
|
+
function checkedPayloads(registry, collection, payloads) {
|
|
217
|
+
const requested = Object.create(null);
|
|
218
|
+
for (const [name, request] of Object.entries(payloads)) {
|
|
219
|
+
if (request === false || request === void 0) {
|
|
220
|
+
payloadDescriptor(registry, collection, name);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
requested[name] = checkedPayload(registry, collection, name, request);
|
|
224
|
+
}
|
|
225
|
+
return Object.keys(requested).length ? requested : void 0;
|
|
226
|
+
}
|
|
167
227
|
//#endregion
|
|
168
228
|
//#region src/query/plan.ts
|
|
169
229
|
/** Starts a fresh query without binding a content revision until execution. */
|
|
@@ -272,7 +332,10 @@ function relationalWhere(registry, collection, where) {
|
|
|
272
332
|
validateExpression(expression);
|
|
273
333
|
return detach(expression);
|
|
274
334
|
}
|
|
275
|
-
/**
|
|
335
|
+
/**
|
|
336
|
+
* Compiles Drizzle-style columns/with options into the shared immutable query plan.
|
|
337
|
+
* Payloads are allowed wherever adjacency is, so sibling scopes never load them.
|
|
338
|
+
*/
|
|
276
339
|
function relationalPlan(registry, collection, options = {}, many = true, allowAdjacent = true, pagination = true) {
|
|
277
340
|
if (!Object.hasOwn(registry, collection)) invalidQuery(`Unknown collection "${collection}".`);
|
|
278
341
|
record$1(options, "Query");
|
|
@@ -282,7 +345,7 @@ function relationalPlan(registry, collection, options = {}, many = true, allowAd
|
|
|
282
345
|
"with",
|
|
283
346
|
...many ? ["orderBy"] : [],
|
|
284
347
|
...many && pagination ? ["limit", "offset"] : [],
|
|
285
|
-
...allowAdjacent ? ["adjacent"] : []
|
|
348
|
+
...allowAdjacent ? ["adjacent", "payloads"] : []
|
|
286
349
|
];
|
|
287
350
|
if (Object.keys(options).some((key) => !allowed.includes(key))) invalidQuery("Unknown relational query option or unsupported scope option.");
|
|
288
351
|
const plan = emptyPlan(collection);
|
|
@@ -317,6 +380,11 @@ function relationalPlan(registry, collection, options = {}, many = true, allowAd
|
|
|
317
380
|
for (const [field, direction] of Object.entries(options.orderBy)) plan.order = checkedOrder(plan, field, direction, many);
|
|
318
381
|
}
|
|
319
382
|
for (const key of ["limit", "offset"]) if (options[key] !== void 0) plan[key] = checkedPagination(options[key], many);
|
|
383
|
+
if (options.payloads !== void 0) {
|
|
384
|
+
record$1(options.payloads, "payloads");
|
|
385
|
+
const payloads = checkedPayloads(registry, collection, options.payloads);
|
|
386
|
+
if (payloads) plan.payloads = payloads;
|
|
387
|
+
}
|
|
320
388
|
if (options.adjacent !== void 0) plan.adjacent = options.adjacent === true ? true : relationalPlan(registry, collection, options.adjacent, true, false, false);
|
|
321
389
|
return plan;
|
|
322
390
|
}
|
|
@@ -523,6 +591,13 @@ function createBuilder(registry, run, plan, many = true, root = true, scopeToken
|
|
|
523
591
|
[name]: nested
|
|
524
592
|
} });
|
|
525
593
|
},
|
|
594
|
+
withPayload(name, variant) {
|
|
595
|
+
if (!allowAdjacent || Object.hasOwn(plan.payloads ?? {}, name)) invalidQuery("Payloads cannot be repeated or requested in adjacent scopes.");
|
|
596
|
+
return branch({ payloads: {
|
|
597
|
+
...plan.payloads,
|
|
598
|
+
[name]: checkedPayload(registry, plan.collection, name, variant)
|
|
599
|
+
} });
|
|
600
|
+
},
|
|
526
601
|
selectAdjacent(configure) {
|
|
527
602
|
if (!allowAdjacent || plan.adjacent !== void 0) invalidQuery("Adjacency cannot be repeated or selected recursively.");
|
|
528
603
|
if (configure === void 0) return branch({ adjacent: true });
|
|
@@ -541,6 +616,7 @@ function createBuilder(registry, run, plan, many = true, root = true, scopeToken
|
|
|
541
616
|
if (!root) invalidQuery("Nested builders have no terminal methods.");
|
|
542
617
|
return run(plan, "all");
|
|
543
618
|
},
|
|
619
|
+
/** Executes a selected root query, matching direct await behavior. */
|
|
544
620
|
execute() {
|
|
545
621
|
if (!root) invalidQuery("Nested builders have no terminal methods.");
|
|
546
622
|
return run(plan, "all");
|
|
@@ -567,7 +643,9 @@ function createBuilder(registry, run, plan, many = true, root = true, scopeToken
|
|
|
567
643
|
/** Defers collection validation until from(), preserving the immutable selection across reuse. */
|
|
568
644
|
function createSelect(registry, run, fields) {
|
|
569
645
|
const selected = [...fields];
|
|
570
|
-
return {
|
|
646
|
+
return {
|
|
647
|
+
/** Binds the current registry and prepares a reusable root query. */
|
|
648
|
+
from(collection) {
|
|
571
649
|
const current = registry();
|
|
572
650
|
if (!Object.hasOwn(current, collection)) invalidQuery(`Unknown collection "${collection}".`);
|
|
573
651
|
const plan = emptyPlan(collection);
|
|
@@ -718,6 +796,7 @@ function neighborScope(plan, records, preserveOrder, scopes, revision) {
|
|
|
718
796
|
exclude: void 0,
|
|
719
797
|
with: {},
|
|
720
798
|
adjacent: void 0,
|
|
799
|
+
payloads: void 0,
|
|
721
800
|
limit: void 0,
|
|
722
801
|
offset: void 0
|
|
723
802
|
} : selection;
|
|
@@ -739,16 +818,20 @@ function neighborScope(plan, records, preserveOrder, scopes, revision) {
|
|
|
739
818
|
}
|
|
740
819
|
return scope;
|
|
741
820
|
}
|
|
742
|
-
/**
|
|
743
|
-
|
|
821
|
+
/**
|
|
822
|
+
* Projects one entry and requested relationships/adjacency from one pinned revision.
|
|
823
|
+
* Requested payloads are recorded for the caller to load after projection.
|
|
824
|
+
*/
|
|
825
|
+
function project(record, plan, revision, scopes, scopeRecords, requests, preserveScopeOrder = false) {
|
|
744
826
|
if (!plan.select && !plan.allFields && !plan.adjacent) invalidQuery("Row-returning queries require select().");
|
|
745
|
-
const
|
|
827
|
+
const fields = plan.allFields ? Object.keys(record).filter((field) => !plan.exclude?.includes(field)) : plan.select ?? [];
|
|
828
|
+
const result = detachSelection(record, fields);
|
|
746
829
|
for (const [name, nested] of Object.entries(plan.with)) {
|
|
747
830
|
const relation = revision.registry[plan.collection].relationships[name];
|
|
748
831
|
if (!relation || relation.collection !== nested.collection || relation.type === "one" && (nested.limit !== void 0 || nested.offset !== void 0 || nested.order.length > 0)) invalidQuery(`Relationship "${name}" no longer matches the prepared query scope.`);
|
|
749
832
|
const candidates = revision.relations.get(relationKey(plan.collection, name))?.get(record.id) ?? [];
|
|
750
833
|
const preserveOrder = relation.referenceSide === "from" && Array.isArray(record[relation.from]);
|
|
751
|
-
const selected = pageRecords(candidates, nested, revision, preserveOrder).map((entry) => project(entry, nested, revision, scopes, candidates, preserveOrder));
|
|
834
|
+
const selected = pageRecords(candidates, nested, revision, preserveOrder).map((entry) => project(entry, nested, revision, scopes, candidates, requests, preserveOrder));
|
|
752
835
|
Object.defineProperty(result, name, {
|
|
753
836
|
value: relation.type === "many" ? selected : selected[0] ?? null,
|
|
754
837
|
enumerable: true,
|
|
@@ -763,25 +846,39 @@ function project(record, plan, revision, scopes, scopeRecords, preserveScopeOrde
|
|
|
763
846
|
const previous = index === void 0 ? void 0 : scope.records[index - 1];
|
|
764
847
|
const next = index === void 0 ? void 0 : scope.records[index + 1];
|
|
765
848
|
result._adjacent = {
|
|
766
|
-
previous: previous ? project(previous, scope.plan, revision, scopes, candidates) : null,
|
|
767
|
-
next: next ? project(next, scope.plan, revision, scopes, candidates) : null
|
|
849
|
+
previous: previous ? project(previous, scope.plan, revision, scopes, candidates, requests) : null,
|
|
850
|
+
next: next ? project(next, scope.plan, revision, scopes, candidates, requests) : null
|
|
768
851
|
};
|
|
769
852
|
}
|
|
853
|
+
if (plan.payloads) requests.push({
|
|
854
|
+
target: result,
|
|
855
|
+
collection: plan.collection,
|
|
856
|
+
id: record.id,
|
|
857
|
+
payloads: plan.payloads
|
|
858
|
+
});
|
|
770
859
|
return result;
|
|
771
860
|
}
|
|
772
|
-
/**
|
|
773
|
-
|
|
861
|
+
/**
|
|
862
|
+
* Runs the shared JavaScript executor; future adapters can execute the same plan.
|
|
863
|
+
* Rows that requested payloads are appended to `requests` for loading after execution.
|
|
864
|
+
*/
|
|
865
|
+
async function executeQuery(revision, plan, terminal, requests = []) {
|
|
774
866
|
const collection = revision.collections[plan.collection];
|
|
775
867
|
if (!collection) invalidQuery(`Unknown collection "${plan.collection}".`);
|
|
776
|
-
if (terminal === "count")
|
|
868
|
+
if (terminal === "count") {
|
|
869
|
+
if (plan.payloads) invalidQuery("Counts cannot request payloads.");
|
|
870
|
+
return queryCandidates(collection.records, plan).filter((record) => plan.filters.every((expression) => matches(record, expression, revision, plan.collection))).length;
|
|
871
|
+
}
|
|
777
872
|
if (!plan.select && !plan.allFields && !plan.adjacent) invalidQuery("Row-returning queries require select().");
|
|
778
873
|
const records = pageRecords(queryCandidates(collection.records, plan), plan, revision);
|
|
779
874
|
const scopes = /* @__PURE__ */ new WeakMap();
|
|
780
|
-
return terminal === "first" ? records[0] ? project(records[0], plan, revision, scopes, collection.records) : void 0 : records.map((record) => project(record, plan, revision, scopes, collection.records));
|
|
875
|
+
return terminal === "first" ? records[0] ? project(records[0], plan, revision, scopes, collection.records, requests) : void 0 : records.map((record) => project(record, plan, revision, scopes, collection.records, requests));
|
|
781
876
|
}
|
|
782
877
|
//#endregion
|
|
783
878
|
//#region src/runtime/content.ts
|
|
784
879
|
assertServerOnly();
|
|
880
|
+
/** Maximum payload loads in flight for one query. */
|
|
881
|
+
const PAYLOAD_CONCURRENCY = 16;
|
|
785
882
|
/** Pins the latest valid revision for an entire asynchronous operation. */
|
|
786
883
|
async function pinned(state, operation) {
|
|
787
884
|
if (state.error) throw state.error;
|
|
@@ -797,38 +894,26 @@ async function pinned(state, operation) {
|
|
|
797
894
|
revision.active--;
|
|
798
895
|
}
|
|
799
896
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
}
|
|
810
|
-
/** Looks up an entry and lazily loads only its revision-specific payload. */
|
|
811
|
-
async function loadPayload(revision, collectionName, id) {
|
|
812
|
-
const collection = revision.collections[collectionName];
|
|
813
|
-
if (!collection || typeof collectionName !== "string" || typeof id !== "string") invalidQuery("Body/render access requires a registered collection and string ID.");
|
|
814
|
-
if (!entryIds(collection.records).has(id)) throw new ContentError({
|
|
815
|
-
code: "entry-not-found",
|
|
816
|
-
collection: collectionName,
|
|
817
|
-
id,
|
|
818
|
-
message: `Entry "${id}" does not exist in "${collectionName}".`
|
|
819
|
-
});
|
|
820
|
-
const key = JSON.stringify([collectionName, id]);
|
|
897
|
+
/** Checks a decoded built-in payload before any of its values reach a query result. */
|
|
898
|
+
function validBuiltinPayload(payload) {
|
|
899
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return false;
|
|
900
|
+
const { body, markdown, rendered } = payload;
|
|
901
|
+
return (body === void 0 || typeof body === "string") && (markdown === void 0 || typeof markdown === "string") && (rendered === void 0 || !!rendered && typeof rendered.html === "string" && Array.isArray(rendered.headings) && rendered.headings.every((heading) => !!heading && Number.isInteger(heading.depth) && heading.depth >= 1 && heading.depth <= 6 && typeof heading.slug === "string" && typeof heading.text === "string"));
|
|
902
|
+
}
|
|
903
|
+
/** Lazily loads one entry's built-in payload once per revision, retrying after failures. */
|
|
904
|
+
function loadBuiltin(revision, collection, id) {
|
|
905
|
+
const key = JSON.stringify([collection, id]);
|
|
821
906
|
let loaded = revision.payloadCache.get(key);
|
|
822
907
|
if (!loaded) {
|
|
823
908
|
loaded = Promise.resolve().then(async () => {
|
|
824
|
-
const payload = await
|
|
825
|
-
if (!
|
|
909
|
+
const payload = await revision.collections[collection].builtins.get(id)?.() ?? {};
|
|
910
|
+
if (!validBuiltinPayload(payload)) throw new Error("Invalid prepared payload shape.");
|
|
826
911
|
return payload;
|
|
827
912
|
}).catch((cause) => {
|
|
828
913
|
revision.payloadCache.delete(key);
|
|
829
914
|
throw contentError(cause, {
|
|
830
915
|
code: "artifact-error",
|
|
831
|
-
collection
|
|
916
|
+
collection,
|
|
832
917
|
id,
|
|
833
918
|
message: "Could not load the prepared entry payload."
|
|
834
919
|
});
|
|
@@ -837,10 +922,84 @@ async function loadPayload(revision, collectionName, id) {
|
|
|
837
922
|
}
|
|
838
923
|
return loaded;
|
|
839
924
|
}
|
|
925
|
+
/** Returns supplied or eagerly compiled output, or renders captured Markdown once per revision. */
|
|
926
|
+
async function renderedPayload(revision, collection, id) {
|
|
927
|
+
const payload = await loadBuiltin(revision, collection, id);
|
|
928
|
+
if (payload.rendered) return payload.rendered;
|
|
929
|
+
if (payload.markdown === void 0) throw new ContentError({
|
|
930
|
+
code: "not-renderable",
|
|
931
|
+
collection,
|
|
932
|
+
id,
|
|
933
|
+
message: "This entry has no supplied output or supported rendering path."
|
|
934
|
+
});
|
|
935
|
+
const key = JSON.stringify([collection, id]);
|
|
936
|
+
let rendered = revision.renderCache.get(key);
|
|
937
|
+
if (!rendered) {
|
|
938
|
+
rendered = Promise.resolve().then(() => {
|
|
939
|
+
if (!revision.renderMarkdown) throw new Error("Missing packaged Markdown renderer.");
|
|
940
|
+
return revision.renderMarkdown(payload.markdown);
|
|
941
|
+
}).catch((cause) => {
|
|
942
|
+
revision.renderCache.delete(key);
|
|
943
|
+
throw contentError(cause, {
|
|
944
|
+
code: "render-error",
|
|
945
|
+
collection,
|
|
946
|
+
id,
|
|
947
|
+
message: "Markdown rendering failed."
|
|
948
|
+
});
|
|
949
|
+
});
|
|
950
|
+
revision.renderCache.set(key, rendered);
|
|
951
|
+
}
|
|
952
|
+
return rendered;
|
|
953
|
+
}
|
|
954
|
+
/** Loads one requested payload value from the pinned revision. */
|
|
955
|
+
async function loadPayload(revision, collection, id, name) {
|
|
956
|
+
if (name === "_body") return (await loadBuiltin(revision, collection, id)).body;
|
|
957
|
+
if (name === "_rendered") return renderedPayload(revision, collection, id);
|
|
958
|
+
invalidQuery(`Payload "${name}" no longer matches the prepared collection.`);
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Loads every requested payload with bounded concurrency, then attaches detached copies under
|
|
962
|
+
* `_payloads`. Each distinct payload loads once; rows that share an entry get independent values.
|
|
963
|
+
*/
|
|
964
|
+
async function attachPayloads(revision, requests) {
|
|
965
|
+
if (!requests.length) return;
|
|
966
|
+
const loads = /* @__PURE__ */ new Map();
|
|
967
|
+
for (const request of requests) for (const name of Object.keys(request.payloads)) {
|
|
968
|
+
const key = JSON.stringify([
|
|
969
|
+
request.collection,
|
|
970
|
+
request.id,
|
|
971
|
+
name,
|
|
972
|
+
request.payloads[name]
|
|
973
|
+
]);
|
|
974
|
+
if (!loads.has(key)) loads.set(key, {
|
|
975
|
+
collection: request.collection,
|
|
976
|
+
id: request.id,
|
|
977
|
+
name
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
const keys = [...loads.keys()];
|
|
981
|
+
const values = await mapConcurrent(keys, PAYLOAD_CONCURRENCY, (key) => {
|
|
982
|
+
const { collection, id, name } = loads.get(key);
|
|
983
|
+
return loadPayload(revision, collection, id, name);
|
|
984
|
+
});
|
|
985
|
+
const loaded = new Map(keys.map((key, index) => [key, values[index]]));
|
|
986
|
+
for (const request of requests) request.target._payloads = Object.fromEntries(Object.keys(request.payloads).map((name) => [name, detach(loaded.get(JSON.stringify([
|
|
987
|
+
request.collection,
|
|
988
|
+
request.id,
|
|
989
|
+
name,
|
|
990
|
+
request.payloads[name]
|
|
991
|
+
])))]));
|
|
992
|
+
}
|
|
840
993
|
/** Creates the initialized runtime without importing source configuration or schemas. */
|
|
841
994
|
function createRuntime(state) {
|
|
842
|
-
/** Pins
|
|
843
|
-
const run = (plan, terminal) => pinned(state, (revision) =>
|
|
995
|
+
/** Pins query execution and payload loading to exactly the same revision and executor. */
|
|
996
|
+
const run = (plan, terminal) => pinned(state, async (revision) => {
|
|
997
|
+
const normalized = normalizePlan(plan);
|
|
998
|
+
const requests = [];
|
|
999
|
+
const result = revision.execute ? await revision.execute(normalized, terminal, requests) : await executeQuery(revision, normalized, terminal, requests);
|
|
1000
|
+
await attachPayloads(revision, requests);
|
|
1001
|
+
return result;
|
|
1002
|
+
});
|
|
844
1003
|
/** Reads the current registry without capturing a development revision in either API. */
|
|
845
1004
|
const registry = () => state.registry ?? state.revision?.registry ?? {};
|
|
846
1005
|
return {
|
|
@@ -850,39 +1009,6 @@ function createRuntime(state) {
|
|
|
850
1009
|
query: createQueryNamespace(registry, run),
|
|
851
1010
|
count(collection, options = {}) {
|
|
852
1011
|
return run(relationalPlan(registry(), collection, options), "count");
|
|
853
|
-
},
|
|
854
|
-
readBody(collection, id) {
|
|
855
|
-
return pinned(state, async (revision) => (await loadPayload(revision, collection, id)).body);
|
|
856
|
-
},
|
|
857
|
-
render(collection, id) {
|
|
858
|
-
return pinned(state, async (revision) => {
|
|
859
|
-
const payload = await loadPayload(revision, collection, id);
|
|
860
|
-
if (payload.rendered) return detach(payload.rendered);
|
|
861
|
-
if (payload.markdown === void 0) throw new ContentError({
|
|
862
|
-
code: "not-renderable",
|
|
863
|
-
collection,
|
|
864
|
-
id,
|
|
865
|
-
message: "This entry has no supplied output or supported rendering path."
|
|
866
|
-
});
|
|
867
|
-
const key = JSON.stringify([collection, id]);
|
|
868
|
-
let rendered = revision.renderCache.get(key);
|
|
869
|
-
if (!rendered) {
|
|
870
|
-
rendered = Promise.resolve().then(() => {
|
|
871
|
-
if (!revision.renderMarkdown) throw new Error("Missing packaged Markdown renderer.");
|
|
872
|
-
return revision.renderMarkdown(payload.markdown);
|
|
873
|
-
}).catch((cause) => {
|
|
874
|
-
revision.renderCache.delete(key);
|
|
875
|
-
throw contentError(cause, {
|
|
876
|
-
code: "render-error",
|
|
877
|
-
collection,
|
|
878
|
-
id,
|
|
879
|
-
message: "Markdown rendering failed."
|
|
880
|
-
});
|
|
881
|
-
});
|
|
882
|
-
revision.renderCache.set(key, rendered);
|
|
883
|
-
}
|
|
884
|
-
return detach(await rendered);
|
|
885
|
-
});
|
|
886
1012
|
}
|
|
887
1013
|
};
|
|
888
1014
|
}
|
|
@@ -1,24 +1,8 @@
|
|
|
1
|
-
import { n as ContentRenderResult } from "./types-
|
|
2
|
-
import {
|
|
3
|
-
import { _ as QueryPlan, n as RelationalQueries, t as RelationalCountOptions, u as Query, v as QueryRegistry } from "./relational-types-
|
|
4
|
-
|
|
5
|
-
//#region src/collection/registry-types.d.ts
|
|
6
|
-
type Scalar = string | number | bigint | boolean | Date;
|
|
7
|
-
type Element<Value> = NonNullable<Value> extends readonly (infer Item)[] ? NonNullable<Item> : NonNullable<Value>;
|
|
8
|
-
type Compatible<Left, Right> = NonNullable<Left> extends readonly unknown[] ? NonNullable<Right> extends readonly unknown[] ? false : ScalarCompatible<Left, Right> : ScalarCompatible<Left, Right>;
|
|
9
|
-
type ScalarCompatible<Left, Right> = [Element<Left> | Element<Right>] extends [Scalar] ? [Element<Left>] extends [never] ? true : [Element<Right>] extends [never] ? true : [Extract<Element<Left>, Element<Right>> | Extract<Element<Right>, Element<Left>>] extends [never] ? false : true : false;
|
|
10
|
-
type CheckRelation<Registry extends CollectionRegistry, Name extends keyof Registry, Link extends Relationship> = Link["collection"] extends keyof Registry ? Link["from"] extends keyof QueryData<Registry[Name]> ? Link["to"] extends keyof QueryData<Registry[Link["collection"]]> ? Compatible<QueryData<Registry[Name]>[Link["from"]], QueryData<Registry[Link["collection"]]>[Link["to"]]> extends true ? never : "incompatible-relationship-fields" : "unknown-target-field" : "unknown-source-field" : "unknown-target-collection";
|
|
11
|
-
/** Registry-level relationship checks require the complete inferred collection map. */
|
|
12
|
-
type InvalidRelationships<Registry extends CollectionRegistry> = { [Name in keyof Registry]: { [Link in keyof RelationshipsOf<Registry[Name]>]: RelationshipsOf<Registry[Name]>[Link] extends Relationship ? CheckRelation<Registry, Name, RelationshipsOf<Registry[Name]>[Link]> : "invalid-relationship" }[keyof RelationshipsOf<Registry[Name]>] }[keyof Registry];
|
|
13
|
-
//#endregion
|
|
14
|
-
//#region src/query/select.d.ts
|
|
15
|
-
/** A selection whose collection and selected result are inferred when from() is supplied. */
|
|
16
|
-
type SelectFrom<Registry extends CollectionRegistry, Keys extends readonly string[]> = {
|
|
17
|
-
/** Binds the collection and rejects selected keys that are absent from its prepared data type. */from<Name extends keyof Registry & string>(collection: Name & (Exclude<Keys[number], keyof QueryData<Registry[NoInfer<Name>]>> extends never ? unknown : never)): Query<Registry, Name, Keys extends readonly [] ? QueryData<Registry[Name]> : { [Key in Keys[number]]-?: Key extends keyof QueryData<Registry[Name]> ? QueryData<Registry[Name]>[Key] : never }, true>;
|
|
18
|
-
};
|
|
19
|
-
//#endregion
|
|
1
|
+
import { n as ContentRenderResult } from "./types-Dh-Sydyw.mjs";
|
|
2
|
+
import { c as Relationship, i as CollectionRegistry, l as RelationshipsOf, s as QueryData } from "./types-BC8gVJ6u.mjs";
|
|
3
|
+
import { _ as QueryPlan, n as RelationalQueries, t as RelationalCountOptions, u as Query, v as QueryRegistry } from "./relational-types-B45zM34W.mjs";
|
|
20
4
|
//#region src/shared/errors.d.ts
|
|
21
|
-
type ContentErrorCode = "
|
|
5
|
+
type ContentErrorCode = "variant-not-found" | "not-renderable" | "source-error" | "validation-error" | "duplicate-id" | "invalid-reference" | "invalid-query" | "artifact-error" | "render-error";
|
|
22
6
|
type ContentDiagnostic = {
|
|
23
7
|
code: ContentErrorCode;
|
|
24
8
|
message: string;
|
|
@@ -48,11 +32,28 @@ declare class ContentError extends Error {
|
|
|
48
32
|
});
|
|
49
33
|
}
|
|
50
34
|
//#endregion
|
|
35
|
+
//#region src/collection/registry-types.d.ts
|
|
36
|
+
type Scalar = string | number | bigint | boolean | Date;
|
|
37
|
+
type Element<Value> = NonNullable<Value> extends readonly (infer Item)[] ? NonNullable<Item> : NonNullable<Value>;
|
|
38
|
+
type Compatible<Left, Right> = NonNullable<Left> extends readonly unknown[] ? NonNullable<Right> extends readonly unknown[] ? false : ScalarCompatible<Left, Right> : ScalarCompatible<Left, Right>;
|
|
39
|
+
type ScalarCompatible<Left, Right> = [Element<Left> | Element<Right>] extends [Scalar] ? [Element<Left>] extends [never] ? true : [Element<Right>] extends [never] ? true : [Extract<Element<Left>, Element<Right>> | Extract<Element<Right>, Element<Left>>] extends [never] ? false : true : false;
|
|
40
|
+
type CheckRelation<Registry extends CollectionRegistry, Name extends keyof Registry, Link extends Relationship> = Link["collection"] extends keyof Registry ? Link["from"] extends keyof QueryData<Registry[Name]> ? Link["to"] extends keyof QueryData<Registry[Link["collection"]]> ? Compatible<QueryData<Registry[Name]>[Link["from"]], QueryData<Registry[Link["collection"]]>[Link["to"]]> extends true ? never : "incompatible-relationship-fields" : "unknown-target-field" : "unknown-source-field" : "unknown-target-collection";
|
|
41
|
+
/** Registry-level relationship checks require the complete inferred collection map. */
|
|
42
|
+
type InvalidRelationships<Registry extends CollectionRegistry> = { [Name in keyof Registry]: { [Link in keyof RelationshipsOf<Registry[Name]>]: RelationshipsOf<Registry[Name]>[Link] extends Relationship ? CheckRelation<Registry, Name, RelationshipsOf<Registry[Name]>[Link]> : "invalid-relationship"; }[keyof RelationshipsOf<Registry[Name]>]; }[keyof Registry];
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/query/select.d.ts
|
|
45
|
+
/** A selection whose collection and selected result are inferred when from() is supplied. */
|
|
46
|
+
type SelectFrom<Registry extends CollectionRegistry, Keys extends readonly string[]> = {
|
|
47
|
+
/** Binds the collection and rejects selected keys that are absent from its prepared data type. */
|
|
48
|
+
from<Name extends keyof Registry & string>(collection: Name & (Exclude<Keys[number], keyof QueryData<Registry[NoInfer<Name>]>> extends never ? unknown : never)): Query<Registry, Name, Keys extends readonly [] ? QueryData<Registry[Name]> : { [Key in Keys[number]]-?: Key extends keyof QueryData<Registry[Name]> ? QueryData<Registry[Name]>[Key] : never; }, true>;
|
|
49
|
+
};
|
|
50
|
+
//#endregion
|
|
51
51
|
//#region src/runtime/revision.d.ts
|
|
52
52
|
type QueryRecord = Record<string, unknown> & {
|
|
53
53
|
id: string;
|
|
54
54
|
};
|
|
55
|
-
|
|
55
|
+
/** The built-in side payload shared by an entry's `_body` and `_rendered` payloads. */
|
|
56
|
+
type BuiltinPayload = {
|
|
56
57
|
body?: string;
|
|
57
58
|
rendered?: ContentRenderResult;
|
|
58
59
|
markdown?: string;
|
|
@@ -60,7 +61,8 @@ type ItemPayload = {
|
|
|
60
61
|
type PreparedCollection = {
|
|
61
62
|
records: readonly QueryRecord[];
|
|
62
63
|
relationships: Record<string, Relationship>;
|
|
63
|
-
payloads
|
|
64
|
+
/** Lazy built-in payloads, present only for entries with a body or rendered output. */
|
|
65
|
+
builtins: ReadonlyMap<string, () => Promise<BuiltinPayload>>;
|
|
64
66
|
sources?: ReadonlyMap<string, string | undefined>;
|
|
65
67
|
};
|
|
66
68
|
type PreparedRevision = {
|
|
@@ -68,10 +70,17 @@ type PreparedRevision = {
|
|
|
68
70
|
collections: Record<string, PreparedCollection>;
|
|
69
71
|
relations: Map<string, Map<string, readonly QueryRecord[]>>;
|
|
70
72
|
active: number;
|
|
71
|
-
payloadCache: Map<string, Promise<
|
|
73
|
+
payloadCache: Map<string, Promise<BuiltinPayload>>;
|
|
72
74
|
renderCache: Map<string, Promise<ContentRenderResult>>;
|
|
73
75
|
renderMarkdown?: (body: string) => Promise<ContentRenderResult>;
|
|
74
|
-
execute?: (plan: QueryPlan, terminal: "all" | "first" | "count") => Promise<unknown>;
|
|
76
|
+
execute?: (plan: QueryPlan, terminal: "all" | "first" | "count", requests: PayloadRequest[]) => Promise<unknown>;
|
|
77
|
+
};
|
|
78
|
+
/** A projected row awaiting the payloads its query scope requested. */
|
|
79
|
+
type PayloadRequest = {
|
|
80
|
+
target: Record<string, unknown>;
|
|
81
|
+
collection: string;
|
|
82
|
+
id: string;
|
|
83
|
+
payloads: NonNullable<QueryPlan["payloads"]>;
|
|
75
84
|
};
|
|
76
85
|
type ContentState = {
|
|
77
86
|
revision?: PreparedRevision;
|
|
@@ -81,11 +90,12 @@ type ContentState = {
|
|
|
81
90
|
//#endregion
|
|
82
91
|
//#region src/runtime/content.d.ts
|
|
83
92
|
type ContentMethods<Registry extends CollectionRegistry = {}> = {
|
|
84
|
-
/** Starts a SQL-ordered fluent query; field names are checked against the collection in from(). */
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
93
|
+
/** Starts a SQL-ordered fluent query; field names are checked against the collection in from(). */
|
|
94
|
+
select<const Keys extends readonly string[]>(...fields: Keys): SelectFrom<Registry, Keys>;
|
|
95
|
+
/** Collection-scoped relational queries with typed columns, relationships, payloads, and filters. */
|
|
96
|
+
readonly query: RelationalQueries<Registry>;
|
|
97
|
+
/** Counts matching entries before pagination, like the fluent count terminal. */
|
|
98
|
+
count<Name extends keyof Registry & string>(collection: Name, options?: RelationalCountOptions<Registry, Name>): Promise<number>;
|
|
89
99
|
};
|
|
90
100
|
type Content<Registry extends CollectionRegistry = {}> = InvalidRelationships<Registry> extends never ? ContentMethods<Registry> : {
|
|
91
101
|
readonly invalidRelationships: InvalidRelationships<Registry>;
|
|
@@ -96,4 +106,4 @@ declare function createLiveRuntime(initial: ContentState): {
|
|
|
96
106
|
update(next: ContentState): void;
|
|
97
107
|
};
|
|
98
108
|
//#endregion
|
|
99
|
-
export {
|
|
109
|
+
export { ContentDiagnostic as a, InvalidRelationships as i, createLiveRuntime as n, ContentError as o, PreparedRevision as r, ContentErrorCode as s, Content as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as ContentError } from "./errors-
|
|
2
|
-
import { o as encode, r as decode } from "./values-
|
|
1
|
+
import { t as ContentError } from "./errors-C2iuBM0G.mjs";
|
|
2
|
+
import { o as encode, r as decode } from "./values-BbTV4QT-.mjs";
|
|
3
3
|
//#region src/runtime/diagnostics.ts
|
|
4
4
|
/** Transports Error nodes as records while preserving the supported diagnostic value graph. */
|
|
5
5
|
function diagnosticGraph(value, causes, restore, seen = /* @__PURE__ */ new Map()) {
|
|
@@ -50,20 +50,21 @@ function diagnosticGraph(value, causes, restore, seen = /* @__PURE__ */ new Map(
|
|
|
50
50
|
/** Encodes contextual development failures without reducing BigInts or structured issues to JSON. */
|
|
51
51
|
function serializeContentError(error) {
|
|
52
52
|
const causes = /* @__PURE__ */ new Set();
|
|
53
|
+
const details = {
|
|
54
|
+
code: error.code,
|
|
55
|
+
message: error.message,
|
|
56
|
+
stack: error.stack,
|
|
57
|
+
operation: error.operation,
|
|
58
|
+
collection: error.collection,
|
|
59
|
+
id: error.id,
|
|
60
|
+
source: error.source,
|
|
61
|
+
path: error.path,
|
|
62
|
+
issues: error.issues,
|
|
63
|
+
cause: error.cause,
|
|
64
|
+
diagnostics: error.diagnostics
|
|
65
|
+
};
|
|
53
66
|
return encode({
|
|
54
|
-
error: diagnosticGraph(
|
|
55
|
-
code: error.code,
|
|
56
|
-
message: error.message,
|
|
57
|
-
stack: error.stack,
|
|
58
|
-
operation: error.operation,
|
|
59
|
-
collection: error.collection,
|
|
60
|
-
id: error.id,
|
|
61
|
-
source: error.source,
|
|
62
|
-
path: error.path,
|
|
63
|
-
issues: error.issues,
|
|
64
|
-
cause: error.cause,
|
|
65
|
-
diagnostics: error.diagnostics
|
|
66
|
-
}, causes, false),
|
|
67
|
+
error: diagnosticGraph(details, causes, false),
|
|
67
68
|
causes: [...causes]
|
|
68
69
|
});
|
|
69
70
|
}
|