@bitcobblers/wod-wiki-tools 0.0.0-stage → 0.36.2134

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 (74) hide show
  1. package/dist/bin/wod.cjs +3543 -0
  2. package/dist/bin/wod.cjs.map +1 -0
  3. package/dist/bin/wod.d.cts +1 -0
  4. package/dist/bin/wod.d.ts +1 -0
  5. package/dist/bin/wod.mjs +27 -0
  6. package/dist/bin/wod.mjs.map +1 -0
  7. package/dist/chunk-32FASQP5.mjs +123 -0
  8. package/dist/chunk-32FASQP5.mjs.map +1 -0
  9. package/dist/chunk-6L5ZDZJ7.mjs +15 -0
  10. package/dist/chunk-6L5ZDZJ7.mjs.map +1 -0
  11. package/dist/chunk-B3QJYJVI.mjs +2061 -0
  12. package/dist/chunk-B3QJYJVI.mjs.map +1 -0
  13. package/dist/chunk-EUQWEX6Z.mjs +253 -0
  14. package/dist/chunk-EUQWEX6Z.mjs.map +1 -0
  15. package/dist/chunk-K3GZZFAC.mjs +300 -0
  16. package/dist/chunk-K3GZZFAC.mjs.map +1 -0
  17. package/dist/compiler-KT6P3AC6.mjs +730 -0
  18. package/dist/compiler-KT6P3AC6.mjs.map +1 -0
  19. package/dist/index.cjs +3548 -0
  20. package/dist/index.cjs.map +1 -0
  21. package/dist/index.d.cts +356 -0
  22. package/dist/index.d.ts +356 -0
  23. package/dist/index.mjs +12 -0
  24. package/dist/index.mjs.map +1 -0
  25. package/dist/server/app.cjs +299 -0
  26. package/dist/server/app.cjs.map +1 -0
  27. package/dist/server/app.d.cts +24 -0
  28. package/dist/server/app.d.ts +24 -0
  29. package/dist/server/app.mjs +9 -0
  30. package/dist/server/app.mjs.map +1 -0
  31. package/dist/server-3TJZDKQC.mjs +11 -0
  32. package/dist/server-3TJZDKQC.mjs.map +1 -0
  33. package/dist/web/playground/ai-skills/parse.md +656 -0
  34. package/dist/web/playground/assets/main-Bhn9NHgQ.css +2 -0
  35. package/dist/web/playground/assets/main-Bj_mxNMk.js +212 -0
  36. package/dist/web/playground/assets/rolldown-runtime-C0FnF6B9.js +1 -0
  37. package/dist/web/playground/assets/statementBuilderFlow-DXlHS86r.js +2 -0
  38. package/dist/web/playground/assets/vendor-BlJqgFME.js +201 -0
  39. package/dist/web/playground/assets/vendor-codemirror-BtJgoBjf.js +40 -0
  40. package/dist/web/playground/fonts/Inter-Regular.woff2 +0 -0
  41. package/dist/web/playground/fonts/Inter-SemiBold.woff2 +0 -0
  42. package/dist/web/playground/images/hero/editing.png +0 -0
  43. package/dist/web/playground/images/hero/journal.png +0 -0
  44. package/dist/web/playground/images/hero/results.png +0 -0
  45. package/dist/web/playground/images/hero/timer.png +0 -0
  46. package/dist/web/playground/images/wod-wiki-logo-dark.png +0 -0
  47. package/dist/web/playground/images/wod-wiki-logo-light.png +0 -0
  48. package/dist/web/playground/index.html +60 -0
  49. package/dist/web/playground/seed/core/chunks/canvas.054cd7db.json +1 -0
  50. package/dist/web/playground/seed/core/chunks/template.e5e0e82d.json +1 -0
  51. package/dist/web/playground/seed/core/manifest.json +24 -0
  52. package/dist/web/playground/seed/core/report.html +43 -0
  53. package/dist/web/server/ai-skills/parse.md +656 -0
  54. package/dist/web/server/assets/main-Bhn9NHgQ.css +2 -0
  55. package/dist/web/server/assets/main-CfAVkURA.js +211 -0
  56. package/dist/web/server/assets/rolldown-runtime-C0FnF6B9.js +1 -0
  57. package/dist/web/server/assets/statementBuilderFlow-CXugzTXB.js +2 -0
  58. package/dist/web/server/assets/vendor-BlJqgFME.js +201 -0
  59. package/dist/web/server/assets/vendor-codemirror-BtJgoBjf.js +40 -0
  60. package/dist/web/server/fonts/Inter-Regular.woff2 +0 -0
  61. package/dist/web/server/fonts/Inter-SemiBold.woff2 +0 -0
  62. package/dist/web/server/images/hero/editing.png +0 -0
  63. package/dist/web/server/images/hero/journal.png +0 -0
  64. package/dist/web/server/images/hero/results.png +0 -0
  65. package/dist/web/server/images/hero/timer.png +0 -0
  66. package/dist/web/server/images/wod-wiki-logo-dark.png +0 -0
  67. package/dist/web/server/images/wod-wiki-logo-light.png +0 -0
  68. package/dist/web/server/index.html +60 -0
  69. package/dist/web/server/seed/core/chunks/canvas.054cd7db.json +1 -0
  70. package/dist/web/server/seed/core/chunks/template.e5e0e82d.json +1 -0
  71. package/dist/web/server/seed/core/manifest.json +24 -0
  72. package/dist/web/server/seed/core/report.html +43 -0
  73. package/package.json +46 -4
  74. package/README.md +0 -3
@@ -0,0 +1,356 @@
1
+ import { WodWikiIRFile, StatementNode, ExecutionLog, LanguagePack } from '@bitcobblers/wod-wiki-engine';
2
+ import { INowProvider, IEffort } from '@bitcobblers/wod-wiki-lang';
3
+ import { QueryServiceStores, QueryResult, RowsQueryResult, FindQueryResult, PipelineResult } from '@bitcobblers/wod-wiki-wql';
4
+ import { EventRecord, AnalyticsDataPoint, Session, Note, BlockIndexRow, BlockRevision } from '@bitcobblers/wod-wiki-core';
5
+ import { IStorage } from '@bitcobblers/wod-wiki-storage';
6
+ import { Database } from '@bitcobblers/wod-wiki-storage/sql';
7
+
8
+ /**
9
+ * Headless Parse Command Runner
10
+ *
11
+ * Parses Whiteboard Script source text and emits a versioned IR parse-tree envelope.
12
+ */
13
+
14
+ declare class ParseSyntaxError extends Error {
15
+ readonly errors: Array<{
16
+ message: string;
17
+ line?: number;
18
+ }>;
19
+ constructor(errors: Array<{
20
+ message: string;
21
+ line?: number;
22
+ }>, message?: string);
23
+ }
24
+ interface ParseOptions {
25
+ sport?: string;
26
+ sourceLabel?: string;
27
+ }
28
+ /**
29
+ * Parses Whiteboard Script source directly into a StatementNode IR envelope.
30
+ */
31
+ declare function runParse(source: string, options?: ParseOptions): WodWikiIRFile<StatementNode>;
32
+
33
+ /**
34
+ * Headless Execution Command Runner
35
+ *
36
+ * Executes Whiteboard Script through JIT compiler and runtime state machine,
37
+ * emitting a versioned execution-log IR envelope.
38
+ */
39
+
40
+ interface RunOptions {
41
+ sport?: string;
42
+ dialect?: 'time' | 'log';
43
+ blockId?: string;
44
+ blockVersion?: number;
45
+ startTime?: number;
46
+ maxIterations?: number;
47
+ now?: INowProvider;
48
+ sourceLabel?: string;
49
+ }
50
+ /**
51
+ * Runs a workout script to completion using headless state machine and Node clock.
52
+ */
53
+ declare function runExecution(source: string, options?: RunOptions): Promise<WodWikiIRFile<ExecutionLog>>;
54
+
55
+ /**
56
+ * Headless WQL Query Command Runner
57
+ *
58
+ * Evaluates WQL queries against in-memory stores sourced from --corpus,
59
+ * --stdin-log, or --stdin-facts, and emits a versioned IR envelope.
60
+ */
61
+
62
+ declare class WqlSyntaxError extends Error {
63
+ readonly query: string;
64
+ readonly syntaxError: string;
65
+ constructor(query: string, syntaxError: string);
66
+ }
67
+ interface QueryCliOptions {
68
+ corpusPath?: string;
69
+ stdinLog?: string;
70
+ stdinFacts?: string;
71
+ preferredUnit?: string;
72
+ sourceLabel?: string;
73
+ /** Pre-built store bundle (e.g. sqlite-backed). When set, the corpus/stdin
74
+ * dataset options are ignored. */
75
+ stores?: QueryServiceStores;
76
+ }
77
+ interface LoadedData {
78
+ eventRecords: EventRecord[];
79
+ facts: AnalyticsDataPoint[];
80
+ results: Session[];
81
+ notes: Note[];
82
+ blocks: BlockIndexRow[];
83
+ revisions: BlockRevision[];
84
+ efforts: IEffort[];
85
+ noteTags: Record<string, string[]>;
86
+ }
87
+ declare function loadQueryData(options: QueryCliOptions): LoadedData;
88
+ /**
89
+ * Runs a WQL query against the provided in-memory dataset and returns a versioned IR envelope.
90
+ */
91
+ declare function runQueryCli(wqlString: string, options?: QueryCliOptions): Promise<WodWikiIRFile<QueryResult | RowsQueryResult | FindQueryResult | PipelineResult>>;
92
+
93
+ /**
94
+ * Dynamic Language Pack Loader
95
+ *
96
+ * Dynamically loads and registers Language Packs passed via `--pack <module-spec>`.
97
+ * Throws PackLoadError on failure, enabling exit code 3 handling.
98
+ */
99
+
100
+ declare class PackLoadError extends Error {
101
+ readonly spec: string;
102
+ readonly originalError: unknown;
103
+ constructor(spec: string, originalError: unknown);
104
+ }
105
+ /**
106
+ * Dynamically imports a Language Pack module and registers it.
107
+ *
108
+ * @param moduleSpec - Relative or absolute path, or package name.
109
+ */
110
+ declare function loadLanguagePack(moduleSpec: string): Promise<LanguagePack>;
111
+
112
+ /**
113
+ * CLI Output Formatters (JSON, ASCII Table, CSV)
114
+ *
115
+ * Implements presentation formatting for parse-tree, execution-log,
116
+ * and WQL query results across json, table, and csv formats.
117
+ */
118
+
119
+ type OutputFormat = 'json' | 'table' | 'csv';
120
+ /**
121
+ * Format StatementNode parse tree
122
+ */
123
+ declare function formatParseOutput(ir: WodWikiIRFile<StatementNode>, format: OutputFormat, pretty?: boolean): string;
124
+ /**
125
+ * Format ExecutionLog runtime output
126
+ */
127
+ declare function formatExecutionOutput(ir: WodWikiIRFile<ExecutionLog>, format: OutputFormat, pretty?: boolean): string;
128
+ /**
129
+ * Format QueryResult / RowsQueryResult / FindQueryResult / PipelineResult
130
+ */
131
+ declare function formatQueryOutput(ir: WodWikiIRFile<QueryResult | RowsQueryResult | FindQueryResult | PipelineResult>, format: OutputFormat, pretty?: boolean): string;
132
+
133
+ /**
134
+ * Unified `wod` CLI Runner
135
+ *
136
+ * Implements CLI command routing, argument parsing, pipe handling,
137
+ * and exit code conventions per wayfinder #962 and issue #967.
138
+ *
139
+ * Exit Codes:
140
+ * 0 - OK / Success (including empty query results)
141
+ * 1 - Runtime error (file missing, bad arguments, execution error)
142
+ * 2 - Parse / Syntax error (invalid Whiteboard Script or malformed WQL)
143
+ * 3 - Pack load error (failed to import or register --pack)
144
+ */
145
+
146
+ interface CliParsedArgs {
147
+ subcommand?: 'parse' | 'run' | 'query' | 'download' | 'import' | 'build' | 'seed' | 'reset' | 'init' | 'server' | 'serve' | 'playground' | 'help' | 'version';
148
+ target?: string;
149
+ /** Second positional: the id (download) or file (import) after the kind. */
150
+ target2?: string;
151
+ corpus?: string;
152
+ stdinLog?: boolean;
153
+ stdinFacts?: boolean;
154
+ format: OutputFormat;
155
+ output?: string;
156
+ packs: string[];
157
+ sport?: string;
158
+ unit?: string;
159
+ help?: boolean;
160
+ version?: boolean;
161
+ /** SQLite database path (--db). Defaults to WOD_WIKI_DB or ./wodwiki.db. */
162
+ db?: string;
163
+ /** Backend driver override (--db-driver): sqlite | postgres | turso. */
164
+ dbDriver?: string;
165
+ /** PostgreSQL connection URL (--database-url) or component flags below. */
166
+ databaseUrl?: string;
167
+ postgresHost?: string;
168
+ postgresPort?: string;
169
+ postgresUser?: string;
170
+ postgresPassword?: string;
171
+ postgresDatabase?: string;
172
+ postgresSsl?: string;
173
+ /** Turso/libSQL target (--turso-url) plus its auth token. */
174
+ tursoUrl?: string;
175
+ tursoAuthToken?: string;
176
+ /** Explicit environment file (--env-file); a missing file is an error. */
177
+ envFile?: string;
178
+ /** Hosting bind address/port and prebuilt frontend directory (server/playground). */
179
+ host?: string;
180
+ port?: string;
181
+ dist?: string;
182
+ /** IR debug output: undefined = off, '' = default file name, otherwise the file name. */
183
+ debug?: string;
184
+ /** Extra seed source directories for `wod seed` (repeatable). */
185
+ sources: string[];
186
+ /** `wod build`: scaffold markdown stubs for newly discovered efforts. */
187
+ scaffold?: boolean;
188
+ /** `wod build`: extra template markdown file compiled as the `template` chunk. */
189
+ template?: string;
190
+ /** `wod build`: content repo root (alias for the positional directory). */
191
+ dir?: string;
192
+ }
193
+ declare function parseCliArgs(args: string[]): CliParsedArgs;
194
+ declare function readStdin(): Promise<string>;
195
+ /**
196
+ * Main CLI entry point. Returns numeric exit code.
197
+ */
198
+ declare function cliMain(argv?: string[], io?: {
199
+ stdout?: (text: string) => void;
200
+ stderr?: (text: string) => void;
201
+ readStdinFn?: () => Promise<string>;
202
+ }): Promise<number>;
203
+
204
+ /**
205
+ * Database configuration for the `wod` CLI (and the dev server): explicit
206
+ * per-property flags → process env → .env file (native node:util.parseEnv,
207
+ * non-mutating).
208
+ *
209
+ * Precedence: flags beat env beats .env. `--db` selects local SQLite even
210
+ * when env configures a remote backend; explicit `--db-driver` /
211
+ * `--postgres-*` / `--turso-*` flags select that backend. Conflicting
212
+ * explicit selectors and ambiguous env backends are rejected. Labels and
213
+ * error messages never contain credentials.
214
+ */
215
+ /** Optional per-property flag bag — every property unset unless given. */
216
+ interface CliDatabaseOptions {
217
+ /** --db: explicit SQLite file. Selects sqlite even when env has a remote backend. */
218
+ db?: string;
219
+ /** --db-driver: sqlite | postgres | turso. */
220
+ dbDriver?: string;
221
+ /** --database-url: driver-scoped URL (mirrors DATABASE_URL). */
222
+ databaseUrl?: string;
223
+ postgresHost?: string;
224
+ postgresPort?: string;
225
+ postgresUser?: string;
226
+ postgresPassword?: string;
227
+ postgresDatabase?: string;
228
+ /** --postgres-ssl <mode>: true/false or disable | prefer | require | verify-ca | verify-full. */
229
+ postgresSsl?: string | boolean;
230
+ tursoUrl?: string;
231
+ tursoAuthToken?: string;
232
+ /** --env-file: explicit file is mandatory — a missing file is an error. */
233
+ envFile?: string;
234
+ }
235
+ /** Discriminated backend selection. `label` is credential-free and safe to
236
+ * print on every variant. sqlite is pure-local (absolute `path`); every
237
+ * remote libsql target normalizes to turso. */
238
+ type PostgresSslMode = 'disable' | 'prefer' | 'require' | 'verify-ca' | 'verify-full';
239
+ type CliDatabaseConfig = {
240
+ kind: 'sqlite';
241
+ label: string;
242
+ path: string;
243
+ } | {
244
+ kind: 'postgres';
245
+ label: string;
246
+ url: string;
247
+ host: string;
248
+ port: number;
249
+ database: string;
250
+ user?: string;
251
+ password?: string;
252
+ ssl: PostgresSslMode;
253
+ } | {
254
+ kind: 'turso';
255
+ label: string;
256
+ url: string;
257
+ authToken?: string;
258
+ };
259
+ /**
260
+ * Load a NON-MUTATING env object from the dotenv tier ONLY: an explicit
261
+ * --env-file is mandatory when given (missing file → error); otherwise
262
+ * `.env` in the working directory, falling back to the nearest ancestor
263
+ * (workspace root) `.env`. Callers merge over process.env themselves
264
+ * ({ ...loadCliEnvironment(file), ...process.env }); process.env is never
265
+ * written.
266
+ */
267
+ declare function loadCliEnvironment(envFile?: string): Record<string, string | undefined>;
268
+ /**
269
+ * Resolve the effective database configuration: flags > env > .env. Pass
270
+ * `env` to supply an already-merged tier (nothing is re-read); without it
271
+ * the .env tier (loadCliEnvironment) sits under the true process tier.
272
+ * Kind-pinning flags (--db, --db-driver, --turso-url/--turso-auth-token,
273
+ * --postgres-*) override even a malformed env driver; conflicting pins and
274
+ * ambiguous env backends are rejected. Never echoes credentials.
275
+ */
276
+ declare function resolveDatabaseConfig(options?: CliDatabaseOptions, env?: Record<string, string | undefined>): CliDatabaseConfig;
277
+
278
+ /**
279
+ * Local SQLite seam for the `wod` CLI.
280
+ *
281
+ * Opens a libsql `file:` database, initializes the shared storage schema
282
+ * (KV store tables + domain projections), and adapts the rows into the WQL
283
+ * store interfaces — the same shape the playground builds over IndexedDB.
284
+ *
285
+ * Everything heavy is loaded lazily (`@libsql/client`,
286
+ * `@bitcobblers/wod-wiki-storage/sql`, node `fs`/`crypto`) so browser
287
+ * bundles that import the engine index never pull native drivers; the
288
+ * dynamic specifiers stay external in the CLI bundle.
289
+ */
290
+
291
+ interface CliDb {
292
+ db: Database;
293
+ storage: IStorage;
294
+ /** Absolute path of the underlying .sqlite file — or the sanitized display
295
+ * label (URL without credentials) for remote backends. */
296
+ path: string;
297
+ close(): Promise<void>;
298
+ }
299
+ /**
300
+ * Open the CLI database: a legacy local sqlite path (internal callers,
301
+ * created/initialized as before) or a resolved backend config from ./config.
302
+ *
303
+ * opts.initialize (default true): run the schema + projection
304
+ * initialization. false → verify the schema/projection guards only: a
305
+ * missing local sqlite file is NOT created and yields an actionable
306
+ * `wod init` error; remote targets are only probed, never created.
307
+ *
308
+ * opts.create (explicit init requests only, postgres): when the configured
309
+ * PostgreSQL database is missing, openDatabase creates it through the admin
310
+ * "postgres" database before initializing. Turso has no management API —
311
+ * its hosted target must already exist.
312
+ */
313
+ declare function openCliDb(target?: string | CliDatabaseConfig, opts?: {
314
+ initialize?: boolean;
315
+ create?: boolean;
316
+ }): Promise<CliDb>;
317
+
318
+ /**
319
+ * server.ts — `wod server` (alias `serve`) / `wod playground` (Bun-only front
320
+ * door).
321
+ *
322
+ * Serves the packaged engine stack: the shared fetch handler shipped in this
323
+ * package (the same code the Docker image serves) plus the frontend build
324
+ * packaged under the engine's dist/web/ — no repository checkout, no runtime
325
+ * build.
326
+ *
327
+ * server probes the configured database first (never creates schema —
328
+ * that's `wod init`), then serves API + SPA.
329
+ * playground serves the browser-storage SPA build and never opens a database
330
+ * (every /api request answers 404).
331
+ *
332
+ * Retains the event loop until SIGINT/SIGTERM; shutdown drains in-flight
333
+ * requests before closing the database.
334
+ */
335
+
336
+ type WriteOut = (text: string) => void;
337
+ type CliServerMode = 'server' | 'playground';
338
+ interface CliServerOptions {
339
+ /** Resolved database configuration. REQUIRED for 'server'; ignored — never
340
+ * opened or validated — for 'playground'. */
341
+ database?: CliDatabaseConfig;
342
+ host?: string;
343
+ port?: string;
344
+ /** Explicit dist directory (flag or PLAYGROUND_DIST); must already exist. */
345
+ dist?: string;
346
+ /** Merged CLI environment (process env + .env). */
347
+ env: Record<string, string | undefined>;
348
+ }
349
+ /**
350
+ * Start the shared server for `wod server` / `wod playground`. Throws on any
351
+ * startup failure; on success it never returns until SIGINT/SIGTERM (drain,
352
+ * close database, exit 0).
353
+ */
354
+ declare function runCliServer(mode: CliServerMode, options: CliServerOptions, writeOut: WriteOut): Promise<void>;
355
+
356
+ export { type CliDatabaseConfig, type CliDb, type CliParsedArgs, type CliServerMode, type CliServerOptions, type OutputFormat, PackLoadError, type ParseOptions, ParseSyntaxError, type QueryCliOptions, type RunOptions, WqlSyntaxError, cliMain, formatExecutionOutput, formatParseOutput, formatQueryOutput, loadCliEnvironment, loadLanguagePack, loadQueryData, openCliDb, parseCliArgs, readStdin, resolveDatabaseConfig, runCliServer, runExecution, runParse, runQueryCli };
package/dist/index.mjs ADDED
@@ -0,0 +1,12 @@
1
+ import { createRequire } from 'module';
2
+ export { PackLoadError, ParseSyntaxError, WqlSyntaxError, cliMain, formatExecutionOutput, formatParseOutput, formatQueryOutput, loadCliEnvironment, loadLanguagePack, loadQueryData, parseCliArgs, readStdin, resolveDatabaseConfig, runExecution, runParse, runQueryCli } from './chunk-B3QJYJVI.mjs';
3
+ export { runCliServer } from './chunk-32FASQP5.mjs';
4
+ export { openCliDb } from './chunk-EUQWEX6Z.mjs';
5
+ import './chunk-K3GZZFAC.mjs';
6
+ import './chunk-6L5ZDZJ7.mjs';
7
+
8
+ if (typeof globalThis.require === 'undefined') {
9
+ try { globalThis.require = createRequire(import.meta.url); } catch {}
10
+ }
11
+ //# sourceMappingURL=index.mjs.map
12
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.mjs"}
@@ -0,0 +1,299 @@
1
+ 'use strict';
2
+
3
+ var path = require('path');
4
+ var promises = require('fs/promises');
5
+ var fs = require('fs');
6
+ var sql = require('@bitcobblers/wod-wiki-storage/sql');
7
+ var wodWikiStorage = require('@bitcobblers/wod-wiki-storage');
8
+
9
+ // src/server/app.ts
10
+
11
+ // src/server/errors.ts
12
+ var HttpError = class extends Error {
13
+ constructor(status, message, code) {
14
+ super(message);
15
+ this.status = status;
16
+ this.code = code;
17
+ this.name = "HttpError";
18
+ }
19
+ status;
20
+ code;
21
+ };
22
+ var json = (value, status) => new Response(JSON.stringify(value), { status, headers: { "content-type": "application/json" } });
23
+ function bunFile(path) {
24
+ const bun = globalThis;
25
+ if (!bun.Bun) throw new Error("the wod-wiki server handler requires the Bun runtime (https://bun.sh)");
26
+ return bun.Bun.file(path);
27
+ }
28
+ var plain = (body, status) => new Response(body, { status, headers: { "content-type": "text/plain; charset=utf-8" } });
29
+ var notFound = () => json({ error: "not found" }, 404);
30
+ var methodNotAllowed = () => json({ error: "method not allowed" }, 405);
31
+ var noContent = () => new Response(null, { status: 204 });
32
+ var qget = (url, name) => url.searchParams.get(name) ?? void 0;
33
+ function jsonParam(url, name) {
34
+ const raw = qget(url, name);
35
+ if (raw === void 0) return void 0;
36
+ try {
37
+ return JSON.parse(raw);
38
+ } catch {
39
+ throw new HttpError(400, `invalid JSON in ?${name}`);
40
+ }
41
+ }
42
+ function rangeParam(url) {
43
+ const v = jsonParam(url, "range");
44
+ if (v === void 0) return void 0;
45
+ if (v !== null && typeof v === "object" && !Array.isArray(v)) return v;
46
+ return { only: v };
47
+ }
48
+ function countParam(url) {
49
+ const raw = qget(url, "count");
50
+ if (raw === void 0) return void 0;
51
+ const n = Number(raw);
52
+ if (!Number.isFinite(n) || n < 0) throw new HttpError(400, "invalid ?count");
53
+ return Math.trunc(n);
54
+ }
55
+ async function readJsonBody(req, maxBytes) {
56
+ const body = await req.text();
57
+ if (maxBytes !== void 0 && body.length > maxBytes) {
58
+ throw new HttpError(413, `domain query body exceeds ${maxBytes} bytes`);
59
+ }
60
+ try {
61
+ return JSON.parse(body);
62
+ } catch {
63
+ throw new HttpError(400, "body must be valid JSON");
64
+ }
65
+ }
66
+ function txOps(value) {
67
+ if (!Array.isArray(value)) throw new HttpError(400, "tx body must be {ops: []}");
68
+ return value.map((op) => {
69
+ if (op === null || typeof op !== "object" || Array.isArray(op) || !("store" in op) || typeof op.store !== "string" || !("op" in op)) {
70
+ throw new HttpError(400, "tx op must have a store and operation");
71
+ }
72
+ const def = sql.storeDefinition(op.store);
73
+ const store = def.name;
74
+ switch (op.op) {
75
+ case "put":
76
+ if (!("value" in op)) throw new HttpError(400, "put requires value");
77
+ return { op: "put", store, value: op.value };
78
+ case "delete":
79
+ if (!("key" in op)) throw new HttpError(400, "delete requires key");
80
+ return { op: "delete", store, key: op.key };
81
+ case "clear":
82
+ return { op: "clear", store };
83
+ case "check": {
84
+ if (!("key" in op) || !("value" in op)) throw new HttpError(400, "check requires key and value");
85
+ const scalar = (key) => typeof key === "string" || typeof key === "number" && Number.isFinite(key);
86
+ if (!scalar(op.key) && !(Array.isArray(op.key) && op.key.length > 0 && op.key.every(scalar))) {
87
+ throw new HttpError(400, "check requires a scalar key or non-empty scalar tuple");
88
+ }
89
+ if (op.value !== null && (typeof op.value !== "object" || Array.isArray(op.value))) {
90
+ throw new HttpError(400, "check value must be a row or null");
91
+ }
92
+ const parts = Array.isArray(op.key) ? op.key : [op.key];
93
+ if (parts.length !== def.keyPath.length) throw new HttpError(400, "check key arity must match the store");
94
+ if (op.value !== null) {
95
+ let rowKey;
96
+ try {
97
+ rowKey = wodWikiStorage.keyFromValue(def, op.value);
98
+ } catch {
99
+ throw new HttpError(400, "check row must have its primary key");
100
+ }
101
+ if (JSON.stringify(rowKey) !== JSON.stringify(op.key)) throw new HttpError(400, "check row primary key must match key");
102
+ }
103
+ return { op: "check", store, key: op.key, value: op.value };
104
+ }
105
+ default:
106
+ throw new HttpError(400, "tx op must be put | delete | clear | check");
107
+ }
108
+ });
109
+ }
110
+ var MIME = {
111
+ html: "text/html; charset=utf-8",
112
+ js: "text/javascript; charset=utf-8",
113
+ mjs: "text/javascript; charset=utf-8",
114
+ css: "text/css; charset=utf-8",
115
+ json: "application/json; charset=utf-8",
116
+ map: "application/json; charset=utf-8",
117
+ svg: "image/svg+xml",
118
+ png: "image/png",
119
+ jpg: "image/jpeg",
120
+ jpeg: "image/jpeg",
121
+ webp: "image/webp",
122
+ ico: "image/x-icon",
123
+ woff: "font/woff",
124
+ woff2: "font/woff2",
125
+ ttf: "font/ttf",
126
+ mp3: "audio/mpeg",
127
+ wav: "audio/wav",
128
+ webmanifest: "application/manifest+json",
129
+ xml: "application/xml",
130
+ txt: "text/plain; charset=utf-8"
131
+ };
132
+ async function realpathSafe(path) {
133
+ try {
134
+ return await promises.realpath(path);
135
+ } catch {
136
+ return void 0;
137
+ }
138
+ }
139
+ async function serveFile(real, rel) {
140
+ const cache = rel.includes("seed/chunks/") ? "public, max-age=31536000, immutable" : "no-cache";
141
+ const ext = rel.slice(rel.lastIndexOf(".") + 1).toLowerCase();
142
+ return new Response(bunFile(real), {
143
+ headers: { "content-type": MIME[ext] ?? "application/octet-stream", "cache-control": cache }
144
+ });
145
+ }
146
+ async function serveSpaShell(distRoot) {
147
+ const index = await realpathSafe(path.join(distRoot, "index.html"));
148
+ if (index && (index === distRoot || index.startsWith(distRoot + path.sep)) && await promises.stat(index).then((s) => s.isFile(), () => false)) {
149
+ return new Response(bunFile(index), {
150
+ headers: { "content-type": MIME.html, "cache-control": "no-cache" }
151
+ });
152
+ }
153
+ return void 0;
154
+ }
155
+ async function staticResponse(distRoot, method, pathname) {
156
+ if (method !== "GET") return plain("method not allowed", 405);
157
+ if (!distRoot) {
158
+ return plain("wod-wiki-api: playground dist not found. Set PLAYGROUND_DIST or run the playground server build.", 503);
159
+ }
160
+ let rel;
161
+ try {
162
+ rel = decodeURIComponent(pathname).replace(/^\/+/, "");
163
+ } catch {
164
+ return plain("bad request", 400);
165
+ }
166
+ if (rel === "") rel = "index.html";
167
+ const parts = rel.split("/");
168
+ if (parts.some((p) => p === "..")) return plain("forbidden", 403);
169
+ const base = parts[parts.length - 1] ?? "";
170
+ const hasExtension = base.includes(".");
171
+ const abs = path.resolve(distRoot, ...parts);
172
+ const real = await realpathSafe(abs);
173
+ if (real && (real === distRoot || real.startsWith(distRoot + path.sep))) {
174
+ const info = await promises.stat(real).then((s) => s.isFile(), () => false);
175
+ if (info) return serveFile(real, rel);
176
+ }
177
+ if (!hasExtension) {
178
+ const shell = await serveSpaShell(distRoot);
179
+ if (shell) return shell;
180
+ }
181
+ return plain("not found", 404);
182
+ }
183
+ async function storeRoutes(db, method, sub, url, req) {
184
+ let inner = sub.slice("/v1/".length);
185
+ if (inner.endsWith("/")) inner = inner.slice(0, -1);
186
+ if (inner === "") return notFound();
187
+ const segments = inner.split("/");
188
+ let name;
189
+ let action;
190
+ if (segments.length === 1) {
191
+ name = segments[0];
192
+ } else if (segments.length === 2) {
193
+ [name, action] = segments;
194
+ } else if (segments.length === 3 && segments[1] === "index" && segments[2] !== "") {
195
+ [name, , action] = segments;
196
+ action = `index/${action}`;
197
+ } else {
198
+ return notFound();
199
+ }
200
+ const def = sql.storeDefinition(name);
201
+ const range = rangeParam(url);
202
+ const count = countParam(url);
203
+ if (action === "get" && method === "GET") {
204
+ const key = jsonParam(url, "key");
205
+ if (key === void 0) throw new HttpError(400, "missing ?key");
206
+ const row = await sql.getRow(db, def, key);
207
+ return row === void 0 ? notFound() : json(row, 200);
208
+ }
209
+ if (action === "all" && method === "GET") {
210
+ return json(await sql.listRows(db, def, range, count), 200);
211
+ }
212
+ if (action === "count" && method === "GET") {
213
+ const indexName = qget(url, "index");
214
+ const n = indexName ? await sql.countRows(db, def, range, indexName) : await sql.countRows(db, def, range);
215
+ return json({ count: Number(n) }, 200);
216
+ }
217
+ if (action === "clear" && method === "POST") {
218
+ await db.transaction((tx) => sql.refreshDomain(tx, [{ op: "clear", store: def.name }]));
219
+ return noContent();
220
+ }
221
+ if (action?.startsWith("index/") && method === "GET") {
222
+ return json(await sql.listRows(db, def, range, count, action.slice("index/".length)), 200);
223
+ }
224
+ if (action === void 0 && method === "PUT") {
225
+ const parsed = await readJsonBody(req);
226
+ const value = typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && "value" in parsed ? parsed.value : void 0;
227
+ if (value === void 0) throw new HttpError(400, "body must be {value: ...}");
228
+ const [key] = await db.transaction((tx) => sql.refreshDomain(tx, [{ op: "put", store: def.name, value }]));
229
+ return json({ key }, 200);
230
+ }
231
+ if (action === void 0 && method === "DELETE") {
232
+ const keyParam = jsonParam(url, "key");
233
+ const target = keyParam !== void 0 ? keyParam : range;
234
+ if (target === void 0 || target === null) throw new HttpError(400, "DELETE requires ?key or ?range");
235
+ await db.transaction((tx) => sql.refreshDomain(tx, [{ op: "delete", store: def.name, key: target }]));
236
+ return noContent();
237
+ }
238
+ return methodNotAllowed();
239
+ }
240
+ async function apiRoutes(db, req, method, sub, url) {
241
+ if (sub === "/health") {
242
+ return json({ ok: true, driver: db.dialect }, 200);
243
+ }
244
+ if (sub === "/v1/wipe" && method === "POST") {
245
+ await db.transaction(async (tx) => {
246
+ await sql.wipeRows(tx);
247
+ await sql.wipeDomain(tx);
248
+ });
249
+ return noContent();
250
+ }
251
+ if (sub === "/v1/tx" && method === "POST") {
252
+ const parsed = await readJsonBody(req);
253
+ const ops = typeof parsed === "object" && parsed !== null && "ops" in parsed ? parsed.ops : void 0;
254
+ const parsedOps = txOps(ops);
255
+ const results = await db.transaction((tx) => sql.refreshDomain(tx, parsedOps));
256
+ return json({ results }, 200);
257
+ }
258
+ if (sub === "/v1/query") {
259
+ if (method !== "POST") return methodNotAllowed();
260
+ const body = await readJsonBody(req, sql.MAX_BODY);
261
+ return json(await sql.handleDomainQuery(db, body), 200);
262
+ }
263
+ if (sub.startsWith("/v1/")) return storeRoutes(db, method, sub, url, req);
264
+ return notFound();
265
+ }
266
+ function distRootOf(playgroundDist) {
267
+ if (!playgroundDist) return void 0;
268
+ try {
269
+ return fs.realpathSync(playgroundDist);
270
+ } catch {
271
+ return void 0;
272
+ }
273
+ }
274
+ function createHandler(db, options) {
275
+ const distRoot = distRootOf(options.playgroundDist);
276
+ return async (request) => {
277
+ try {
278
+ const url = new URL(request.url);
279
+ const method = request.method === "HEAD" ? "GET" : request.method;
280
+ const path = url.pathname;
281
+ const response = path === "/api" || path.startsWith("/api/") ? db ? await apiRoutes(db, request, method, path.slice("/api".length) || "/", url) : notFound() : await staticResponse(distRoot, method, path);
282
+ return response;
283
+ } catch (e) {
284
+ if (e instanceof HttpError || e instanceof sql.StorageError) {
285
+ const status = e.status;
286
+ if (status >= 500) console.error("[wod-wiki-api]", e.message);
287
+ const body = { error: status >= 500 ? "internal error" : e.message };
288
+ if (e.code) body.code = e.code;
289
+ return json(body, status);
290
+ }
291
+ console.error("[wod-wiki-api]", e);
292
+ return json({ error: "internal error" }, 500);
293
+ }
294
+ };
295
+ }
296
+
297
+ exports.createHandler = createHandler;
298
+ //# sourceMappingURL=app.cjs.map
299
+ //# sourceMappingURL=app.cjs.map