content-query 0.3.0 → 0.5.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 +46 -15
- package/dist/{build-CI4s0oFP.mjs → build-DWpB69_c.mjs} +826 -69
- package/dist/{build-jKnjDjQE.d.mts → build-DfoncKL8.d.mts} +17 -5
- package/dist/build.d.mts +1 -1
- package/dist/build.mjs +1 -1
- package/dist/cli.mjs +14 -8
- package/dist/{content-N1gYshjg.mjs → collection-Cb5eWGnv.mjs} +998 -344
- package/dist/collection-types-CsQtGw7_.d.mts +119 -0
- package/dist/computed.d.mts +2 -2
- package/dist/{types-FmUJ3cH1.d.mts → descriptor-bcPa6XLk.d.mts} +17 -1
- package/dist/{diagnostics-Cu94mZhl.mjs → diagnostics-DmUZtrJ6.mjs} +3 -1
- package/dist/{errors-C2iuBM0G.mjs → errors-Bfd0R4_P.mjs} +16 -1
- package/dist/file-DSOcdFj0.mjs +81 -0
- package/dist/{glob-Bv0dpvCQ.mjs → glob-BXJuwy_p.mjs} +94 -6
- package/dist/index.d.mts +76 -18
- package/dist/index.mjs +1 -1
- package/dist/internal.d.mts +54 -7
- package/dist/internal.mjs +424 -19
- package/dist/loaders.d.mts +1 -2
- package/dist/loaders.mjs +2 -81
- package/dist/next.d.mts +3 -2
- package/dist/next.mjs +67 -14
- package/dist/operators-D-w1-gDg.mjs +391 -0
- package/dist/query-style-C2FcT1f9.d.mts +23 -0
- package/dist/query.d.mts +25 -2
- package/dist/query.mjs +1 -1
- package/dist/{relational-types-B45zM34W.d.mts → relational-types-CTcalUNW.d.mts} +241 -50
- package/dist/revision-DBwU-aC9.d.mts +56 -0
- package/dist/runtime.d.mts +3 -2
- package/dist/runtime.mjs +1 -1
- package/dist/types-Dz1vTXa2.d.mts +214 -0
- package/dist/vite.d.mts +1 -1
- package/dist/vite.mjs +77 -35
- package/package.json +1 -1
- package/dist/content-lwPxE9Yu.d.mts +0 -109
- package/dist/descriptor-BUPT2J7X.d.mts +0 -18
- package/dist/operators-UqWUhKQJ.mjs +0 -173
- package/dist/types-BC8gVJ6u.d.mts +0 -69
- package/dist/validation-D_XdE1pF.mjs +0 -84
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { c as Loader, n as GlobOptions, t as FileOptions } from "./descriptor-bcPa6XLk.mjs";
|
|
2
|
+
import { n as ContentRenderResult, r as RenderedContent } from "./types-Dh-Sydyw.mjs";
|
|
3
|
+
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
4
|
+
//#region src/collection/types.d.ts
|
|
5
|
+
declare const builtinPayloadsType: unique symbol;
|
|
6
|
+
declare const customPayloadsType: unique symbol;
|
|
7
|
+
declare const loadedCollectionsType: unique symbol;
|
|
8
|
+
type Schema = StandardSchemaV1;
|
|
9
|
+
type SchemaOutput<Value> = Value extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<Value> : Record<string, unknown>;
|
|
10
|
+
/** One file or record that a `files` helper read, shaped like a loader entry. */
|
|
11
|
+
type FileEntry<Data = Record<string, unknown>> = {
|
|
12
|
+
id: string;
|
|
13
|
+
data: Data;
|
|
14
|
+
/** The Markdown body, when the file is Markdown and `retainBody` is not `false`. */
|
|
15
|
+
body?: string;
|
|
16
|
+
/** The file's path relative to the project root. */
|
|
17
|
+
filePath: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Tracked file access for computed and payload resolvers. Paths are relative to the entry's
|
|
21
|
+
* folder and must stay inside it or one of the collection's `sharedFiles` directories. Every read
|
|
22
|
+
* is recorded, so editing, adding, or removing a file re-resolves only what read it.
|
|
23
|
+
*/
|
|
24
|
+
type EntryFiles = {
|
|
25
|
+
/** Lists and parses files like `glob()`, with `base` relative to the entry's folder. */
|
|
26
|
+
glob<FileSchema extends Schema | undefined = undefined>(options: Omit<GlobOptions, "base" | "deferRender"> & {
|
|
27
|
+
base?: string;
|
|
28
|
+
schema?: FileSchema;
|
|
29
|
+
}): Promise<FileEntry<SchemaOutput<FileSchema>>[]>;
|
|
30
|
+
/** Parses one data file's records like `file()`, returning one entry per record. */
|
|
31
|
+
file<FileSchema extends Schema | undefined = undefined>(filePath: string, options?: FileOptions & {
|
|
32
|
+
schema?: FileSchema;
|
|
33
|
+
}): Promise<FileEntry<SchemaOutput<FileSchema>>[]>;
|
|
34
|
+
/** Reads a file as UTF-8 text. */
|
|
35
|
+
text(filePath: string): Promise<string>;
|
|
36
|
+
/** Reads a file's bytes. */
|
|
37
|
+
bytes(filePath: string): Promise<Uint8Array>;
|
|
38
|
+
/** Renders Markdown with the built-in renderer, like a loader's `renderMarkdown()`. */
|
|
39
|
+
renderMarkdown(body: string): Promise<RenderedContent>;
|
|
40
|
+
};
|
|
41
|
+
type ComputedContext<Data, Source = unknown> = {
|
|
42
|
+
/** The collection's key in the configured registry. */
|
|
43
|
+
collection: string;
|
|
44
|
+
id: string;
|
|
45
|
+
/** Loader-provided source metadata, detached from stored entries for this resolver. */
|
|
46
|
+
source?: Source extends object ? Readonly<Source> : Source;
|
|
47
|
+
data: Readonly<Data>;
|
|
48
|
+
readBody(this: void): Promise<string | undefined>;
|
|
49
|
+
/** Tracked reads of files beside the entry; edits re-prepare the entry. */
|
|
50
|
+
files: EntryFiles;
|
|
51
|
+
signal: AbortSignal;
|
|
52
|
+
};
|
|
53
|
+
type ComputedDefinition<Data = Record<string, unknown>, Source = unknown> = {
|
|
54
|
+
schema: Schema;
|
|
55
|
+
resolve(context: ComputedContext<Data, Source>): unknown;
|
|
56
|
+
};
|
|
57
|
+
/** Context for a payload's `combinations()` override, with the entry's prepared data. */
|
|
58
|
+
type VariantContext<Data, Source = unknown> = {
|
|
59
|
+
/** The collection's key in the configured registry. */
|
|
60
|
+
collection: string;
|
|
61
|
+
id: string;
|
|
62
|
+
/** Loader-provided source metadata, detached from stored entries for this resolver. */
|
|
63
|
+
source?: Source extends object ? Readonly<Source> : Source;
|
|
64
|
+
/** Validated metadata plus computed fields. */
|
|
65
|
+
data: Readonly<Data>;
|
|
66
|
+
signal: AbortSignal;
|
|
67
|
+
};
|
|
68
|
+
/** Context for a custom payload resolver, called once per entry and variant combination. */
|
|
69
|
+
type PayloadContext<Data, Source = unknown, Variant = {}> = VariantContext<Data, Source> & {
|
|
70
|
+
/** The variant combination being resolved; empty for payloads without variants. */
|
|
71
|
+
variant: Readonly<Variant>;
|
|
72
|
+
readBody(this: void): Promise<string | undefined>;
|
|
73
|
+
/** Tracked reads of files beside the entry; edits re-resolve only this variant. */
|
|
74
|
+
files: EntryFiles;
|
|
75
|
+
};
|
|
76
|
+
/** Data fields, including computed fields, whose values are string arrays. */
|
|
77
|
+
type VariantField<Data> = { [Field in keyof Data]-?: NonNullable<Data[Field]> extends readonly string[] ? Field : never; }[keyof Data] & string;
|
|
78
|
+
/**
|
|
79
|
+
* Enumerates one variant key's values: the name of a string-array data field, read per entry, or
|
|
80
|
+
* a fixed list. Derive values in a computed field when they need logic.
|
|
81
|
+
*/
|
|
82
|
+
type VariantValues<Data> = VariantField<Data> | readonly string[];
|
|
83
|
+
/**
|
|
84
|
+
* A custom payload: schema-validated data resolved during preparation and loaded only when a
|
|
85
|
+
* query requests it. Names cannot start with `_`, which built-in payloads such as `_body` use.
|
|
86
|
+
*/
|
|
87
|
+
type PayloadDefinition<Data = Record<string, unknown>, Source = unknown, Variant = {}> = {
|
|
88
|
+
schema: Schema;
|
|
89
|
+
/** Variant keys and their values for an entry. Omit for a single payload value. */
|
|
90
|
+
variants?: Record<string, VariantValues<Data>>;
|
|
91
|
+
/**
|
|
92
|
+
* Overrides the default cross product of enumerated values. Each combination must name every
|
|
93
|
+
* variant key with one of that key's enumerated values.
|
|
94
|
+
*/
|
|
95
|
+
combinations?(context: VariantContext<Data, Source> & {
|
|
96
|
+
variants: Readonly<Record<string, readonly string[]>>;
|
|
97
|
+
}): Iterable<Variant> | PromiseLike<Iterable<Variant>>;
|
|
98
|
+
resolve(context: PayloadContext<Data, Source, Variant>): unknown;
|
|
99
|
+
/**
|
|
100
|
+
* Set to `false` to resolve again in every revision, for resolvers that read data `files` cannot
|
|
101
|
+
* track, such as a remote API. Defaults to `true`.
|
|
102
|
+
*/
|
|
103
|
+
cache?: boolean;
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Any payload declaration once its inferred types are erased, as registries and preparation see
|
|
107
|
+
* it. Typed declarations from `defineCollection` are always assignable.
|
|
108
|
+
*/
|
|
109
|
+
type PayloadDeclaration = {
|
|
110
|
+
schema: Schema;
|
|
111
|
+
variants?: Record<string, string | readonly string[]>;
|
|
112
|
+
combinations?(context: never): unknown;
|
|
113
|
+
resolve(context: never): unknown;
|
|
114
|
+
cache?: boolean;
|
|
115
|
+
};
|
|
116
|
+
type Relationship = {
|
|
117
|
+
collection: string;
|
|
118
|
+
type: "one" | "many";
|
|
119
|
+
from: string;
|
|
120
|
+
to: string;
|
|
121
|
+
referenceSide: "from" | "to" | "none";
|
|
122
|
+
};
|
|
123
|
+
type CollectionDefinition<SourceSchema extends Schema | undefined = Schema | undefined, Computed extends Record<string, ComputedDefinition<never, never>> = Record<string, ComputedDefinition<never, never>>, Relationships extends Record<string, Relationship> = Record<string, Relationship>> = {
|
|
124
|
+
loader: Loader;
|
|
125
|
+
schema?: SourceSchema;
|
|
126
|
+
computed?: Computed;
|
|
127
|
+
relationships?: Relationships;
|
|
128
|
+
payloads?: Record<string, PayloadDeclaration>;
|
|
129
|
+
/** Directories, relative to the project root, that every entry's `files` helpers may read. */
|
|
130
|
+
sharedFiles?: readonly string[];
|
|
131
|
+
/**
|
|
132
|
+
* The singular name in generated types, such as `"Category"` for `ContentCategory`. Defaults to
|
|
133
|
+
* the export name without one trailing `s`.
|
|
134
|
+
*/
|
|
135
|
+
typeName?: string;
|
|
136
|
+
};
|
|
137
|
+
type CollectionRegistry = Record<string, CollectionDefinition>;
|
|
138
|
+
type EntryData<Definition> = Definition extends {
|
|
139
|
+
schema: infer SourceSchema;
|
|
140
|
+
} ? SchemaOutput<SourceSchema> : Record<string, unknown>;
|
|
141
|
+
type ComputedData<Definition> = Definition extends {
|
|
142
|
+
computed: infer Computed;
|
|
143
|
+
} ? { [Name in keyof Computed]: Computed[Name] extends {
|
|
144
|
+
schema: infer ResultSchema;
|
|
145
|
+
} ? SchemaOutput<ResultSchema> : never; } : {};
|
|
146
|
+
type QueryData<Definition> = {
|
|
147
|
+
id: string;
|
|
148
|
+
} & EntryData<Definition> & ComputedData<Definition>;
|
|
149
|
+
/**
|
|
150
|
+
* Type-only marker recording which collections a query scope loaded. A marked definition offers
|
|
151
|
+
* only the relationships whose target is loaded; an unmarked one offers every relationship.
|
|
152
|
+
*/
|
|
153
|
+
type LoadedMarker<Loaded> = {
|
|
154
|
+
readonly [loadedCollectionsType]?: Loaded;
|
|
155
|
+
};
|
|
156
|
+
/** The collections a marked definition's query scope loaded, or `unknown` when unmarked. */
|
|
157
|
+
type LoadedOf<Definition> = Definition extends {
|
|
158
|
+
readonly [loadedCollectionsType]?: infer Loaded;
|
|
159
|
+
} ? Loaded : unknown;
|
|
160
|
+
/** The declared relationships, keeping only those into `Loaded` when it is not `unknown`. */
|
|
161
|
+
type RelationshipsInto<Relationships, Loaded, Keep extends boolean> = unknown extends Loaded ? Keep extends true ? Relationships : {} : { [Name in keyof Relationships as (Relationships[Name] extends {
|
|
162
|
+
collection: Loaded;
|
|
163
|
+
} ? Keep : Keep extends true ? false : true) extends true ? Name : never]: Relationships[Name]; };
|
|
164
|
+
/** Relationships a query can follow: every declared one, or only those into loaded collections. */
|
|
165
|
+
type RelationshipsOf<Definition> = Definition extends {
|
|
166
|
+
relationships: infer Relationships;
|
|
167
|
+
} ? RelationshipsInto<Relationships, LoadedOf<Definition>, true> : {};
|
|
168
|
+
/**
|
|
169
|
+
* The type error for a declared relationship whose target collection the query has not loaded,
|
|
170
|
+
* or `never` for a name that is not such a relationship.
|
|
171
|
+
*/
|
|
172
|
+
type UnavailableRelationship<Definition, Name> = Name extends keyof UnavailableRelationshipsOf<Definition> ? UnavailableRelationshipsOf<Definition>[Name] extends {
|
|
173
|
+
collection: infer Target extends string;
|
|
174
|
+
} ? `Relationship "${Name & string}" targets the ${Target} collection, which this query has not loaded. Pass it to using().` : never : never;
|
|
175
|
+
/** Declared relationships into collections the query scope has not loaded. */
|
|
176
|
+
type UnavailableRelationshipsOf<Definition> = Definition extends {
|
|
177
|
+
relationships: infer Relationships;
|
|
178
|
+
} ? RelationshipsInto<Relationships, LoadedOf<Definition>, false> : {};
|
|
179
|
+
/**
|
|
180
|
+
* Payloads that Markdown sources, `glob()`, and custom loaders provide for every entry.
|
|
181
|
+
* Built-in names start with an underscore, so they never collide with custom payloads.
|
|
182
|
+
*/
|
|
183
|
+
type BuiltinPayloads = {
|
|
184
|
+
/** The retained raw body, or undefined when the entry has none or retention is disabled. */
|
|
185
|
+
_body: string | undefined;
|
|
186
|
+
/** HTML and headings; entries without a rendering path reject with `not-renderable`. */
|
|
187
|
+
_rendered: ContentRenderResult;
|
|
188
|
+
};
|
|
189
|
+
/** Type-only marker recording which built-in payloads a defined collection carries. */
|
|
190
|
+
type BuiltinPayloadMarker<Available extends keyof BuiltinPayloads> = {
|
|
191
|
+
readonly [builtinPayloadsType]?: Available;
|
|
192
|
+
};
|
|
193
|
+
/** One custom payload's validated value type and variant record, as recorded by a definition. */
|
|
194
|
+
type CustomPayload<Value = unknown, Variant = {}> = {
|
|
195
|
+
value: Value;
|
|
196
|
+
variant: Variant;
|
|
197
|
+
};
|
|
198
|
+
/** Type-only marker recording a defined collection's custom payload values and variant keys. */
|
|
199
|
+
type CustomPayloadMarker<Payloads extends Record<string, CustomPayload>> = {
|
|
200
|
+
readonly [customPayloadsType]?: Payloads;
|
|
201
|
+
};
|
|
202
|
+
type CustomPayloadsOf<Definition> = Definition extends {
|
|
203
|
+
readonly [customPayloadsType]?: infer Payloads;
|
|
204
|
+
} ? Payloads extends Record<string, CustomPayload> ? Payloads : {} : {};
|
|
205
|
+
/** Payload names and value types that queries can request for a collection definition. */
|
|
206
|
+
type PayloadsOf<Definition> = (Definition extends {
|
|
207
|
+
readonly [builtinPayloadsType]?: infer Available;
|
|
208
|
+
} ? Pick<BuiltinPayloads, Available & keyof BuiltinPayloads> : BuiltinPayloads) & { [Name in keyof CustomPayloadsOf<Definition>]: CustomPayloadsOf<Definition>[Name] extends CustomPayload<infer Value> ? Value : never; };
|
|
209
|
+
/** The value type of one payload that queries return under `_payloads`. */
|
|
210
|
+
type PayloadData<Definition, Name extends keyof PayloadsOf<Definition>> = PayloadsOf<Definition>[Name];
|
|
211
|
+
/** The variant keys a payload request must supply; empty for built-in and single-value payloads. */
|
|
212
|
+
type VariantOf<Definition, Name> = Name extends keyof CustomPayloadsOf<Definition> ? CustomPayloadsOf<Definition>[Name] extends CustomPayload<unknown, infer Variant> ? Variant : {} : {};
|
|
213
|
+
//#endregion
|
|
214
|
+
export { VariantContext as C, VariantValues as E, UnavailableRelationshipsOf as S, VariantOf as T, Relationship as _, ComputedContext as a, SchemaOutput as b, EntryFiles as c, PayloadContext as d, PayloadData as f, QueryData as g, PayloadsOf as h, CollectionRegistry as i, FileEntry as l, PayloadDefinition as m, BuiltinPayloads as n, CustomPayload as o, PayloadDeclaration as p, CollectionDefinition as r, CustomPayloadMarker as s, BuiltinPayloadMarker as t, LoadedMarker as u, RelationshipsOf as v, VariantField as w, UnavailableRelationship as x, Schema as y };
|
package/dist/vite.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ContentSourceEvent, o as ContentSourceSnapshot, t as BuildContentOptions } from "./build-
|
|
1
|
+
import { a as ContentSourceEvent, o as ContentSourceSnapshot, t as BuildContentOptions } from "./build-DfoncKL8.mjs";
|
|
2
2
|
import { Plugin } from "vite";
|
|
3
3
|
//#region src/integrations/vite/plugin.d.ts
|
|
4
4
|
/** Public, format-neutral hooks for integrations sharing this plugin's preparation session. */
|
package/dist/vite.mjs
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { I as collectionNames } from "./operators-D-w1-gDg.mjs";
|
|
2
|
+
import { a as sessionInternals, i as resolveConfigPath, l as virtualDeclarations, o as withinDirectory, r as configCandidates, s as writeChanged, t as buildAtRoot, u as revisionModules } from "./build-DWpB69_c.mjs";
|
|
3
|
+
import { n as serializeContentError } from "./diagnostics-DmUZtrJ6.mjs";
|
|
3
4
|
import { createRequire } from "node:module";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { randomUUID } from "node:crypto";
|
|
6
7
|
import { realpathSync } from "node:fs";
|
|
8
|
+
//#region src/integrations/vite/modules.ts
|
|
9
|
+
/** Collection facades are `content-query:collections/<export name>`. */
|
|
10
|
+
const collectionPrefix = "content-query:collections/";
|
|
7
11
|
const preparedFacade = "content-query:prepared-server";
|
|
8
12
|
const clientModule = "\0content-query:client.js";
|
|
9
13
|
const virtualPrefix = "\0content-query:";
|
|
@@ -51,24 +55,38 @@ export function onContentUpdate(callback) {
|
|
|
51
55
|
async function preparedModules(state) {
|
|
52
56
|
const revision = state.revision;
|
|
53
57
|
const error = state.error;
|
|
54
|
-
let next;
|
|
55
58
|
if (error) {
|
|
56
59
|
const serialized = serializeContentError(error);
|
|
57
|
-
|
|
58
|
-
} else {
|
|
59
|
-
next = await revisionModules(revision, { payloadChunkBytes: 131072 });
|
|
60
|
-
next.set("server.js", next.get("server.js").replaceAll("createArtifactRuntime", "createArtifactRevision").replace("export const content =", "export const revision =") + "\nexport const registry = revision.registry; export const error = undefined;\n");
|
|
60
|
+
return /* @__PURE__ */ new Map([["prepared-server.js", `import { restoreContentError } from "content-query/internal"; export const revision = undefined; export const registry = JSON.parse(${JSON.stringify(JSON.stringify(state.registry ?? {}))}); export const error = restoreContentError(${JSON.stringify(serialized)});`]]);
|
|
61
61
|
}
|
|
62
|
-
return
|
|
62
|
+
return revisionModules(revision, {
|
|
63
|
+
payloadChunkBytes: 131072,
|
|
64
|
+
preparedEntry: true
|
|
65
|
+
});
|
|
63
66
|
}
|
|
64
|
-
/**
|
|
65
|
-
function
|
|
66
|
-
return
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
/** Picks a module-local binding that cannot shadow the facade's export name. */
|
|
68
|
+
function binding(base, exportName) {
|
|
69
|
+
return base === exportName ? `${base}_` : base;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Creates one collection's stable facade, which accepts revision updates in host and isolated
|
|
73
|
+
* development runtimes.
|
|
74
|
+
*
|
|
75
|
+
* @param bridgeKey - Identifies the plugin's host session for runtimes in the same process.
|
|
76
|
+
* @param name - The collection's name.
|
|
77
|
+
* @param exportName - The facade's export name.
|
|
78
|
+
* @returns The facade module source.
|
|
79
|
+
*/
|
|
80
|
+
function devFacade(bridgeKey, name, exportName) {
|
|
81
|
+
const runtime = binding("runtime", exportName);
|
|
82
|
+
const prepared = binding("prepared", exportName);
|
|
83
|
+
const live = binding("live", exportName);
|
|
84
|
+
return `import * as ${runtime} from "content-query/internal";
|
|
85
|
+
import * as ${prepared} from ${JSON.stringify(preparedFacade)};
|
|
86
|
+
const ${live} = ${runtime}.createLiveCollection(${prepared}, ${JSON.stringify(name)});
|
|
87
|
+
export const ${exportName} = globalThis[Symbol.for("content-query.dev-sessions")]?.get(${JSON.stringify(bridgeKey)})?.collections[${JSON.stringify(name)}] ?? ${live}.collection;
|
|
70
88
|
if (import.meta.hot) {
|
|
71
|
-
const apply = next => { if (next) live.update(next); };
|
|
89
|
+
const apply = next => { if (next) ${live}.update(next); };
|
|
72
90
|
import.meta.hot.accept(${JSON.stringify(preparedFacade)}, apply);
|
|
73
91
|
// Module runners unwrap virtual IDs in update messages. Register that form too.
|
|
74
92
|
const hot = import.meta.hot;
|
|
@@ -109,6 +127,8 @@ function forbidden(source, config, options, importer) {
|
|
|
109
127
|
//#endregion
|
|
110
128
|
//#region src/integrations/vite/plugin.ts
|
|
111
129
|
const bridgeSymbol = Symbol.for("content-query.dev-sessions");
|
|
130
|
+
/** Matches a collection facade's virtual ID and captures its export name. */
|
|
131
|
+
const facadeModule = /^\0content-query:collections\/([^/]+)\/server\.js$/;
|
|
112
132
|
/** Integrates in-memory content with Vite; opt into generated storage for standalone artifacts. */
|
|
113
133
|
function contentQuery(options = {}) {
|
|
114
134
|
let config;
|
|
@@ -123,10 +143,18 @@ function contentQuery(options = {}) {
|
|
|
123
143
|
const bridgeKey = randomUUID();
|
|
124
144
|
const refreshClients = /* @__PURE__ */ new WeakSet();
|
|
125
145
|
const sourceListeners = /* @__PURE__ */ new Set();
|
|
126
|
-
|
|
127
|
-
|
|
146
|
+
let configFile;
|
|
147
|
+
/** Publishes type-only facade bindings without importing configuration in executable code. */
|
|
148
|
+
async function writeTypes(registry) {
|
|
149
|
+
if (!configFile || !registry) return;
|
|
128
150
|
const directory = path.join(config.root, ".content-query");
|
|
129
|
-
await writeChanged(path.join(directory, "types.d.ts"), virtualDeclarations(directory,
|
|
151
|
+
await writeChanged(path.join(directory, "types.d.ts"), virtualDeclarations(directory, configFile, collectionNames(registry)));
|
|
152
|
+
}
|
|
153
|
+
/** Maps a collection facade's export name back to its collection, or undefined when unknown. */
|
|
154
|
+
function collectionOf(exportName) {
|
|
155
|
+
const state = session && sessionInternals(session).state;
|
|
156
|
+
const registry = state?.revision?.registry ?? state?.registry ?? {};
|
|
157
|
+
for (const [name, generated] of collectionNames(registry)) if (generated.exportName === exportName) return name;
|
|
130
158
|
}
|
|
131
159
|
/** Notifies the host after atomic publication or a recoverable preparation failure. */
|
|
132
160
|
async function refreshModules(state) {
|
|
@@ -156,21 +184,24 @@ function contentQuery(options = {}) {
|
|
|
156
184
|
/** Notifies every host environment after portable state modules are ready. */
|
|
157
185
|
async function changed(state) {
|
|
158
186
|
await refreshModules(state);
|
|
187
|
+
await writeTypes(state.revision?.registry ?? state.registry).catch(deliveryFailed);
|
|
159
188
|
if (!server) return;
|
|
160
189
|
for (const environment of Object.values(server.environments)) {
|
|
161
|
-
const boundary = environment.moduleGraph.getModuleById(`${virtualPrefix}server.js`);
|
|
162
190
|
const prepared = environment.moduleGraph.getModuleById(`${virtualPrefix}prepared-server.js`);
|
|
163
|
-
|
|
164
|
-
if (
|
|
191
|
+
const boundaries = [...environment.moduleGraph.idToModuleMap.values()].filter((module) => facadeModule.test(module.id ?? ""));
|
|
192
|
+
if (prepared) environment.moduleGraph.invalidateModule(prepared);
|
|
193
|
+
const timestamp = Date.now();
|
|
194
|
+
const updates = prepared ? boundaries.map((boundary) => ({
|
|
195
|
+
type: "js-update",
|
|
196
|
+
timestamp,
|
|
197
|
+
path: boundary.url,
|
|
198
|
+
acceptedPath: prepared.url,
|
|
199
|
+
explicitImportRequired: false,
|
|
200
|
+
isWithinCircularImport: false
|
|
201
|
+
})) : [];
|
|
202
|
+
if (updates.length) environment.hot.send({
|
|
165
203
|
type: "update",
|
|
166
|
-
updates
|
|
167
|
-
type: "js-update",
|
|
168
|
-
timestamp: Date.now(),
|
|
169
|
-
path: boundary.url,
|
|
170
|
-
acceptedPath: prepared.url,
|
|
171
|
-
explicitImportRequired: false,
|
|
172
|
-
isWithinCircularImport: false
|
|
173
|
-
}]
|
|
204
|
+
updates
|
|
174
205
|
});
|
|
175
206
|
}
|
|
176
207
|
if (state.error) server.ws.send({
|
|
@@ -232,7 +263,9 @@ function contentQuery(options = {}) {
|
|
|
232
263
|
global[bridgeSymbol].set(bridgeKey, prepared);
|
|
233
264
|
await refreshModules(sessionInternals(prepared).state);
|
|
234
265
|
}
|
|
235
|
-
|
|
266
|
+
configFile = configPath;
|
|
267
|
+
const state = sessionInternals(prepared).state;
|
|
268
|
+
await writeTypes(state.revision?.registry ?? state.registry);
|
|
236
269
|
} catch (cause) {
|
|
237
270
|
await prepared.dispose();
|
|
238
271
|
globalThis[bridgeSymbol]?.delete(bridgeKey);
|
|
@@ -283,25 +316,34 @@ function contentQuery(options = {}) {
|
|
|
283
316
|
},
|
|
284
317
|
resolveId(source, importer, resolveOptions) {
|
|
285
318
|
if (clientEnvironment(this, config, resolveOptions.ssr) && forbidden(source, config, options, importer)) this.error(`[content-query] Server-only import "${source}" is reachable from a client module${importer ? `: ${importer}` : "."}`);
|
|
286
|
-
if (source
|
|
319
|
+
if (source.startsWith("content-query:collections/")) return `${virtualPrefix}collections/${source.slice(26)}/server.js`;
|
|
287
320
|
if (source === "content-query:client") return clientModule;
|
|
288
321
|
if (source === "content-query:prepared-server") return `${virtualPrefix}prepared-server.js`;
|
|
289
322
|
if (source.startsWith("\0content-query:")) return source;
|
|
290
323
|
if (source.startsWith(".") && importer?.startsWith("\0content-query:")) return `${virtualPrefix}${path.posix.normalize(path.posix.join(path.posix.dirname(importer.slice(15)), source))}`;
|
|
291
|
-
|
|
324
|
+
const physical = physicalPath(source, config, importer);
|
|
325
|
+
const entries = path.resolve(config.root, options.outDir ?? "./.content-query", "collections");
|
|
326
|
+
const relative = physical ? path.relative(entries, physical).replace(/\\/g, "/") : "";
|
|
327
|
+
if (/^[^/.][^/]*\/server\.js$/.test(relative)) return `${virtualPrefix}collections/${relative}`;
|
|
292
328
|
},
|
|
293
329
|
async load(id, loadOptions) {
|
|
294
330
|
if (id === "\0content-query:client.js") return devClient(config.command === "serve");
|
|
295
331
|
if (!id.startsWith("\0content-query:")) return;
|
|
296
332
|
if (clientEnvironment(this, config, loadOptions?.ssr)) this.error("[content-query] Content runtime cannot be loaded by the client.");
|
|
297
333
|
await ensureSession();
|
|
334
|
+
const facade = facadeModule.exec(id)?.[1];
|
|
335
|
+
/** Reports an import of a collection that the configuration does not define. */
|
|
336
|
+
const unknown = (exportName) => this.error(`[content-query] "${collectionPrefix}${exportName}" does not match a collection in content-query.config.`);
|
|
298
337
|
if (config.command === "serve") {
|
|
299
338
|
await refreshModules(sessionInternals(session).state);
|
|
300
|
-
if (
|
|
301
|
-
|
|
339
|
+
if (facade !== void 0) {
|
|
340
|
+
const name = collectionOf(facade);
|
|
341
|
+
return name === void 0 ? unknown(facade) : devFacade(bridgeKey, name, facade);
|
|
342
|
+
}
|
|
302
343
|
return modules.get(id.slice(15));
|
|
303
344
|
}
|
|
304
|
-
|
|
345
|
+
const code = modules.get(id.slice(15));
|
|
346
|
+
return facade !== void 0 && code === void 0 ? unknown(facade) : code;
|
|
305
347
|
},
|
|
306
348
|
transform(_code, id, transformOptions) {
|
|
307
349
|
if (clientEnvironment(this, config, transformOptions?.ssr) && forbidden(id, config, options)) this.error(`[content-query] Server-only module is reachable from the client: ${id}`);
|
package/package.json
CHANGED
|
@@ -1,109 +0,0 @@
|
|
|
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";
|
|
4
|
-
//#region src/shared/errors.d.ts
|
|
5
|
-
type ContentErrorCode = "variant-not-found" | "not-renderable" | "source-error" | "validation-error" | "duplicate-id" | "invalid-reference" | "invalid-query" | "artifact-error" | "render-error";
|
|
6
|
-
type ContentDiagnostic = {
|
|
7
|
-
code: ContentErrorCode;
|
|
8
|
-
message: string;
|
|
9
|
-
operation?: string;
|
|
10
|
-
collection?: string;
|
|
11
|
-
id?: string;
|
|
12
|
-
source?: string;
|
|
13
|
-
path?: readonly PropertyKey[];
|
|
14
|
-
issues?: readonly unknown[];
|
|
15
|
-
cause?: unknown;
|
|
16
|
-
};
|
|
17
|
-
/** A contextual library failure; inspect code and diagnostics instead of message text. */
|
|
18
|
-
declare class ContentError extends Error {
|
|
19
|
-
/** Recognizes contextual errors across preparation and separately bundled loader modules. */
|
|
20
|
-
static [Symbol.hasInstance](value: unknown): boolean;
|
|
21
|
-
readonly code: ContentErrorCode;
|
|
22
|
-
readonly operation?: string;
|
|
23
|
-
readonly collection?: string;
|
|
24
|
-
readonly id?: string;
|
|
25
|
-
readonly source?: string;
|
|
26
|
-
readonly path?: readonly PropertyKey[];
|
|
27
|
-
readonly issues?: readonly unknown[];
|
|
28
|
-
readonly diagnostics: readonly ContentDiagnostic[];
|
|
29
|
-
/** Creates one failure or a grouped report preserving individual causes and issues. */
|
|
30
|
-
constructor(options: ContentDiagnostic & {
|
|
31
|
-
diagnostics?: readonly ContentDiagnostic[];
|
|
32
|
-
});
|
|
33
|
-
}
|
|
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
|
-
//#region src/runtime/revision.d.ts
|
|
52
|
-
type QueryRecord = Record<string, unknown> & {
|
|
53
|
-
id: string;
|
|
54
|
-
};
|
|
55
|
-
/** The built-in side payload shared by an entry's `_body` and `_rendered` payloads. */
|
|
56
|
-
type BuiltinPayload = {
|
|
57
|
-
body?: string;
|
|
58
|
-
rendered?: ContentRenderResult;
|
|
59
|
-
markdown?: string;
|
|
60
|
-
};
|
|
61
|
-
type PreparedCollection = {
|
|
62
|
-
records: readonly QueryRecord[];
|
|
63
|
-
relationships: Record<string, Relationship>;
|
|
64
|
-
/** Lazy built-in payloads, present only for entries with a body or rendered output. */
|
|
65
|
-
builtins: ReadonlyMap<string, () => Promise<BuiltinPayload>>;
|
|
66
|
-
sources?: ReadonlyMap<string, string | undefined>;
|
|
67
|
-
};
|
|
68
|
-
type PreparedRevision = {
|
|
69
|
-
registry: QueryRegistry;
|
|
70
|
-
collections: Record<string, PreparedCollection>;
|
|
71
|
-
relations: Map<string, Map<string, readonly QueryRecord[]>>;
|
|
72
|
-
active: number;
|
|
73
|
-
payloadCache: Map<string, Promise<BuiltinPayload>>;
|
|
74
|
-
renderCache: Map<string, Promise<ContentRenderResult>>;
|
|
75
|
-
renderMarkdown?: (body: string) => Promise<ContentRenderResult>;
|
|
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"]>;
|
|
84
|
-
};
|
|
85
|
-
type ContentState = {
|
|
86
|
-
revision?: PreparedRevision;
|
|
87
|
-
registry?: QueryRegistry;
|
|
88
|
-
error?: ContentError;
|
|
89
|
-
};
|
|
90
|
-
//#endregion
|
|
91
|
-
//#region src/runtime/content.d.ts
|
|
92
|
-
type ContentMethods<Registry extends CollectionRegistry = {}> = {
|
|
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>;
|
|
99
|
-
};
|
|
100
|
-
type Content<Registry extends CollectionRegistry = {}> = InvalidRelationships<Registry> extends never ? ContentMethods<Registry> : {
|
|
101
|
-
readonly invalidRelationships: InvalidRelationships<Registry>;
|
|
102
|
-
};
|
|
103
|
-
/** Keeps dev builders live across host-delivered revisions while pinning active operations. */
|
|
104
|
-
declare function createLiveRuntime(initial: ContentState): {
|
|
105
|
-
content: ContentMethods<CollectionRegistry>;
|
|
106
|
-
update(next: ContentState): void;
|
|
107
|
-
};
|
|
108
|
-
//#endregion
|
|
109
|
-
export { ContentDiagnostic as a, InvalidRelationships as i, createLiveRuntime as n, ContentError as o, PreparedRevision as r, ContentErrorCode as s, Content as t };
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { m as MaybePromise } from "./types-FmUJ3cH1.mjs";
|
|
2
|
-
//#region src/loaders/descriptor.d.ts
|
|
3
|
-
type GlobOptions = {
|
|
4
|
-
pattern: string | string[];
|
|
5
|
-
base?: string | URL;
|
|
6
|
-
generateId?: (context: {
|
|
7
|
-
entry: string;
|
|
8
|
-
base: URL;
|
|
9
|
-
data: Record<string, unknown>;
|
|
10
|
-
}) => string;
|
|
11
|
-
retainBody?: boolean;
|
|
12
|
-
deferRender?: boolean;
|
|
13
|
-
};
|
|
14
|
-
type FileOptions = {
|
|
15
|
-
parser?: (text: string) => MaybePromise<unknown>;
|
|
16
|
-
};
|
|
17
|
-
//#endregion
|
|
18
|
-
export { GlobOptions as n, FileOptions as t };
|