content-query 0.4.0 → 0.5.1

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/dist/vite.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as ContentSourceEvent, o as ContentSourceSnapshot, t as BuildContentOptions } from "./build-DL1GzmRy.mjs";
1
+ import { a as ContentSourceEvent, o as ContentSourceSnapshot, t as BuildContentOptions } from "./build-Aoi7Unm0.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 { 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-CcqIpkes.mjs";
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-CIw4yWs9.mjs";
2
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
- next = /* @__PURE__ */ new Map([["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)});`]]);
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 next;
62
+ return revisionModules(revision, {
63
+ payloadChunkBytes: 131072,
64
+ preparedEntry: true
65
+ });
63
66
  }
64
- /** Creates the stable facade that accepts revision updates in host and isolated dev runtimes. */
65
- function devFacade(bridgeKey) {
66
- return `import { createLiveRuntime } from "content-query/internal";
67
- import * as prepared from ${JSON.stringify(preparedFacade)};
68
- const live = createLiveRuntime(prepared);
69
- export const content = globalThis[Symbol.for("content-query.dev-sessions")]?.get(${JSON.stringify(bridgeKey)})?.content ?? live.content;
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
- /** Publishes type-only facade binding without importing configuration in executable code. */
127
- async function writeTypes(configPath) {
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, configPath));
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
- for (const module of environment.moduleGraph.idToModuleMap.values()) if (module.id === `content-query:prepared-server.js`) environment.moduleGraph.invalidateModule(module);
164
- if (boundary && prepared) environment.hot.send({
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
- await writeTypes(configPath);
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 === "content-query:server") return `${virtualPrefix}server.js`;
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
- if (physicalPath(source, config, importer) === path.resolve(config.root, options.outDir ?? "./.content-query", "server.js")) return `${virtualPrefix}server.js`;
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 (id === `content-query:server.js`) return devFacade(bridgeKey);
301
- if (id === `content-query:prepared-server.js`) return modules.get("server.js");
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
- return modules.get(id.slice(15));
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,6 +1,6 @@
1
1
  {
2
2
  "name": "content-query",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Typed, framework-independent content collections, queries, and rendering.",
5
5
  "homepage": "https://github.com/yangshun/content-query#readme",
6
6
  "bugs": {
@@ -1,134 +0,0 @@
1
- import { n as ContentRenderResult } from "./types-Dh-Sydyw.mjs";
2
- import { _ as RelationshipsOf, g as Relationship, h as QueryData, i as CollectionRegistry } from "./types-DbFfF6JE.mjs";
3
- import { b as QueryRegistry, n as RelationalQueries, t as RelationalCountOptions, u as Query, y as QueryPlan } from "./relational-types-C8xJtLG5.mjs";
4
- import { i as QueryStyleMember, n as QueryStyle, r as QueryStyleDisabled, t as MembersOf } from "./query-style-eXENQJ0O.mjs";
5
- //#region src/shared/errors.d.ts
6
- type ContentErrorCode = "variant-not-found" | "not-renderable" | "source-error" | "validation-error" | "duplicate-id" | "invalid-reference" | "invalid-query" | "artifact-error" | "render-error";
7
- type ContentDiagnostic = {
8
- code: ContentErrorCode;
9
- message: string;
10
- operation?: string;
11
- collection?: string;
12
- id?: string;
13
- source?: string;
14
- path?: readonly PropertyKey[];
15
- /** The custom payload being resolved or loaded. */
16
- payload?: string;
17
- /** The payload's variant combination, with keys in sorted order. */
18
- variant?: Readonly<Record<string, string>>;
19
- issues?: readonly unknown[];
20
- cause?: unknown;
21
- };
22
- /** A contextual library failure; inspect code and diagnostics instead of message text. */
23
- declare class ContentError extends Error {
24
- /** Recognizes contextual errors across preparation and separately bundled loader modules. */
25
- static [Symbol.hasInstance](value: unknown): boolean;
26
- readonly code: ContentErrorCode;
27
- readonly operation?: string;
28
- readonly collection?: string;
29
- readonly id?: string;
30
- readonly source?: string;
31
- readonly path?: readonly PropertyKey[];
32
- readonly payload?: string;
33
- readonly variant?: Readonly<Record<string, string>>;
34
- readonly issues?: readonly unknown[];
35
- readonly diagnostics: readonly ContentDiagnostic[];
36
- /** Creates one failure or a grouped report preserving individual causes and issues. */
37
- constructor(options: ContentDiagnostic & {
38
- diagnostics?: readonly ContentDiagnostic[];
39
- });
40
- }
41
- //#endregion
42
- //#region src/collection/registry-types.d.ts
43
- type Scalar = string | number | bigint | boolean | Date;
44
- type Element<Value> = NonNullable<Value> extends readonly (infer Item)[] ? NonNullable<Item> : NonNullable<Value>;
45
- type Compatible<Left, Right> = NonNullable<Left> extends readonly unknown[] ? NonNullable<Right> extends readonly unknown[] ? false : ScalarCompatible<Left, Right> : ScalarCompatible<Left, Right>;
46
- 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;
47
- 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";
48
- /** Registry-level relationship checks require the complete inferred collection map. */
49
- 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];
50
- //#endregion
51
- //#region src/runtime/revision.d.ts
52
- type QueryRecord = Record<string, unknown> & {
53
- id: string;
54
- };
55
- /** Custom payload loaders by payload name, entry ID, and canonical variant key. */
56
- type CustomPayloadTable = ReadonlyMap<string, ReadonlyMap<string, ReadonlyMap<string, () => Promise<unknown>>>>;
57
- /** The built-in side payload shared by an entry's `_body` and `_rendered` payloads. */
58
- type BuiltinPayload = {
59
- body?: string;
60
- rendered?: ContentRenderResult;
61
- markdown?: string;
62
- };
63
- type PreparedCollection = {
64
- records: readonly QueryRecord[];
65
- relationships: Record<string, Relationship>;
66
- /** Lazy built-in payloads, present only for entries with a body or rendered output. */
67
- builtins: ReadonlyMap<string, () => Promise<BuiltinPayload>>;
68
- /**
69
- * Lazy custom payloads by payload name, entry ID, and canonical variant key. A missing variant
70
- * key means the entry has no such combination.
71
- */
72
- payloads?: CustomPayloadTable;
73
- sources?: ReadonlyMap<string, string | undefined>;
74
- };
75
- type PreparedRevision = {
76
- registry: QueryRegistry;
77
- collections: Record<string, PreparedCollection>;
78
- relations: Map<string, Map<string, readonly QueryRecord[]>>;
79
- active: number;
80
- /** Built-in payloads keyed by collection and ID; custom payloads also by name and variant. */
81
- payloadCache: Map<string, Promise<unknown>>;
82
- renderCache: Map<string, Promise<ContentRenderResult>>;
83
- renderMarkdown?: (body: string) => Promise<ContentRenderResult>;
84
- /** The only query style this revision's configuration allows; absent allows both. */
85
- queryStyle?: QueryStyle;
86
- execute?: (plan: QueryPlan, terminal: "all" | "first" | "count", requests: PayloadRequest[]) => Promise<unknown>;
87
- };
88
- /** A projected row awaiting the payloads its query scope requested. */
89
- type PayloadRequest = {
90
- target: Record<string, unknown>;
91
- collection: string;
92
- id: string;
93
- payloads: NonNullable<QueryPlan["payloads"]>;
94
- };
95
- type ContentState = {
96
- revision?: PreparedRevision;
97
- registry?: QueryRegistry;
98
- error?: ContentError;
99
- };
100
- //#endregion
101
- //#region src/runtime/content.d.ts
102
- type ContentMethods<Registry extends CollectionRegistry = {}> = {
103
- /**
104
- * Starts a fluent query over one collection. Nothing is selected until `select()`, `with()`,
105
- * `withPayload()`, or `selectAdjacent()` requests data, not even `id`.
106
- */
107
- from<Name extends keyof Registry & string>(collection: Name): Query<Registry, Name>;
108
- /** Collection-scoped relational queries with typed columns, relationships, payloads, and filters. */
109
- readonly query: RelationalQueries<Registry>;
110
- /** Counts matching entries before pagination, like the fluent count terminal. */
111
- count<Name extends keyof Registry & string>(collection: Name, options?: RelationalCountOptions<Registry, Name>): Promise<number>;
112
- };
113
- /**
114
- * Root members outside the allowed style. Property access reports the message type directly; a
115
- * call reports it as the required `this` type, whatever arguments it passes.
116
- */
117
- type DisabledMembers<Style extends QueryStyle> = { readonly [Member in Exclude<QueryStyleMember, MembersOf<Style>>]: ContentMethods[Member] extends ((...arguments_: never) => unknown) ? (this: QueryStyleDisabled<Member, Style>, ...arguments_: readonly unknown[]) => never : QueryStyleDisabled<Member, Style>; };
118
- /**
119
- * The generated `content` export. `Style` narrows the root query APIs to the `queryStyle` exported
120
- * by content-query.config; the default allows both fluent and relational queries.
121
- *
122
- * The gate avoids a conditional on `Style`: a conditional on a generic `Style` would make
123
- * `ContentSession<Registry>` invariant in `Registry` and break assignment to wider sessions.
124
- */
125
- type Content<Registry extends CollectionRegistry = {}, Style extends QueryStyle = QueryStyle> = InvalidRelationships<Registry> extends never ? Pick<ContentMethods<Registry>, MembersOf<Style>> & DisabledMembers<Style> : {
126
- readonly invalidRelationships: InvalidRelationships<Registry>;
127
- };
128
- /** Keeps dev builders live across host-delivered revisions while pinning active operations. */
129
- declare function createLiveRuntime(initial: ContentState): {
130
- content: Pick<ContentMethods<CollectionRegistry>, "from" | "query" | "count"> & DisabledMembers<QueryStyle>;
131
- update(next: ContentState): void;
132
- };
133
- //#endregion
134
- export { ContentDiagnostic as a, InvalidRelationships as i, createLiveRuntime as n, ContentError as o, PreparedRevision as r, ContentErrorCode as s, Content as t };
@@ -1,22 +0,0 @@
1
- //#region src/runtime/query-style.d.ts
2
- /** Query APIs an application can allow through `queryStyle` in content-query.config. */
3
- type QueryStyle = "fluent" | "relational";
4
- /**
5
- * Root `content` members that start each query style. Every other query API hangs off these
6
- * members, so gating them gates payloads, relationships, and adjacency as well.
7
- */
8
- declare const queryStyleMembers: {
9
- readonly fluent: readonly ["from"];
10
- readonly relational: readonly ["query", "count"];
11
- };
12
- /** A root `content` member that belongs to exactly one query style. */
13
- type QueryStyleMember = (typeof queryStyleMembers)[QueryStyle][number];
14
- /** Root members that start a query in the given style. */
15
- type MembersOf<Style extends QueryStyle> = (typeof queryStyleMembers)[Style][number];
16
- /**
17
- * Describes a disabled root member in the type error itself, for example:
18
- * `content.from is unavailable because content-query.config sets queryStyle to "relational".`
19
- */
20
- type QueryStyleDisabled<Member extends QueryStyleMember, Style extends QueryStyle> = `content.${Member} is unavailable because content-query.config sets queryStyle to "${Style}".`;
21
- //#endregion
22
- export { QueryStyleMember as i, QueryStyle as n, QueryStyleDisabled as r, MembersOf as t };