content-query 0.2.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 CHANGED
@@ -10,6 +10,7 @@ Use it with your existing framework. Your application owns its routes, layouts,
10
10
  - **Flexible sources.** Load local files, use Astro-compatible content loaders, or write your own loader.
11
11
  - **Queries and relationships.** Select fields, filter, sort, paginate, and load related entries with validated references.
12
12
  - **Markdown rendering.** Request HTML, headings, and syntax highlighting as query payloads, loaded only when a query asks for them.
13
+ - **Optional MDX companion.** [`@content-query/mdx`](https://github.com/yangshun/content-query/tree/main/packages/mdx) supplies React islands and ordinary React document modules with Vite SSR. Core has no MDX or React dependency.
13
14
  - **Framework integrations.** Use Vite, Next.js, or the standalone CLI, with content updates during development.
14
15
 
15
16
  ## Usage
@@ -74,7 +75,7 @@ Version 0.2.0 removes `content.readBody()` and `content.render()`. Request bodie
74
75
  | `await content.render("posts", id)` | `(await content.query.posts.findFirst({ where: { id }, payloads: { _rendered: true } }))?._payloads._rendered` |
75
76
  | Fluent queries | Add `.withPayload("_rendered")` or `.withPayload("_body")` before the terminal. |
76
77
 
77
- Built-in payload names start with an underscore: `_body` and `_rendered`. A query for a missing entry returns `undefined` instead of rejecting with `entry-not-found`, which no longer exists. JSON, YAML, and TOML sources and `file()` collections offer no payloads. Rebuild generated content after upgrading; 0.1 artifacts are rejected.
78
+ Built-in payload names start with an underscore: `_body` and `_rendered`. A query for a missing entry returns `undefined` instead of rejecting with `entry-not-found`, which no longer exists. JSON, YAML, and TOML sources and `file()` collections offer no payloads, and custom loaders typed as `BodyLoader` offer only `_body`. Rebuild generated content after upgrading; 0.1 artifacts are rejected.
78
79
 
79
80
  ## Documentation
80
81
 
@@ -83,10 +84,14 @@ Built-in payload names start with an underscore: `_body` and `_rendered`. A quer
83
84
  - [API and usage](https://github.com/yangshun/content-query/blob/main/docs/API.md)
84
85
  - [Guides and reference](https://github.com/yangshun/content-query/blob/main/docs/README.md)
85
86
 
86
- The Vite integration supports Vite 8, and the Next.js integration supports Next.js 16. Both integrations are optional; standalone builds and queries require neither framework.
87
+ The Vite integration supports Vite 8, and the Next.js integration supports Next.js 15 and 16. Both integrations are optional; standalone builds and queries require neither framework.
87
88
 
88
89
  Pass `contentQuery()` directly to Vite or Vite+'s `plugins` array. No `Plugin` type cast is needed; workspace consumers share the pinned toolchain versions in the dependency catalog.
89
90
 
90
91
  Repository development checks package types and isolated consumers with [tsrs](https://github.com/oxc-project/tsrs). Run `vp run check` in the package directory for formatting, linting, and type checks; build tools use the shared TypeScript 6 compatibility package for its `6.0.3` JavaScript API. Native TypeScript `7.1.0-beta` is available at the repository root as `vp exec tsc`.
91
92
 
93
+ Build integrations can reuse the active source session without reading schemas or bodies. `ContentSession.sources()` from `content-query/build` returns an immutable `{ revision, entries: [{ collection, id, filePath? }] }` snapshot; `session.subscribe(listener)` reports successful publications (`{ type: "ready", snapshot }`) and errors (`{ type: "error", error }`) and returns an unsubscribe function. Revisions are opaque and session-scoped. In Vite, `contentQuery().api.prepare()` asynchronously prepares the shared session and returns its source snapshot, and `.api.subscribe()` forwards subsequent events. The [MDX architecture](https://github.com/yangshun/content-query/blob/main/docs/MDX-ARCHITECTURE.md) documents its use by companion integrations.
94
+
95
+ `prepare()` waits for pending preparation and rejects while validation errors are active. Successful unchanged reads reuse the frozen snapshot; publications supply a new revision. Subscribers can observe errors and recovery, and unsubscribing stops subsequent notifications. Development integrations should let core display recoverable errors without aborting the server or replacing the host's refresh subscription.
96
+
92
97
  Released under the [MIT license](https://github.com/yangshun/content-query/blob/main/LICENSE).
@@ -1020,6 +1020,43 @@ async function createSession(options) {
1020
1020
  outDir: path.resolve(options.root, options.outDir)
1021
1021
  };
1022
1022
  const state = {};
1023
+ const sourceListeners = /* @__PURE__ */ new Set();
1024
+ const sourceSnapshots = /* @__PURE__ */ new WeakMap();
1025
+ const sourceOwner = randomUUID();
1026
+ let sourceVersion = 0;
1027
+ /** Detaches and freezes integration metadata without exposing prepared payloads or schemas. */
1028
+ function sources() {
1029
+ if (state.error) throw state.error;
1030
+ if (!state.revision) throw new ContentError({
1031
+ code: "artifact-error",
1032
+ message: "No prepared source revision is available."
1033
+ });
1034
+ let snapshot = sourceSnapshots.get(state.revision);
1035
+ if (!snapshot) {
1036
+ snapshot = Object.freeze({
1037
+ revision: `${sourceOwner}:${++sourceVersion}`,
1038
+ entries: Object.freeze(Object.entries(state.revision.collections).flatMap(([collection, prepared]) => prepared.records.map(({ id }) => {
1039
+ const filePath = prepared.sources?.get(id);
1040
+ return Object.freeze({
1041
+ collection,
1042
+ id,
1043
+ ...filePath === void 0 ? {} : { filePath }
1044
+ });
1045
+ })))
1046
+ });
1047
+ sourceSnapshots.set(state.revision, snapshot);
1048
+ }
1049
+ return snapshot;
1050
+ }
1051
+ /** Keeps a consumer notification failure from corrupting successfully prepared content. */
1052
+ function notifySources(event) {
1053
+ const listeners = [...sourceListeners];
1054
+ for (const listener of listeners) try {
1055
+ listener(event);
1056
+ } catch (cause) {
1057
+ console.error("[content-query] Source subscriber failed", cause);
1058
+ }
1059
+ }
1023
1060
  let registry = {};
1024
1061
  let loaders = /* @__PURE__ */ new Map();
1025
1062
  let configDependencies;
@@ -1084,6 +1121,10 @@ async function createSession(options) {
1084
1121
  message: cause instanceof Error ? cause.message : "Content preparation failed."
1085
1122
  });
1086
1123
  options.change?.(state);
1124
+ notifySources({
1125
+ type: "error",
1126
+ error: state.error
1127
+ });
1087
1128
  }
1088
1129
  /** Aborts an owned subscription group and attempts every cleanup even when one fails. */
1089
1130
  async function releaseResources(controller, handlers) {
@@ -1229,6 +1270,10 @@ async function createSession(options) {
1229
1270
  state.revision = revision;
1230
1271
  state.error = void 0;
1231
1272
  options.change?.(state);
1273
+ notifySources({
1274
+ type: "ready",
1275
+ snapshot: sources()
1276
+ });
1232
1277
  } catch (cause) {
1233
1278
  if (!disposed && currentGeneration === generation) fail(cause);
1234
1279
  }
@@ -1282,6 +1327,7 @@ async function createSession(options) {
1282
1327
  async function dispose() {
1283
1328
  if (disposed) return;
1284
1329
  disposed = true;
1330
+ sourceListeners.clear();
1285
1331
  generation++;
1286
1332
  attempt?.abort();
1287
1333
  const failed = (await Promise.allSettled([watcher?.close() ?? Promise.resolve(), releaseSubscriptions()])).find((result) => result.status === "rejected");
@@ -1293,6 +1339,14 @@ async function createSession(options) {
1293
1339
  }
1294
1340
  const session = {
1295
1341
  content: createRuntime(state),
1342
+ sources,
1343
+ subscribe(listener) {
1344
+ if (disposed) throw new Error("Content session has been disposed.");
1345
+ sourceListeners.add(listener);
1346
+ return () => {
1347
+ sourceListeners.delete(listener);
1348
+ };
1349
+ },
1296
1350
  dispose
1297
1351
  };
1298
1352
  internals.set(session, {
@@ -0,0 +1,48 @@
1
+ import { i as InvalidRelationships, o as ContentError, t as Content } from "./content-lwPxE9Yu.mjs";
2
+ import { i as CollectionRegistry } from "./types-BC8gVJ6u.mjs";
3
+ //#region src/preparation/session.d.ts
4
+ /** A local source captured by a successfully prepared content revision. */
5
+ type ContentSourceEntry = Readonly<{
6
+ collection: string;
7
+ id: string;
8
+ filePath?: string;
9
+ }>;
10
+ /** Format-neutral integration metadata; the revision token is opaque and session-scoped. */
11
+ type ContentSourceSnapshot = Readonly<{
12
+ revision: string;
13
+ entries: readonly ContentSourceEntry[];
14
+ }>;
15
+ /** Notifications are delivered only after atomic publication, or after a failed attempt. */
16
+ type ContentSourceEvent = {
17
+ type: "ready";
18
+ snapshot: ContentSourceSnapshot;
19
+ } | {
20
+ type: "error";
21
+ error: ContentError;
22
+ };
23
+ type ContentSession<Registry extends CollectionRegistry = {}> = {
24
+ content: Content<Registry>;
25
+ /** Reads the latest successful source snapshot; current preparation errors are rethrown. */
26
+ sources(): ContentSourceSnapshot;
27
+ /** Observes subsequent publications and failures; returns an idempotent unsubscribe function. */
28
+ subscribe(listener: (event: ContentSourceEvent) => void): () => void;
29
+ dispose(): Promise<void>;
30
+ };
31
+ //#endregion
32
+ //#region src/preparation/build.d.ts
33
+ /** Options shared by standalone content builds and host integrations. */
34
+ type BuildContentOptions = {
35
+ /** Exact config path; omission discovers one content-query.config.{ts,mts,cts,js,mjs,cjs} file. */
36
+ config?: string;
37
+ backend?: "in-memory" | "generated";
38
+ /** Generated backend output directory, relative to the application root; defaults to ./.content-query. */
39
+ outDir?: string;
40
+ watch?: boolean;
41
+ };
42
+ /**
43
+ * Builds content independently of a bundler; bind the registry through an erased type import.
44
+ * Generated watch sessions prune previous-run artifacts after their first successful publication.
45
+ */
46
+ declare function buildContent<Registry extends CollectionRegistry = {}>(...arguments_: InvalidRelationships<Registry> extends never ? [options?: BuildContentOptions] : [invalidRegistry: never]): Promise<ContentSession<Registry>>;
47
+ //#endregion
48
+ export { ContentSourceEvent as a, ContentSourceEntry as i, buildContent as n, ContentSourceSnapshot as o, ContentSession as r, BuildContentOptions as t };
package/dist/build.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { n as buildContent, r as ContentSession, t as BuildContentOptions } from "./build-DfKd2bpf.mjs";
2
- export { type BuildContentOptions, type ContentSession, buildContent };
1
+ import { a as ContentSourceEvent, i as ContentSourceEntry, n as buildContent, o as ContentSourceSnapshot, r as ContentSession, t as BuildContentOptions } from "./build-jKnjDjQE.mjs";
2
+ export { type BuildContentOptions, type ContentSession, type ContentSourceEntry, type ContentSourceEvent, type ContentSourceSnapshot, buildContent };
package/dist/build.mjs CHANGED
@@ -1 +1 @@
1
- export { n as buildContent } from "./build-Dxz7P1ur.mjs";
1
+ export { n as buildContent } from "./build-CI4s0oFP.mjs";
package/dist/cli.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { t as ContentError } from "./errors-C2iuBM0G.mjs";
3
- import { t as buildAtRoot } from "./build-Dxz7P1ur.mjs";
3
+ import { t as buildAtRoot } from "./build-CI4s0oFP.mjs";
4
4
  import { cac } from "cac";
5
5
  //#region package.json
6
- var version = "0.2.0";
6
+ var version = "0.3.0";
7
7
  //#endregion
8
8
  //#region src/integrations/cli/arguments.ts
9
9
  /** Validates one path option after CAC's automatic numeric conversion. */
@@ -1,5 +1,5 @@
1
- import { p as MaybePromise } from "./types-ChaNhj42.mjs";
2
- import { a as ComputedContext } from "./types-C-kdMblM.mjs";
1
+ import { m as MaybePromise } from "./types-FmUJ3cH1.mjs";
2
+ import { a as ComputedContext } from "./types-BC8gVJ6u.mjs";
3
3
  import { StandardSchemaV1 } from "@standard-schema/spec";
4
4
  //#region src/computed/text.d.ts
5
5
  /** The syntax removed before computing text-derived fields; literal text preserves all characters. */
@@ -1,22 +1,6 @@
1
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-C-kdMblM.mjs";
3
- import { _ as QueryPlan, n as RelationalQueries, t as RelationalCountOptions, u as Query, v as QueryRegistry } from "./relational-types-DgK3rLuw.mjs";
4
- //#region src/collection/registry-types.d.ts
5
- type Scalar = string | number | bigint | boolean | Date;
6
- type Element<Value> = NonNullable<Value> extends readonly (infer Item)[] ? NonNullable<Item> : NonNullable<Value>;
7
- type Compatible<Left, Right> = NonNullable<Left> extends readonly unknown[] ? NonNullable<Right> extends readonly unknown[] ? false : ScalarCompatible<Left, Right> : ScalarCompatible<Left, Right>;
8
- 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;
9
- 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";
10
- /** Registry-level relationship checks require the complete inferred collection map. */
11
- 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];
12
- //#endregion
13
- //#region src/query/select.d.ts
14
- /** A selection whose collection and selected result are inferred when from() is supplied. */
15
- type SelectFrom<Registry extends CollectionRegistry, Keys extends readonly string[]> = {
16
- /** Binds the collection and rejects selected keys that are absent from its prepared data type. */
17
- 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
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
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 = {
@@ -48,6 +32,22 @@ 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;
@@ -106,4 +106,4 @@ declare function createLiveRuntime(initial: ContentState): {
106
106
  update(next: ContentState): void;
107
107
  };
108
108
  //#endregion
109
- export { ContentError as a, ContentDiagnostic as i, createLiveRuntime as n, ContentErrorCode as o, PreparedRevision as r, InvalidRelationships as s, Content as t };
109
+ export { ContentDiagnostic as a, InvalidRelationships as i, createLiveRuntime as n, ContentError as o, PreparedRevision as r, ContentErrorCode as s, Content as t };
@@ -1,4 +1,4 @@
1
- import { p as MaybePromise } from "./types-ChaNhj42.mjs";
1
+ import { m as MaybePromise } from "./types-FmUJ3cH1.mjs";
2
2
  //#region src/loaders/descriptor.d.ts
3
3
  type GlobOptions = {
4
4
  pattern: string | string[];
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
- import { a as Loader, c as LoaderLogger, d as MetaStore, f as RecordLoader, i as FileSource, l as LoaderSource, n as DataStore, o as LoaderConfig, p as MaybePromise, r as FileEntrySource, s as LoaderContext, t as DataEntry, u as LoaderWatcher } from "./types-ChaNhj42.mjs";
1
+ import { a as FileSource, c as LoaderContext, d as LoaderWatcher, f as MetaStore, i as FileEntrySource, l as LoaderLogger, m as MaybePromise, n as DataEntry, o as Loader, p as RecordLoader, r as DataStore, s as LoaderConfig, t as BodyLoader, u as LoaderSource } from "./types-FmUJ3cH1.mjs";
2
2
  import { n as ContentRenderResult, r as RenderedContent, t as ContentHeading } from "./types-Dh-Sydyw.mjs";
3
- import { a as ComputedContext, c as Relationship, d as SchemaOutput, i as CollectionRegistry, n as BuiltinPayloads, o as PayloadsOf, r as CollectionDefinition, s as QueryData, t as BuiltinPayloadMarker, u as Schema } from "./types-C-kdMblM.mjs";
4
- import { n as GlobOptions } from "./descriptor-D9Th5hzk.mjs";
3
+ import { a as ComputedContext, c as Relationship, d as SchemaOutput, i as CollectionRegistry, n as BuiltinPayloads, o as PayloadsOf, r as CollectionDefinition, s as QueryData, t as BuiltinPayloadMarker, u as Schema } from "./types-BC8gVJ6u.mjs";
4
+ import { n as GlobOptions } from "./descriptor-BUPT2J7X.mjs";
5
5
  import { StandardSchemaV1 } from "@standard-schema/spec";
6
6
  //#region src/collection/source.d.ts
7
7
  type CollectionSource = string | ({
@@ -68,11 +68,12 @@ type Defined<SourceSchema extends StandardSchemaV1<unknown, Record<string, unkno
68
68
  export declare function defineCollection<const SourceSchema extends StandardSchemaV1<unknown, Record<string, unknown>> | undefined = undefined, const ComputedSchemas extends Record<string, Schema> = {}, const Relationships extends Record<string, Relationship> = {}>(definition: {
69
69
  source: RecordSource;
70
70
  loader?: never;
71
- } & CollectionFields<SourceSchema, ComputedSchemas, Relationships, FileSource>): Defined<SourceSchema, ComputedSchemas, Relationships, Loader> & BuiltinPayloadMarker<false>;
71
+ } & CollectionFields<SourceSchema, ComputedSchemas, Relationships, FileSource>): Defined<SourceSchema, ComputedSchemas, Relationships, Loader> & BuiltinPayloadMarker<never>;
72
72
  /**
73
73
  * Declares fields with inferred schema data and optional loader source metadata, plus relationships.
74
- * Record loaders such as `file()` carry no built-in `_body` or `_rendered` payloads.
74
+ * Record loaders such as `file()` carry no built-in `_body` or `_rendered` payloads, and body
75
+ * loaders carry `_body` without `_rendered`.
75
76
  */
76
- export declare function defineCollection<const SourceSchema extends StandardSchemaV1<unknown, Record<string, unknown>> | undefined = undefined, const ComputedSchemas extends Record<string, Schema> = {}, const Relationships extends Record<string, Relationship> = {}, const ContentLoader extends Loader = Loader>(definition: Definition<SourceSchema, ComputedSchemas, Relationships, ContentLoader>): Defined<SourceSchema, ComputedSchemas, Relationships, ContentLoader> & BuiltinPayloadMarker<[ContentLoader] extends [RecordLoader] ? false : true>;
77
+ export declare function defineCollection<const SourceSchema extends StandardSchemaV1<unknown, Record<string, unknown>> | undefined = undefined, const ComputedSchemas extends Record<string, Schema> = {}, const Relationships extends Record<string, Relationship> = {}, const ContentLoader extends Loader = Loader>(definition: Definition<SourceSchema, ComputedSchemas, Relationships, ContentLoader>): Defined<SourceSchema, ComputedSchemas, Relationships, ContentLoader> & BuiltinPayloadMarker<[ContentLoader] extends [RecordLoader] ? never : [ContentLoader] extends [BodyLoader] ? "_body" : keyof BuiltinPayloads>;
77
78
  //#endregion
78
- export type { BuiltinPayloads, CollectionDefinition, CollectionRegistry, CollectionSource, ComputedContext, ContentHeading, ContentRenderResult, DataEntry, DataStore, FileEntrySource, FileSource, Loader, LoaderConfig, LoaderContext, LoaderLogger, LoaderSource, LoaderWatcher, MaybePromise, MetaStore, PayloadsOf, QueryData, RecordLoader, Relationship, RenderedContent };
79
+ export type { BodyLoader, BuiltinPayloads, CollectionDefinition, CollectionRegistry, CollectionSource, ComputedContext, ContentHeading, ContentRenderResult, DataEntry, DataStore, FileEntrySource, FileSource, Loader, LoaderConfig, LoaderContext, LoaderLogger, LoaderSource, LoaderWatcher, MaybePromise, MetaStore, PayloadsOf, QueryData, RecordLoader, Relationship, RenderedContent };
@@ -1,7 +1,7 @@
1
+ import { n as createLiveRuntime, o as ContentError, r as PreparedRevision, t as Content } from "./content-lwPxE9Yu.mjs";
1
2
  import { n as ContentRenderResult } from "./types-Dh-Sydyw.mjs";
2
- import { i as CollectionRegistry } from "./types-C-kdMblM.mjs";
3
- import { a as ContentError, n as createLiveRuntime, r as PreparedRevision, t as Content } from "./content-G69dPPuo.mjs";
4
- import { v as QueryRegistry } from "./relational-types-DgK3rLuw.mjs";
3
+ import { i as CollectionRegistry } from "./types-BC8gVJ6u.mjs";
4
+ import { v as QueryRegistry } from "./relational-types-B45zM34W.mjs";
5
5
  //#region src/artifacts/types.d.ts
6
6
  type ArtifactManifest = {
7
7
  version: number;
@@ -1,5 +1,5 @@
1
- import { a as Loader, c as LoaderLogger, d as MetaStore, f as RecordLoader, i as FileSource, l as LoaderSource, n as DataStore, o as LoaderConfig, r as FileEntrySource, s as LoaderContext, t as DataEntry, u as LoaderWatcher } from "./types-ChaNhj42.mjs";
2
- import { n as GlobOptions, t as FileOptions } from "./descriptor-D9Th5hzk.mjs";
1
+ import { a as FileSource, c as LoaderContext, d as LoaderWatcher, f as MetaStore, i as FileEntrySource, l as LoaderLogger, n as DataEntry, o as Loader, p as RecordLoader, r as DataStore, s as LoaderConfig, t as BodyLoader, u as LoaderSource } from "./types-FmUJ3cH1.mjs";
2
+ import { n as GlobOptions, t as FileOptions } from "./descriptor-BUPT2J7X.mjs";
3
3
  //#region src/loaders/file.d.ts
4
4
  /** Declares a multi-entry data file with typed source paths and record keys, plus an optional parser. */
5
5
  export declare function file(filename: string, options?: FileOptions): RecordLoader<FileEntrySource>;
@@ -8,4 +8,4 @@ export declare function file(filename: string, options?: FileOptions): RecordLoa
8
8
  /** Declares local files with Astro-style IDs and typed, project-relative source paths. */
9
9
  export declare function glob(options: GlobOptions): Loader<FileSource>;
10
10
  //#endregion
11
- export type { DataEntry, DataStore, FileEntrySource, FileOptions, FileSource, GlobOptions, Loader, LoaderConfig, LoaderContext, LoaderLogger, LoaderSource, LoaderWatcher, MetaStore, RecordLoader };
11
+ export type { BodyLoader, DataEntry, DataStore, FileEntrySource, FileOptions, FileSource, GlobOptions, Loader, LoaderConfig, LoaderContext, LoaderLogger, LoaderSource, LoaderWatcher, MetaStore, RecordLoader };
package/dist/next.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as BuildContentOptions } from "./build-DfKd2bpf.mjs";
1
+ import { t as BuildContentOptions } from "./build-jKnjDjQE.mjs";
2
2
  import { NextConfig } from "next";
3
3
  //#region src/integrations/next/plugin.d.ts
4
4
  /** Content locations for Next.js; the adapter chooses generated storage and the watch lifecycle. */
package/dist/next.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { c as contentDeclarations, i as resolveConfigPath, t as buildAtRoot } from "./build-Dxz7P1ur.mjs";
1
+ import { c as contentDeclarations, i as resolveConfigPath, t as buildAtRoot } from "./build-CI4s0oFP.mjs";
2
2
  import { n as serializeContentError } from "./diagnostics-Cu94mZhl.mjs";
3
3
  import path from "node:path";
4
4
  import { randomUUID } from "node:crypto";
package/dist/query.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as RelationalWhere, c as Fields, d as Scalar, f as ScalarField, g as Expression, h as Where, i as RelationalQueryResult, l as Ordered, m as SelectionResult, n as RelationalQueries, o as Adjacent, p as Selection, r as RelationalQueryOptions, s as Field, t as RelationalCountOptions } from "./relational-types-DgK3rLuw.mjs";
1
+ import { a as RelationalWhere, c as Fields, d as Scalar, f as ScalarField, g as Expression, h as Where, i as RelationalQueryResult, l as Ordered, m as SelectionResult, n as RelationalQueries, o as Adjacent, p as Selection, r as RelationalQueryOptions, s as Field, t as RelationalCountOptions } from "./relational-types-B45zM34W.mjs";
2
2
  //#region src/query/operators.d.ts
3
3
  /** Matches a scalar using strict value/date equality. */
4
4
  export declare function eq<Value>(field: ScalarField<Value>, value: NoInfer<Value>): Expression;
@@ -1,4 +1,4 @@
1
- import { c as Relationship, i as CollectionRegistry, l as RelationshipsOf, o as PayloadsOf, s as QueryData } from "./types-C-kdMblM.mjs";
1
+ import { c as Relationship, i as CollectionRegistry, l as RelationshipsOf, o as PayloadsOf, s as QueryData } from "./types-BC8gVJ6u.mjs";
2
2
  //#region src/query/plan.d.ts
3
3
  type PredicateOperator = "eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "in-array" | "not-in-array" | "contains" | "starts-with" | "ends-with" | "like" | "ilike" | "array-contains" | "array-contains-any" | "array-contains-all" | "array-contained" | "is-null" | "is-undefined" | "is-missing";
4
4
  type Expression = {
@@ -1,3 +1,3 @@
1
+ import { a as ContentDiagnostic, o as ContentError, s as ContentErrorCode, t as Content } from "./content-lwPxE9Yu.mjs";
1
2
  import { n as ContentRenderResult, t as ContentHeading } from "./types-Dh-Sydyw.mjs";
2
- import { a as ContentError, i as ContentDiagnostic, o as ContentErrorCode, t as Content } from "./content-G69dPPuo.mjs";
3
3
  export { type Content, type ContentDiagnostic, ContentError, type ContentErrorCode, type ContentHeading, type ContentRenderResult };
@@ -1,4 +1,4 @@
1
- import { a as Loader } from "./types-ChaNhj42.mjs";
1
+ import { o as Loader } from "./types-FmUJ3cH1.mjs";
2
2
  import { n as ContentRenderResult } from "./types-Dh-Sydyw.mjs";
3
3
  import { StandardSchemaV1 } from "@standard-schema/spec";
4
4
  //#region src/collection/types.d.ts
@@ -57,13 +57,13 @@ type BuiltinPayloads = {
57
57
  /** HTML and headings; entries without a rendering path reject with `not-renderable`. */
58
58
  _rendered: ContentRenderResult;
59
59
  };
60
- /** Type-only marker recording whether a defined collection carries built-in payloads. */
61
- type BuiltinPayloadMarker<Available extends boolean> = {
60
+ /** Type-only marker recording which built-in payloads a defined collection carries. */
61
+ type BuiltinPayloadMarker<Available extends keyof BuiltinPayloads> = {
62
62
  readonly [builtinPayloadsType]?: Available;
63
63
  };
64
64
  /** Payload names and value types that queries can request for a collection definition. */
65
65
  type PayloadsOf<Definition> = Definition extends {
66
66
  readonly [builtinPayloadsType]?: infer Available;
67
- } ? [Available] extends [false | undefined] ? {} : BuiltinPayloads : BuiltinPayloads;
67
+ } ? Pick<BuiltinPayloads, Available & keyof BuiltinPayloads> : BuiltinPayloads;
68
68
  //#endregion
69
69
  export { ComputedContext as a, Relationship as c, SchemaOutput as d, CollectionRegistry as i, RelationshipsOf as l, BuiltinPayloads as n, PayloadsOf as o, CollectionDefinition as r, QueryData as s, BuiltinPayloadMarker as t, Schema as u };
@@ -5,6 +5,7 @@ type MaybePromise<Value> = Value | Promise<Value>;
5
5
  //#region src/loaders/types.d.ts
6
6
  declare const loaderSourceType: unique symbol;
7
7
  declare const recordLoaderType: unique symbol;
8
+ declare const bodyLoaderType: unique symbol;
8
9
  /** A stored content record, following Astro's build-time DataEntry contract. */
9
10
  type DataEntry<Data extends Record<string, unknown> = Record<string, unknown>, Source = unknown> = {
10
11
  id: string;
@@ -135,9 +136,17 @@ type RecordLoader<Source = unknown> = Loader<Source> & {
135
136
  /** Type-only marker: entries never carry built-in `_body` or `_rendered` payloads. */
136
137
  readonly [recordLoaderType]: true;
137
138
  };
139
+ /**
140
+ * A loader whose entries carry raw bodies but never rendered output, such as a loader for a
141
+ * format that the host application compiles itself.
142
+ */
143
+ type BodyLoader<Source = unknown> = Loader<Source> & {
144
+ /** Type-only marker: entries offer the built-in `_body` payload but never `_rendered`. */
145
+ readonly [bodyLoaderType]: true;
146
+ };
138
147
  /** Infers the source metadata a typed loader supplies to computed resolvers. */
139
148
  type LoaderSource<Value> = Value extends {
140
149
  readonly [loaderSourceType]?: infer Source;
141
150
  } ? Source : unknown;
142
151
  //#endregion
143
- export { Loader as a, LoaderLogger as c, MetaStore as d, RecordLoader as f, FileSource as i, LoaderSource as l, DataStore as n, LoaderConfig as o, MaybePromise as p, FileEntrySource as r, LoaderContext as s, DataEntry as t, LoaderWatcher as u };
152
+ export { FileSource as a, LoaderContext as c, LoaderWatcher as d, MetaStore as f, FileEntrySource as i, LoaderLogger as l, MaybePromise as m, DataEntry as n, Loader as o, RecordLoader as p, DataStore as r, LoaderConfig as s, BodyLoader as t, LoaderSource as u };
package/dist/vite.d.mts CHANGED
@@ -1,6 +1,18 @@
1
- import { t as BuildContentOptions } from "./build-DfKd2bpf.mjs";
1
+ import { a as ContentSourceEvent, o as ContentSourceSnapshot, t as BuildContentOptions } from "./build-jKnjDjQE.mjs";
2
2
  import { Plugin } from "vite";
3
3
  //#region src/integrations/vite/plugin.d.ts
4
+ /** Public, format-neutral hooks for integrations sharing this plugin's preparation session. */
5
+ type ContentQueryPluginApi = {
6
+ /** Prepares or awaits the shared session and returns its current source identities. */
7
+ prepare(): Promise<ContentSourceSnapshot>;
8
+ /** Observes subsequent source publications and errors without owning another watcher. */
9
+ subscribe(listener: (event: ContentSourceEvent) => void): () => void;
10
+ };
11
+ /** A normal Vite plugin with a typed source-integration API. */
12
+ interface ContentQueryPlugin extends Plugin<ContentQueryPluginApi> {
13
+ api: ContentQueryPluginApi;
14
+ }
4
15
  /** Integrates in-memory content with Vite; opt into generated storage for standalone artifacts. */
5
- export declare function contentQuery(options?: Omit<BuildContentOptions, "watch">): Plugin;
6
- //#endregion
16
+ export declare function contentQuery(options?: Omit<BuildContentOptions, "watch">): ContentQueryPlugin;
17
+ //#endregion
18
+ export type { ContentQueryPlugin, ContentQueryPluginApi };
package/dist/vite.mjs CHANGED
@@ -1,4 +1,4 @@
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-Dxz7P1ur.mjs";
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-CI4s0oFP.mjs";
2
2
  import { n as serializeContentError } from "./diagnostics-Cu94mZhl.mjs";
3
3
  import { createRequire } from "node:module";
4
4
  import path from "node:path";
@@ -122,6 +122,7 @@ function contentQuery(options = {}) {
122
122
  let encoding;
123
123
  const bridgeKey = randomUUID();
124
124
  const refreshClients = /* @__PURE__ */ new WeakSet();
125
+ const sourceListeners = /* @__PURE__ */ new Set();
125
126
  /** Publishes type-only facade binding without importing configuration in executable code. */
126
127
  async function writeTypes(configPath) {
127
128
  const directory = path.join(config.root, ".content-query");
@@ -216,6 +217,14 @@ function contentQuery(options = {}) {
216
217
  if (config.command === "serve") changed(state).catch(deliveryFailed);
217
218
  });
218
219
  session = prepared;
220
+ prepared.subscribe((event) => {
221
+ const listeners = [...sourceListeners];
222
+ for (const listener of listeners) try {
223
+ listener(event);
224
+ } catch (cause) {
225
+ config.logger.error(`Content source subscriber failed: ${cause instanceof Error ? cause.message : String(cause)}`);
226
+ }
227
+ });
219
228
  try {
220
229
  if (config.command === "serve") {
221
230
  const global = globalThis;
@@ -235,6 +244,19 @@ function contentQuery(options = {}) {
235
244
  }
236
245
  return {
237
246
  name: "content-query",
247
+ api: {
248
+ async prepare() {
249
+ const prepared = await ensureSession();
250
+ await sessionInternals(prepared).idle();
251
+ return prepared.sources();
252
+ },
253
+ subscribe(listener) {
254
+ sourceListeners.add(listener);
255
+ return () => {
256
+ sourceListeners.delete(listener);
257
+ };
258
+ }
259
+ },
238
260
  enforce: "pre",
239
261
  configResolved(resolved) {
240
262
  config = resolved;
@@ -288,6 +310,7 @@ function contentQuery(options = {}) {
288
310
  server = void 0;
289
311
  await starting?.catch(() => void 0);
290
312
  await session?.dispose();
313
+ sourceListeners.clear();
291
314
  globalThis[bridgeSymbol]?.delete(bridgeKey);
292
315
  }
293
316
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "content-query",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Typed, framework-independent content collections, queries, and rendering.",
5
5
  "homepage": "https://github.com/yangshun/content-query#readme",
6
6
  "bugs": {
@@ -71,7 +71,7 @@
71
71
  "vite-plus": "1.3.0"
72
72
  },
73
73
  "peerDependencies": {
74
- "next": "^16.0.0",
74
+ "next": "^15.0.0 || ^16.0.0",
75
75
  "vite": "^8.0.0"
76
76
  },
77
77
  "peerDependenciesMeta": {
@@ -1,25 +0,0 @@
1
- import { i as CollectionRegistry } from "./types-C-kdMblM.mjs";
2
- import { s as InvalidRelationships, t as Content } from "./content-G69dPPuo.mjs";
3
- //#region src/preparation/session.d.ts
4
- type ContentSession<Registry extends CollectionRegistry = {}> = {
5
- content: Content<Registry>;
6
- dispose(): Promise<void>;
7
- };
8
- //#endregion
9
- //#region src/preparation/build.d.ts
10
- /** Options shared by standalone content builds and host integrations. */
11
- type BuildContentOptions = {
12
- /** Exact config path; omission discovers one content-query.config.{ts,mts,cts,js,mjs,cjs} file. */
13
- config?: string;
14
- backend?: "in-memory" | "generated";
15
- /** Generated backend output directory, relative to the application root; defaults to ./.content-query. */
16
- outDir?: string;
17
- watch?: boolean;
18
- };
19
- /**
20
- * Builds content independently of a bundler; bind the registry through an erased type import.
21
- * Generated watch sessions prune previous-run artifacts after their first successful publication.
22
- */
23
- declare function buildContent<Registry extends CollectionRegistry = {}>(...arguments_: InvalidRelationships<Registry> extends never ? [options?: BuildContentOptions] : [invalidRegistry: never]): Promise<ContentSession<Registry>>;
24
- //#endregion
25
- export { buildContent as n, ContentSession as r, BuildContentOptions as t };