content-query 0.0.2 → 0.2.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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +91 -2
  3. package/dist/build-DfKd2bpf.d.mts +25 -0
  4. package/dist/build-Dxz7P1ur.mjs +1439 -0
  5. package/dist/build.d.mts +2 -0
  6. package/dist/build.mjs +1 -0
  7. package/dist/cli.d.mts +1 -0
  8. package/dist/cli.mjs +97 -0
  9. package/dist/computed.d.mts +43 -0
  10. package/dist/computed.mjs +139 -0
  11. package/dist/content-G69dPPuo.d.mts +109 -0
  12. package/dist/content-N1gYshjg.mjs +1028 -0
  13. package/dist/descriptor-D9Th5hzk.d.mts +18 -0
  14. package/dist/diagnostics-Cu94mZhl.mjs +80 -0
  15. package/dist/dist-C7_FOzY_.mjs +5405 -0
  16. package/dist/errors-C2iuBM0G.mjs +72 -0
  17. package/dist/github-slugger-BMgR_Q0I.mjs +72 -0
  18. package/dist/glob-Bv0dpvCQ.mjs +159 -0
  19. package/dist/index.d.mts +78 -0
  20. package/dist/index.mjs +46 -0
  21. package/dist/internal/markdown.d.mts +5 -0
  22. package/dist/internal/markdown.mjs +1 -0
  23. package/dist/internal.d.mts +54 -0
  24. package/dist/internal.mjs +89 -0
  25. package/dist/loaders.d.mts +11 -0
  26. package/dist/loaders.mjs +86 -0
  27. package/dist/markdown-CHvBBJNa.mjs +54 -0
  28. package/dist/next.d.mts +20 -0
  29. package/dist/next.mjs +142 -0
  30. package/dist/operators-UqWUhKQJ.mjs +173 -0
  31. package/dist/query.d.mts +44 -0
  32. package/dist/query.mjs +1 -0
  33. package/dist/relational-types-DgK3rLuw.d.mts +278 -0
  34. package/dist/rolldown-runtime-Bg1v5YCA.mjs +34 -0
  35. package/dist/runtime.d.mts +3 -0
  36. package/dist/runtime.mjs +5 -0
  37. package/dist/schema-C5Str-E8.mjs +20 -0
  38. package/dist/schema.d.mts +11 -0
  39. package/dist/schema.mjs +1 -0
  40. package/dist/server-only-CIJUlyL-.mjs +7 -0
  41. package/dist/types-C-kdMblM.d.mts +69 -0
  42. package/dist/types-ChaNhj42.d.mts +143 -0
  43. package/dist/types-Dh-Sydyw.d.mts +20 -0
  44. package/dist/validation-D_XdE1pF.mjs +84 -0
  45. package/dist/values-BbTV4QT-.mjs +901 -0
  46. package/dist/vite.d.mts +6 -0
  47. package/dist/vite.mjs +296 -0
  48. package/package.json +92 -10
@@ -0,0 +1,6 @@
1
+ import { t as BuildContentOptions } from "./build-DfKd2bpf.mjs";
2
+ import { Plugin } from "vite";
3
+ //#region src/integrations/vite/plugin.d.ts
4
+ /** 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
package/dist/vite.mjs ADDED
@@ -0,0 +1,296 @@
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";
2
+ import { n as serializeContentError } from "./diagnostics-Cu94mZhl.mjs";
3
+ import { createRequire } from "node:module";
4
+ import path from "node:path";
5
+ import { randomUUID } from "node:crypto";
6
+ import { realpathSync } from "node:fs";
7
+ const preparedFacade = "content-query:prepared-server";
8
+ const clientModule = "\0content-query:client.js";
9
+ const virtualPrefix = "\0content-query:";
10
+ /** Provides a client-safe subscription without exposing the content runtime or registry. */
11
+ function devClient(development) {
12
+ if (!development) return "export function onContentUpdate() { return () => {}; }";
13
+ return `const listeners = new Set();
14
+ let pending = false;
15
+ let refreshing = false;
16
+ async function refresh() {
17
+ pending = true;
18
+ if (refreshing) return;
19
+ refreshing = true;
20
+ try {
21
+ while (pending) {
22
+ pending = false;
23
+ try {
24
+ await Promise.all([...listeners].map(callback => Promise.resolve().then(callback)));
25
+ if (typeof document !== "undefined") {
26
+ for (const overlay of document.querySelectorAll("vite-error-overlay")) {
27
+ if (overlay.shadowRoot?.querySelector(".plugin")?.textContent.trim() === "[plugin:content-query]") overlay.close();
28
+ }
29
+ }
30
+ } catch (error) {
31
+ console.error("[content-query] Could not refresh page data", error);
32
+ }
33
+ }
34
+ } finally { refreshing = false; }
35
+ }
36
+ if (import.meta.hot) {
37
+ import.meta.hot.on("content-query:update", refresh);
38
+ import.meta.hot.on("vite:ws:connect", () => { if (listeners.size) import.meta.hot.send("content-query:subscribe"); });
39
+ import.meta.hot.dispose(() => { listeners.clear(); import.meta.hot.send("content-query:unsubscribe"); });
40
+ }
41
+ export function onContentUpdate(callback) {
42
+ listeners.add(callback);
43
+ import.meta.hot?.send("content-query:subscribe");
44
+ return () => {
45
+ listeners.delete(callback);
46
+ if (!listeners.size) import.meta.hot?.send("content-query:unsubscribe");
47
+ };
48
+ }`;
49
+ }
50
+ /** Encodes a complete prepared revision or its contextual development error for isolated runtimes. */
51
+ async function preparedModules(state) {
52
+ const revision = state.revision;
53
+ const error = state.error;
54
+ let next;
55
+ if (error) {
56
+ 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");
61
+ }
62
+ return next;
63
+ }
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;
70
+ if (import.meta.hot) {
71
+ const apply = next => { if (next) live.update(next); };
72
+ import.meta.hot.accept(${JSON.stringify(preparedFacade)}, apply);
73
+ // Module runners unwrap virtual IDs in update messages. Register that form too.
74
+ const hot = import.meta.hot;
75
+ hot.accept(${JSON.stringify(`${virtualPrefix}prepared-server.js`)}, apply);
76
+ }`;
77
+ }
78
+ //#endregion
79
+ //#region src/integrations/vite/client-boundary.ts
80
+ /** Locates this installed package, so application paths named "content-query" never match it. */
81
+ function packageDirectory() {
82
+ try {
83
+ return path.dirname(realpathSync(createRequire(import.meta.url).resolve("content-query/package.json")));
84
+ } catch {
85
+ return;
86
+ }
87
+ }
88
+ const installed = packageDirectory();
89
+ /** Detects the consuming environment before any server module is loaded. */
90
+ function clientEnvironment(context, config, ssr) {
91
+ const environment = context.environment;
92
+ return environment?.config?.consumer ? environment.config.consumer === "client" : environment?.name ? environment.name === "client" : !ssr && !config.build.ssr;
93
+ }
94
+ /** Resolves physical paths without relying on a server entry's import spelling. */
95
+ function physicalPath(source, config, importer) {
96
+ const cleaned = source.split("?")[0];
97
+ return cleaned.startsWith(".") ? path.resolve(importer ? path.dirname(importer) : config.root, cleaned) : path.isAbsolute(cleaned) ? cleaned : "";
98
+ }
99
+ /** Identifies build/runtime dependencies that must never enter a client graph. */
100
+ function forbidden(source, config, options, importer) {
101
+ if (source === "content-query:client" || source === "\0content-query:client.js") return false;
102
+ if (source.startsWith("content-query:") || source.startsWith("virtual:content-query/") || source.startsWith("\0content-query:") || /^content-query(?:\/|$)/.test(source)) return true;
103
+ const filename = physicalPath(source, config, importer).replace(/\\/g, "/");
104
+ if (!filename) return false;
105
+ const configPaths = configCandidates(config.root, options.config).map((filename) => filename.replace(/\\/g, "/"));
106
+ const outputPath = path.resolve(config.root, options.outDir ?? "./.content-query").replace(/\\/g, "/");
107
+ return configPaths.includes(filename) || filename.startsWith(`${outputPath}/`) || filename.includes("/node_modules/content-query/") || installed !== void 0 && ["dist", "src"].some((directory) => withinDirectory(path.join(installed, directory), filename));
108
+ }
109
+ //#endregion
110
+ //#region src/integrations/vite/plugin.ts
111
+ const bridgeSymbol = Symbol.for("content-query.dev-sessions");
112
+ /** Integrates in-memory content with Vite; opt into generated storage for standalone artifacts. */
113
+ function contentQuery(options = {}) {
114
+ let config;
115
+ let building = false;
116
+ let server;
117
+ let session;
118
+ let starting;
119
+ let modules = /* @__PURE__ */ new Map();
120
+ let deliveredRevision;
121
+ let deliveredError;
122
+ let encoding;
123
+ const bridgeKey = randomUUID();
124
+ const refreshClients = /* @__PURE__ */ new WeakSet();
125
+ /** Publishes type-only facade binding without importing configuration in executable code. */
126
+ async function writeTypes(configPath) {
127
+ const directory = path.join(config.root, ".content-query");
128
+ await writeChanged(path.join(directory, "types.d.ts"), virtualDeclarations(directory, configPath));
129
+ }
130
+ /** Notifies the host after atomic publication or a recoverable preparation failure. */
131
+ async function refreshModules(state) {
132
+ const revision = state.revision;
133
+ const error = state.error;
134
+ if (deliveredRevision === revision && deliveredError === error && modules.size) return;
135
+ if (!encoding || encoding.revision !== revision || encoding.error !== error) encoding = {
136
+ revision,
137
+ error,
138
+ pending: preparedModules(state)
139
+ };
140
+ const current = encoding;
141
+ try {
142
+ const next = await current.pending;
143
+ if (state.revision !== revision || state.error !== error) {
144
+ await refreshModules(state);
145
+ return;
146
+ }
147
+ if (deliveredRevision === revision && deliveredError === error && modules.size) return;
148
+ modules = new Map([...modules, ...next]);
149
+ deliveredRevision = revision;
150
+ deliveredError = error;
151
+ } finally {
152
+ if (encoding === current) encoding = void 0;
153
+ }
154
+ }
155
+ /** Notifies every host environment after portable state modules are ready. */
156
+ async function changed(state) {
157
+ await refreshModules(state);
158
+ if (!server) return;
159
+ for (const environment of Object.values(server.environments)) {
160
+ const boundary = environment.moduleGraph.getModuleById(`${virtualPrefix}server.js`);
161
+ const prepared = environment.moduleGraph.getModuleById(`${virtualPrefix}prepared-server.js`);
162
+ for (const module of environment.moduleGraph.idToModuleMap.values()) if (module.id === `content-query:prepared-server.js`) environment.moduleGraph.invalidateModule(module);
163
+ if (boundary && prepared) environment.hot.send({
164
+ type: "update",
165
+ updates: [{
166
+ type: "js-update",
167
+ timestamp: Date.now(),
168
+ path: boundary.url,
169
+ acceptedPath: prepared.url,
170
+ explicitImportRequired: false,
171
+ isWithinCircularImport: false
172
+ }]
173
+ });
174
+ }
175
+ if (state.error) server.ws.send({
176
+ type: "error",
177
+ err: {
178
+ message: state.error.message,
179
+ stack: state.error.stack ?? "",
180
+ plugin: "content-query"
181
+ }
182
+ });
183
+ else {
184
+ server.ws.send({
185
+ type: "custom",
186
+ event: "content-query:update",
187
+ data: {}
188
+ });
189
+ for (const client of server.ws.clients) if (!refreshClients.has(client)) client.send({ type: "full-reload" });
190
+ }
191
+ }
192
+ /** Reports rejected host notifications instead of leaving a detached promise unhandled. */
193
+ function deliveryFailed(cause) {
194
+ const message = `[content-query] Could not deliver prepared content: ${cause instanceof Error ? cause.message : String(cause)}`;
195
+ config.logger.error(message);
196
+ try {
197
+ server?.ws.send({
198
+ type: "error",
199
+ err: {
200
+ message,
201
+ stack: cause instanceof Error ? cause.stack ?? "" : "",
202
+ plugin: "content-query"
203
+ }
204
+ });
205
+ } catch {}
206
+ }
207
+ /** Starts exactly one host-owned preparation/watch session. */
208
+ async function ensureSession() {
209
+ starting ??= resolveConfigPath(config.root, options.config).then(async (configPath) => {
210
+ const prepared = await buildAtRoot(config.root, {
211
+ ...options,
212
+ backend: options.backend ?? "in-memory",
213
+ config: configPath,
214
+ watch: !building && !config.isProduction && config.command === "serve" && config.server.watch !== null
215
+ }, (state) => {
216
+ if (config.command === "serve") changed(state).catch(deliveryFailed);
217
+ });
218
+ session = prepared;
219
+ try {
220
+ if (config.command === "serve") {
221
+ const global = globalThis;
222
+ global[bridgeSymbol] ??= /* @__PURE__ */ new Map();
223
+ global[bridgeSymbol].set(bridgeKey, prepared);
224
+ await refreshModules(sessionInternals(prepared).state);
225
+ }
226
+ await writeTypes(configPath);
227
+ } catch (cause) {
228
+ await prepared.dispose();
229
+ globalThis[bridgeSymbol]?.delete(bridgeKey);
230
+ throw cause;
231
+ }
232
+ return prepared;
233
+ });
234
+ return starting;
235
+ }
236
+ return {
237
+ name: "content-query",
238
+ enforce: "pre",
239
+ configResolved(resolved) {
240
+ config = resolved;
241
+ building ||= resolved.command === "build";
242
+ if (resolved.server.watch !== null) {
243
+ const ignored = resolved.server.watch?.ignored;
244
+ const output = path.resolve(resolved.root, options.outDir ?? "./.content-query");
245
+ resolved.server.watch = {
246
+ ...resolved.server.watch,
247
+ ignored: [...ignored === void 0 ? [] : Array.isArray(ignored) ? ignored : [ignored], (filename) => withinDirectory(output, path.resolve(filename))]
248
+ };
249
+ }
250
+ },
251
+ configureServer(viteServer) {
252
+ server = viteServer;
253
+ server.ws.on("content-query:subscribe", (_data, client) => refreshClients.add(client));
254
+ server.ws.on("content-query:unsubscribe", (_data, client) => refreshClients.delete(client));
255
+ ensureSession().catch(() => {});
256
+ },
257
+ async buildStart() {
258
+ if (config.command === "build" && clientEnvironment(this, config)) return;
259
+ const prepared = await ensureSession();
260
+ if (config.command === "build") modules = await revisionModules(sessionInternals(prepared).state.revision, { payloadChunkBytes: 131072 });
261
+ },
262
+ resolveId(source, importer, resolveOptions) {
263
+ 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}` : "."}`);
264
+ if (source === "content-query:server") return `${virtualPrefix}server.js`;
265
+ if (source === "content-query:client") return clientModule;
266
+ if (source === "content-query:prepared-server") return `${virtualPrefix}prepared-server.js`;
267
+ if (source.startsWith("\0content-query:")) return source;
268
+ if (source.startsWith(".") && importer?.startsWith("\0content-query:")) return `${virtualPrefix}${path.posix.normalize(path.posix.join(path.posix.dirname(importer.slice(15)), source))}`;
269
+ if (physicalPath(source, config, importer) === path.resolve(config.root, options.outDir ?? "./.content-query", "server.js")) return `${virtualPrefix}server.js`;
270
+ },
271
+ async load(id, loadOptions) {
272
+ if (id === "\0content-query:client.js") return devClient(config.command === "serve");
273
+ if (!id.startsWith("\0content-query:")) return;
274
+ if (clientEnvironment(this, config, loadOptions?.ssr)) this.error("[content-query] Content runtime cannot be loaded by the client.");
275
+ await ensureSession();
276
+ if (config.command === "serve") {
277
+ await refreshModules(sessionInternals(session).state);
278
+ if (id === `content-query:server.js`) return devFacade(bridgeKey);
279
+ if (id === `content-query:prepared-server.js`) return modules.get("server.js");
280
+ return modules.get(id.slice(15));
281
+ }
282
+ return modules.get(id.slice(15));
283
+ },
284
+ transform(_code, id, transformOptions) {
285
+ if (clientEnvironment(this, config, transformOptions?.ssr) && forbidden(id, config, options)) this.error(`[content-query] Server-only module is reachable from the client: ${id}`);
286
+ },
287
+ async closeBundle() {
288
+ server = void 0;
289
+ await starting?.catch(() => void 0);
290
+ await session?.dispose();
291
+ globalThis[bridgeSymbol]?.delete(bridgeKey);
292
+ }
293
+ };
294
+ }
295
+ //#endregion
296
+ export { contentQuery };
package/package.json CHANGED
@@ -1,13 +1,95 @@
1
1
  {
2
2
  "name": "content-query",
3
- "version": "0.0.2",
4
- "description": "",
5
- "main": "index.js",
6
- "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
3
+ "version": "0.2.0",
4
+ "description": "Typed, framework-independent content collections, queries, and rendering.",
5
+ "homepage": "https://github.com/yangshun/content-query#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/yangshun/content-query/issues"
8
+ },
9
+ "license": "MIT",
10
+ "author": "Yangshun Tay",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/yangshun/content-query.git",
14
+ "directory": "packages/content-query"
15
+ },
16
+ "bin": {
17
+ "content-query": "./dist/cli.mjs"
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "LICENSE"
22
+ ],
23
+ "type": "module",
24
+ "types": "./dist/index.d.mts",
25
+ "exports": {
26
+ ".": "./dist/index.mjs",
27
+ "./build": "./dist/build.mjs",
28
+ "./cli": "./dist/cli.mjs",
29
+ "./computed": "./dist/computed.mjs",
30
+ "./internal": "./dist/internal.mjs",
31
+ "./internal/markdown": "./dist/internal/markdown.mjs",
32
+ "./loaders": "./dist/loaders.mjs",
33
+ "./next": "./dist/next.mjs",
34
+ "./query": "./dist/query.mjs",
35
+ "./runtime": "./dist/runtime.mjs",
36
+ "./schema": "./dist/schema.mjs",
37
+ "./vite": "./dist/vite.mjs",
38
+ "./package.json": "./package.json"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "dependencies": {
44
+ "@shikijs/engine-javascript": "^4.5.0",
45
+ "@standard-schema/spec": "^1.1.0",
46
+ "cac": "^7.1.0",
47
+ "chokidar": "^5.0.0",
48
+ "devalue": "6.0.2",
49
+ "fflate": "^0.8.3",
50
+ "github-slugger": "^2.0.0",
51
+ "gray-matter": "^4.0.3",
52
+ "htmlparser2": "^12.0.0",
53
+ "jiti": "^2.7.0",
54
+ "markdown-exit": "^1.3.0",
55
+ "picomatch": "^4.0.4",
56
+ "reading-time": "^1.5.0",
57
+ "shiki": "^4.5.0",
58
+ "smol-toml": "^1.4.2",
59
+ "tinyglobby": "^0.2.17",
60
+ "yaml": "^2.9.1",
61
+ "zod": "^4.3.6"
8
62
  },
9
- "keywords": [],
10
- "author": "",
11
- "license": "ISC",
12
- "packageManager": "pnpm@10.28.1"
13
- }
63
+ "devDependencies": {
64
+ "@ascorbic/csv-loader": "^2.0.3",
65
+ "@types/node": "^25.5.0",
66
+ "@types/picomatch": "^4.0.3",
67
+ "astro": "7.4.0",
68
+ "next": "16.4.2",
69
+ "tsrs": "0.18.0-ts7.1.0-dev.20260929",
70
+ "typescript": "npm:@typescript/typescript6@6.0.2",
71
+ "vite-plus": "1.3.0"
72
+ },
73
+ "peerDependencies": {
74
+ "next": "^16.0.0",
75
+ "vite": "^8.0.0"
76
+ },
77
+ "peerDependenciesMeta": {
78
+ "next": {
79
+ "optional": true
80
+ },
81
+ "vite": {
82
+ "optional": true
83
+ }
84
+ },
85
+ "engines": {
86
+ "node": ">=22.12.0"
87
+ },
88
+ "scripts": {
89
+ "build": "vp pack",
90
+ "dev": "vp pack --watch --no-clean",
91
+ "check": "vp check && vp run typecheck",
92
+ "typecheck": "tsrs --noEmit",
93
+ "test": "vp test"
94
+ }
95
+ }