@bitcobblers/wod-wiki-engine 0.35.2129 → 0.36.2130

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 +758 -194
  2. package/dist/bin/wod.cjs.map +1 -1
  3. package/dist/bin/wod.d.cts +1 -1
  4. package/dist/bin/wod.d.ts +1 -1
  5. package/dist/bin/wod.mjs +5 -3
  6. package/dist/bin/wod.mjs.map +1 -1
  7. package/dist/chunk-6L5ZDZJ7.mjs +15 -0
  8. package/dist/chunk-6L5ZDZJ7.mjs.map +1 -0
  9. package/dist/{chunk-Y6WO6MXS.mjs → chunk-BMHE7DDG.mjs} +3 -9
  10. package/dist/chunk-BMHE7DDG.mjs.map +1 -0
  11. package/dist/chunk-EUQWEX6Z.mjs +253 -0
  12. package/dist/chunk-EUQWEX6Z.mjs.map +1 -0
  13. package/dist/{chunk-N4KFWY6Z.mjs → chunk-UKBQ36RW.mjs} +377 -193
  14. package/dist/chunk-UKBQ36RW.mjs.map +1 -0
  15. package/dist/{compiler-SXUCF7AU.mjs → compiler-NODHKWJJ.mjs} +4 -3
  16. package/dist/compiler-NODHKWJJ.mjs.map +1 -0
  17. package/dist/index.cjs +763 -199
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.cts +27 -8
  20. package/dist/index.d.ts +27 -8
  21. package/dist/index.mjs +10 -8
  22. package/dist/index.mjs.map +1 -1
  23. package/dist/server/app.cjs +299 -0
  24. package/dist/server/app.cjs.map +1 -0
  25. package/dist/server/app.d.cts +24 -0
  26. package/dist/server/app.d.ts +24 -0
  27. package/dist/server/app.mjs +301 -0
  28. package/dist/server/app.mjs.map +1 -0
  29. package/dist/server-4P6AJAGT.mjs +128 -0
  30. package/dist/server-4P6AJAGT.mjs.map +1 -0
  31. package/dist/web/playground/ai-skills/parse.md +656 -0
  32. package/dist/web/playground/assets/main-Bhn9NHgQ.css +2 -0
  33. package/dist/web/playground/assets/main-CGSJY1vM.js +212 -0
  34. package/dist/web/playground/assets/rolldown-runtime-C0FnF6B9.js +1 -0
  35. package/dist/web/playground/assets/statementBuilderFlow-PTgOOzz5.js +2 -0
  36. package/dist/web/playground/assets/vendor-BlJqgFME.js +201 -0
  37. package/dist/web/playground/assets/vendor-codemirror-BtJgoBjf.js +40 -0
  38. package/dist/web/playground/fonts/Inter-Regular.woff2 +0 -0
  39. package/dist/web/playground/fonts/Inter-SemiBold.woff2 +0 -0
  40. package/dist/web/playground/images/hero/editing.png +0 -0
  41. package/dist/web/playground/images/hero/journal.png +0 -0
  42. package/dist/web/playground/images/hero/results.png +0 -0
  43. package/dist/web/playground/images/hero/timer.png +0 -0
  44. package/dist/web/playground/images/wod-wiki-logo-dark.png +0 -0
  45. package/dist/web/playground/images/wod-wiki-logo-light.png +0 -0
  46. package/dist/web/playground/index.html +60 -0
  47. package/dist/web/playground/seed/core/chunks/canvas.e6c77ad0.json +1 -0
  48. package/dist/web/playground/seed/core/chunks/template.e5e0e82d.json +1 -0
  49. package/dist/web/playground/seed/core/manifest.json +24 -0
  50. package/dist/web/playground/seed/core/report.html +43 -0
  51. package/dist/web/server/ai-skills/parse.md +656 -0
  52. package/dist/web/server/assets/main-Bhn9NHgQ.css +2 -0
  53. package/dist/web/server/assets/main-DhN2J5Ax.js +211 -0
  54. package/dist/web/server/assets/rolldown-runtime-C0FnF6B9.js +1 -0
  55. package/dist/web/server/assets/statementBuilderFlow-Dj0o2Wpk.js +2 -0
  56. package/dist/web/server/assets/vendor-BlJqgFME.js +201 -0
  57. package/dist/web/server/assets/vendor-codemirror-BtJgoBjf.js +40 -0
  58. package/dist/web/server/fonts/Inter-Regular.woff2 +0 -0
  59. package/dist/web/server/fonts/Inter-SemiBold.woff2 +0 -0
  60. package/dist/web/server/images/hero/editing.png +0 -0
  61. package/dist/web/server/images/hero/journal.png +0 -0
  62. package/dist/web/server/images/hero/results.png +0 -0
  63. package/dist/web/server/images/hero/timer.png +0 -0
  64. package/dist/web/server/images/wod-wiki-logo-dark.png +0 -0
  65. package/dist/web/server/images/wod-wiki-logo-light.png +0 -0
  66. package/dist/web/server/index.html +60 -0
  67. package/dist/web/server/seed/core/chunks/canvas.e6c77ad0.json +1 -0
  68. package/dist/web/server/seed/core/chunks/template.e5e0e82d.json +1 -0
  69. package/dist/web/server/seed/core/manifest.json +24 -0
  70. package/dist/web/server/seed/core/report.html +43 -0
  71. package/package.json +11 -5
  72. package/dist/chunk-N4KFWY6Z.mjs.map +0 -1
  73. package/dist/chunk-Y6WO6MXS.mjs.map +0 -1
  74. package/dist/compiler-SXUCF7AU.mjs.map +0 -1
package/dist/index.d.cts CHANGED
@@ -257,7 +257,7 @@ declare function formatQueryOutput(ir: WodWikiIRFile<QueryResult | RowsQueryResu
257
257
  */
258
258
 
259
259
  interface CliParsedArgs {
260
- subcommand?: 'parse' | 'run' | 'query' | 'download' | 'import' | 'build' | 'seed' | 'reset' | 'help' | 'version';
260
+ subcommand?: 'parse' | 'run' | 'query' | 'download' | 'import' | 'build' | 'seed' | 'reset' | 'init' | 'server' | 'serve' | 'playground' | 'help' | 'version';
261
261
  target?: string;
262
262
  /** Second positional: the id (download) or file (import) after the kind. */
263
263
  target2?: string;
@@ -273,6 +273,25 @@ interface CliParsedArgs {
273
273
  version?: boolean;
274
274
  /** SQLite database path (--db). Defaults to WOD_WIKI_DB or ./wodwiki.db. */
275
275
  db?: string;
276
+ /** Backend driver override (--db-driver): sqlite | postgres | turso. */
277
+ dbDriver?: string;
278
+ /** PostgreSQL connection URL (--database-url) or component flags below. */
279
+ databaseUrl?: string;
280
+ postgresHost?: string;
281
+ postgresPort?: string;
282
+ postgresUser?: string;
283
+ postgresPassword?: string;
284
+ postgresDatabase?: string;
285
+ postgresSsl?: string;
286
+ /** Turso/libSQL target (--turso-url) plus its auth token. */
287
+ tursoUrl?: string;
288
+ tursoAuthToken?: string;
289
+ /** Explicit environment file (--env-file); a missing file is an error. */
290
+ envFile?: string;
291
+ /** Hosting bind address/port and prebuilt frontend directory (server/playground). */
292
+ host?: string;
293
+ port?: string;
294
+ dist?: string;
276
295
  /** IR debug output: undefined = off, '' = default file name, otherwise the file name. */
277
296
  debug?: string;
278
297
  /** Extra seed source directories for `wod seed` (repeatable). */
@@ -372,7 +391,7 @@ declare function effortToMarkdown(effort: {
372
391
  /** One-label-per-row CSV (header `label`) for equipment catalogs. */
373
392
  declare function equipmentToCsv(labels: readonly string[]): string;
374
393
  declare function csvToEquipmentLabels(csv: string): string[];
375
- declare const SESSION_CSV_HEADER: ("duration" | "dialect" | "origin" | "completed" | "metricType" | "sourceBlockKey" | "timestamp" | "blockContentId" | "noteId" | "blockId" | "blockVersion" | "status" | "startTime" | "endTime" | "effortSlug" | "sessionId" | "sessionCreatedAt" | "sessionExtraJson" | "eventId" | "grain" | "outputType" | "pageId" | "eventBlockId" | "eventBlockVersion" | "eventBlockContentId" | "eventOrigin" | "timeSpan" | "sourceStatementId" | "line" | "text" | "stackLevel" | "completionReason" | "representationKind" | "metricValue" | "metricUnit" | "metricImage" | "metricOrigin" | "metricMetadata" | "metricTemporal" | "metricJson" | "eventExtraJson")[];
394
+ declare const SESSION_CSV_HEADER: ("status" | "duration" | "dialect" | "origin" | "completed" | "metricType" | "sourceBlockKey" | "timestamp" | "blockContentId" | "noteId" | "blockId" | "blockVersion" | "startTime" | "endTime" | "effortSlug" | "sessionId" | "sessionCreatedAt" | "sessionExtraJson" | "eventId" | "grain" | "outputType" | "pageId" | "eventBlockId" | "eventBlockVersion" | "eventBlockContentId" | "eventOrigin" | "timeSpan" | "sourceStatementId" | "line" | "text" | "stackLevel" | "completionReason" | "representationKind" | "metricValue" | "metricUnit" | "metricImage" | "metricOrigin" | "metricMetadata" | "metricTemporal" | "metricJson" | "eventExtraJson")[];
376
395
  /**
377
396
  * Serialize a session + its events to CSV — one row per metric, preceded by
378
397
  * one session anchor row (empty event columns). Import with
@@ -398,13 +417,13 @@ declare function csvToSessionRows(csv: string): SessionImportRows;
398
417
  * Auto-generated build version metadata.
399
418
  * Do not edit directly; updated by scripts/stamp-version.ts
400
419
  */
401
- declare const VERSION = "0.35.2129";
402
- declare const GIT_SHA = "a49fd2c";
403
- declare const BUILD_TIME = "2026-10-14T20:59:51.571Z";
420
+ declare const VERSION = "0.36.2130";
421
+ declare const GIT_SHA = "aabe90c";
422
+ declare const BUILD_TIME = "2026-10-14T23:19:24.665Z";
404
423
  declare const SEMVER: {
405
- readonly version: "0.35.2129";
406
- readonly gitSha: "a49fd2c";
407
- readonly buildTime: "2026-10-14T20:59:51.571Z";
424
+ readonly version: "0.36.2130";
425
+ readonly gitSha: "aabe90c";
426
+ readonly buildTime: "2026-10-14T23:19:24.665Z";
408
427
  };
409
428
 
410
429
  declare const SEED_SCHEMA = 8;
package/dist/index.d.ts CHANGED
@@ -257,7 +257,7 @@ declare function formatQueryOutput(ir: WodWikiIRFile<QueryResult | RowsQueryResu
257
257
  */
258
258
 
259
259
  interface CliParsedArgs {
260
- subcommand?: 'parse' | 'run' | 'query' | 'download' | 'import' | 'build' | 'seed' | 'reset' | 'help' | 'version';
260
+ subcommand?: 'parse' | 'run' | 'query' | 'download' | 'import' | 'build' | 'seed' | 'reset' | 'init' | 'server' | 'serve' | 'playground' | 'help' | 'version';
261
261
  target?: string;
262
262
  /** Second positional: the id (download) or file (import) after the kind. */
263
263
  target2?: string;
@@ -273,6 +273,25 @@ interface CliParsedArgs {
273
273
  version?: boolean;
274
274
  /** SQLite database path (--db). Defaults to WOD_WIKI_DB or ./wodwiki.db. */
275
275
  db?: string;
276
+ /** Backend driver override (--db-driver): sqlite | postgres | turso. */
277
+ dbDriver?: string;
278
+ /** PostgreSQL connection URL (--database-url) or component flags below. */
279
+ databaseUrl?: string;
280
+ postgresHost?: string;
281
+ postgresPort?: string;
282
+ postgresUser?: string;
283
+ postgresPassword?: string;
284
+ postgresDatabase?: string;
285
+ postgresSsl?: string;
286
+ /** Turso/libSQL target (--turso-url) plus its auth token. */
287
+ tursoUrl?: string;
288
+ tursoAuthToken?: string;
289
+ /** Explicit environment file (--env-file); a missing file is an error. */
290
+ envFile?: string;
291
+ /** Hosting bind address/port and prebuilt frontend directory (server/playground). */
292
+ host?: string;
293
+ port?: string;
294
+ dist?: string;
276
295
  /** IR debug output: undefined = off, '' = default file name, otherwise the file name. */
277
296
  debug?: string;
278
297
  /** Extra seed source directories for `wod seed` (repeatable). */
@@ -372,7 +391,7 @@ declare function effortToMarkdown(effort: {
372
391
  /** One-label-per-row CSV (header `label`) for equipment catalogs. */
373
392
  declare function equipmentToCsv(labels: readonly string[]): string;
374
393
  declare function csvToEquipmentLabels(csv: string): string[];
375
- declare const SESSION_CSV_HEADER: ("duration" | "dialect" | "origin" | "completed" | "metricType" | "sourceBlockKey" | "timestamp" | "blockContentId" | "noteId" | "blockId" | "blockVersion" | "status" | "startTime" | "endTime" | "effortSlug" | "sessionId" | "sessionCreatedAt" | "sessionExtraJson" | "eventId" | "grain" | "outputType" | "pageId" | "eventBlockId" | "eventBlockVersion" | "eventBlockContentId" | "eventOrigin" | "timeSpan" | "sourceStatementId" | "line" | "text" | "stackLevel" | "completionReason" | "representationKind" | "metricValue" | "metricUnit" | "metricImage" | "metricOrigin" | "metricMetadata" | "metricTemporal" | "metricJson" | "eventExtraJson")[];
394
+ declare const SESSION_CSV_HEADER: ("status" | "duration" | "dialect" | "origin" | "completed" | "metricType" | "sourceBlockKey" | "timestamp" | "blockContentId" | "noteId" | "blockId" | "blockVersion" | "startTime" | "endTime" | "effortSlug" | "sessionId" | "sessionCreatedAt" | "sessionExtraJson" | "eventId" | "grain" | "outputType" | "pageId" | "eventBlockId" | "eventBlockVersion" | "eventBlockContentId" | "eventOrigin" | "timeSpan" | "sourceStatementId" | "line" | "text" | "stackLevel" | "completionReason" | "representationKind" | "metricValue" | "metricUnit" | "metricImage" | "metricOrigin" | "metricMetadata" | "metricTemporal" | "metricJson" | "eventExtraJson")[];
376
395
  /**
377
396
  * Serialize a session + its events to CSV — one row per metric, preceded by
378
397
  * one session anchor row (empty event columns). Import with
@@ -398,13 +417,13 @@ declare function csvToSessionRows(csv: string): SessionImportRows;
398
417
  * Auto-generated build version metadata.
399
418
  * Do not edit directly; updated by scripts/stamp-version.ts
400
419
  */
401
- declare const VERSION = "0.35.2129";
402
- declare const GIT_SHA = "a49fd2c";
403
- declare const BUILD_TIME = "2026-10-14T20:59:51.571Z";
420
+ declare const VERSION = "0.36.2130";
421
+ declare const GIT_SHA = "aabe90c";
422
+ declare const BUILD_TIME = "2026-10-14T23:19:24.665Z";
404
423
  declare const SEMVER: {
405
- readonly version: "0.35.2129";
406
- readonly gitSha: "a49fd2c";
407
- readonly buildTime: "2026-10-14T20:59:51.571Z";
424
+ readonly version: "0.36.2130";
425
+ readonly gitSha: "aabe90c";
426
+ readonly buildTime: "2026-10-14T23:19:24.665Z";
408
427
  };
409
428
 
410
429
  declare const SEED_SCHEMA = 8;
package/dist/index.mjs CHANGED
@@ -1,6 +1,8 @@
1
1
  import { createRequire } from 'module';
2
- export { NOTE_ID_FRONTMATTER_KEY, PackLoadError, ParseSyntaxError, SESSION_CSV_HEADER, WqlSyntaxError, applySeedChunk, assertSeedManifest, assertSeedRows, buildStatementTree, buildWorkoutResults, cliMain, composeNoteMarkdown, createIRFile, csvToEquipmentLabels, csvToSessionRows, defineLanguagePack, effortToMarkdown, equipmentToCsv, extractNoteIdentity, extractTypedTags, formatExecutionOutput, formatParseOutput, formatQueryOutput, getRegisteredLanguagePacks, inMemoryEventStore, injectIdentityFrontmatter, isIRFile, listLanguagePacks, loadLanguagePack, loadQueryData, markdownToEffort, parseCliArgs, parseCsvLine, readFrontmatterMeta, readStdin, registerLanguagePack, runExecution, runParse, runQueryCli, seedNoteId, sessionToCsv, splitCsvRecords, statementToNode, unregisterLanguagePack } from './chunk-N4KFWY6Z.mjs';
3
- export { CANVAS_CHUNK_ID, DASHBOARDS_CHUNK_ID, EFFORTS_CHUNK_ID, SEED_BROADCAST_CHANNEL, SEED_REGISTERED_WIDGETS, SEED_SCHEMA, TEMPLATE_CHUNK_ID, canvasRouteSlug, catalogForChunkId, emptySeedMeta, seedMetaKey, seedNoteSourceId } from './chunk-Y6WO6MXS.mjs';
2
+ export { NOTE_ID_FRONTMATTER_KEY, PackLoadError, ParseSyntaxError, SESSION_CSV_HEADER, WqlSyntaxError, applySeedChunk, assertSeedManifest, assertSeedRows, buildStatementTree, buildWorkoutResults, cliMain, composeNoteMarkdown, createIRFile, csvToEquipmentLabels, csvToSessionRows, defineLanguagePack, effortToMarkdown, equipmentToCsv, extractNoteIdentity, extractTypedTags, formatExecutionOutput, formatParseOutput, formatQueryOutput, getRegisteredLanguagePacks, inMemoryEventStore, injectIdentityFrontmatter, isIRFile, listLanguagePacks, loadLanguagePack, loadQueryData, markdownToEffort, parseCliArgs, parseCsvLine, readFrontmatterMeta, readStdin, registerLanguagePack, runExecution, runParse, runQueryCli, seedNoteId, sessionToCsv, splitCsvRecords, statementToNode, unregisterLanguagePack } from './chunk-UKBQ36RW.mjs';
3
+ export { CANVAS_CHUNK_ID, DASHBOARDS_CHUNK_ID, EFFORTS_CHUNK_ID, SEED_BROADCAST_CHANNEL, SEED_REGISTERED_WIDGETS, SEED_SCHEMA, TEMPLATE_CHUNK_ID, canvasRouteSlug, catalogForChunkId, emptySeedMeta, seedMetaKey, seedNoteSourceId } from './chunk-BMHE7DDG.mjs';
4
+ import './chunk-EUQWEX6Z.mjs';
5
+ import './chunk-6L5ZDZJ7.mjs';
4
6
  export * from '@bitcobblers/wod-wiki-core';
5
7
  export { ABSENT, AGGREGATE_BUILTINS, AUTHORITATIVE_CASTS, AmrapLogicStrategy, AnalyticsEngine, AnalyticsTransformer, BUILTIN_CALCS, BUNDLED_EFFORT_COUNT, BlockBuilder, ButtonBehavior, CONSUMED_HINTS, CONSUMED_HINT_KEYS, CONTEXT_ATOMS, CalcEngine, CalculationRegistry, CardioDialect, ChildSelectionBehavior, ChildrenStrategy, ChoiceGroupMetric, ClimbDialect, ClimbMetricType, CountdownTimerBehavior, CountupTimerBehavior, CrossFitDialect, DEFAULT_RESOLVER_OPTIONS, DEFAULT_RUNTIME_OPTIONS, DEFAULT_UNRESOLVED_EFFORT_MET, DIM_LENGTH, DIM_MASS, DIM_TIME, DIM_ZERO, DialectStack, EFFORT_DATA_METRIC_TYPE, EFFORT_REGISTRY_ORIGINS, EffortFallbackStrategy, EffortResolver, ErrorAction, EventBus, ExecutionContext, ExitBehavior, FixtureSyntaxError, GenericGroupStrategy, GenericLoopStrategy, GenericTimerStrategy, HabitsDialect, INTENSITY_TIERS, InMemoryEffortRegistry, IntervalLogicStrategy, JitCompiler, LabelingBehavior, LocalRuntimeSubscription, LookupRegistry, MdTimerRuntime, MemoryLocation, MemoryTypeEnum, NextEvent, OutputEmitter, PRODUCTION_STRATEGIES, PopBlockAction, PushBlockAction, RegisterEventHandlerAction, Registry, ReportOutputBehavior, ReportOutputStrategy, RoundsMetric, RuntimeAdapter, RuntimeClock, RuntimeFactory, RuntimeLogger, RuntimeMemory, RuntimeObservers, RuntimeStack, STORE_CALCS, STREAM_ATOMS, ScriptRuntime, SessionRootStrategy, SnapshotClock, SoundCueBehavior, SoundStrategy, StandardAnalyticsProfile, StartSessionAction, StartWorkoutAction, SubscriptionManager, TimeSpan, TypedMemoryReference, UNITS, UnitsDialect, UnregisterEventHandlerAction, VISIBILITY_ICONS, VISIBILITY_LABELS, WhiteboardScript, WodDialect, YogaDialect, buildTooltip, bundledEfforts, calculateElapsed, canonicalizeType, collapseUnresolvedChoices, compareStatement, compileLineForm, compoundName, computeColumnLabel, computeLabel, createAnalyticsEngineForBlock, createCalcEngine, createCompiler, createDialectStack, createMetricPresentationPolicy, createMockClock, createParser, dialectRegistry, dialectStack, diffStatement, evaluate, evaluateCalculateDefinitions, extractMetrics, extractStatements, findBestFuzzyMatch, findUnresolvedChoices, frozenNow, getAnalyticsFromLogs, getHints, getMetricVisibility, getRuntimeNowMs, hasHint, hintMetric, hintsToContainer, isCastSubscription, isChoiceResolved, isTimeLikeMetric, isWithinThreshold, levenshteinDistance, metricPresentation, normalizeForFuzzy, outputNodeId, outputNodeIds, parseCalcLine, parseCalculateBlock, parseExpression, parseMetricLine, parseScript, realtimeProcessorRegistry, renderMetric, strategyRegistry, summaryProcessorRegistry, toStoredOutputStatement, truthy, wallClockNow, whiteboardParser, whiteboardScript, whiteboardScriptLanguage, writeChoiceSelection } from '@bitcobblers/wod-wiki-lang';
6
8
  export { BlockTimerDisplay, ScriptRuntimeProvider, useActiveControls, useBlockMemory, useLiveAnalytics, useMemorySubscription, useNextPreview, useOutputStatements, usePrimaryTimer, useRoundDisplay, useRuntimeExecution, useScriptRuntime, useSecondaryTimers, useSnapshotBlocks, useStackDisplayRows, useStackFragmentSources, useStackSnapshot, useStackTimers, useTimerDisplay, useTimerElapsed } from '@bitcobblers/wod-wiki-lang/react';
@@ -11,13 +13,13 @@ if (typeof globalThis.require === 'undefined') {
11
13
  }
12
14
 
13
15
  // src/version.ts
14
- var VERSION = "0.35.2129";
15
- var GIT_SHA = "a49fd2c";
16
- var BUILD_TIME = "2026-10-14T20:59:51.571Z";
16
+ var VERSION = "0.36.2130";
17
+ var GIT_SHA = "aabe90c";
18
+ var BUILD_TIME = "2026-10-14T23:19:24.665Z";
17
19
  var SEMVER = {
18
- version: "0.35.2129",
19
- gitSha: "a49fd2c",
20
- buildTime: "2026-10-14T20:59:51.571Z"
20
+ version: "0.36.2130",
21
+ gitSha: "aabe90c",
22
+ buildTime: "2026-10-14T23:19:24.665Z"
21
23
  };
22
24
 
23
25
  export { BUILD_TIME, GIT_SHA, SEMVER, VERSION };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAIO,IAAM,OAAA,GAAU;AAChB,IAAM,OAAA,GAAU;AAChB,IAAM,UAAA,GAAa;AACnB,IAAM,MAAA,GAAS;AAAA,EACpB,OAAA,EAAS,WAAA;AAAA,EACT,MAAA,EAAQ,SAAA;AAAA,EACR,SAAA,EAAW;AACb","file":"index.mjs","sourcesContent":["/**\n * Auto-generated build version metadata.\n * Do not edit directly; updated by scripts/stamp-version.ts\n */\nexport const VERSION = '0.35.2129';\nexport const GIT_SHA = 'a49fd2c';\nexport const BUILD_TIME = '2026-10-14T20:59:51.571Z';\nexport const SEMVER = {\n version: '0.35.2129',\n gitSha: 'a49fd2c',\n buildTime: '2026-10-14T20:59:51.571Z',\n} as const;\n"]}
1
+ {"version":3,"sources":["../src/version.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAIO,IAAM,OAAA,GAAU;AAChB,IAAM,OAAA,GAAU;AAChB,IAAM,UAAA,GAAa;AACnB,IAAM,MAAA,GAAS;AAAA,EACpB,OAAA,EAAS,WAAA;AAAA,EACT,MAAA,EAAQ,SAAA;AAAA,EACR,SAAA,EAAW;AACb","file":"index.mjs","sourcesContent":["/**\n * Auto-generated build version metadata.\n * Do not edit directly; updated by scripts/stamp-version.ts\n */\nexport const VERSION = '0.36.2130';\nexport const GIT_SHA = 'aabe90c';\nexport const BUILD_TIME = '2026-10-14T23:19:24.665Z';\nexport const SEMVER = {\n version: '0.36.2130',\n gitSha: 'aabe90c',\n buildTime: '2026-10-14T23:19:24.665Z',\n} as const;\n"]}
@@ -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
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/server/errors.ts","../../src/server/app.ts"],"names":["storeDefinition","keyFromValue","realpath","join","sep","stat","resolve","getRow","listRows","countRows","refreshDomain","wipeRows","wipeDomain","MAX_BODY","handleDomainQuery","realpathSync","StorageError"],"mappings":"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","file":"app.cjs","sourcesContent":["/**\n * errors.ts — the single HTTP error type every layer throws; app.ts maps it\n * to the wire's { error, code? } JSON with the matching status.\n */\nexport class HttpError extends Error {\n constructor(readonly status: number, message: string, readonly code?: string) {\n super(message);\n this.name = 'HttpError';\n }\n}\n","/**\n * app.ts — one native fetch handler for the whole surface:\n * GET /api/health\n * GET /api/v1/:store/get?key= GET /api/v1/:store/all?range=&count=\n * GET /api/v1/:store/index/:idx?range=&count=\n * GET /api/v1/:store/count?range=&index=\n * PUT /api/v1/:store {value} DELETE /api/v1/:store?key=\n * POST /api/v1/:store/clear POST /api/v1/tx {ops}\n * POST /api/v1/wipe POST /api/v1/query\n * everything else: static SPA serving from playgroundDist (asset miss with an\n * extension → 404; extensionless → index.html; traversal/symlink escapes →\n * 403; /api never falls through to the SPA).\n *\n * `db` may be null for the CLI playground mode: static SPA serving only, every\n * /api request answers 404 — the playground never touches a server database.\n */\n\nimport { join, resolve, sep } from 'node:path';\nimport { stat, realpath } from 'node:fs/promises';\nimport { realpathSync } from 'node:fs';\nimport { HttpError } from './errors';\nimport {\n handleDomainQuery,\n refreshDomain,\n wipeDomain,\n MAX_BODY,\n StorageError,\n storeDefinition,\n getRow,\n listRows,\n countRows,\n wipeRows,\n type Database,\n} from '@bitcobblers/wod-wiki-storage/sql';\nimport { keyFromValue, type KeyDTO, type RangeDTO, type TxOp } from '@bitcobblers/wod-wiki-storage';\n\nconst json = (value: unknown, status: number): Response =>\n new Response(JSON.stringify(value), { status, headers: { 'content-type': 'application/json' } });\n\n/** Bun.file without the bun-types ambient global (the engine stays\n * Node-compilable; server code only ever runs under Bun). */\nfunction bunFile(path: string): BodyInit {\n // Well-known runtime global invisible to the compiler (no bun-types here).\n const bun = globalThis as { Bun?: { file(path: string): BodyInit } };\n if (!bun.Bun) throw new Error('the wod-wiki server handler requires the Bun runtime (https://bun.sh)');\n return bun.Bun.file(path);\n}\n\nconst plain = (body: string, status: number): Response =>\n new Response(body, { status, headers: { 'content-type': 'text/plain; charset=utf-8' } });\n\nconst notFound = (): Response => json({ error: 'not found' }, 404);\nconst methodNotAllowed = (): Response => json({ error: 'method not allowed' }, 405);\n\nconst noContent = (): Response => new Response(null, { status: 204 });\n\n/** URLSearchParams already applies the form-urlencoded '+' → space then %XX\n * decoding the wire contract relies on. */\nconst qget = (url: URL, name: string): string | undefined => url.searchParams.get(name) ?? undefined;\n\nfunction jsonParam(url: URL, name: string): unknown {\n const raw = qget(url, name);\n if (raw === undefined) return undefined;\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n throw new HttpError(400, `invalid JSON in ?${name}`);\n }\n}\n\n/** ?range accepts a RangeDTO object or a bare key (scalar/tuple → {only}). */\nfunction rangeParam(url: URL): RangeDTO | undefined {\n const v = jsonParam(url, 'range');\n if (v === undefined) return undefined;\n if (v !== null && typeof v === 'object' && !Array.isArray(v)) return v as RangeDTO;\n return { only: v as KeyDTO };\n}\n\nfunction countParam(url: URL): number | undefined {\n const raw = qget(url, 'count');\n if (raw === undefined) return undefined;\n const n = Number(raw); // Number('') === 0, matching the wire contract\n if (!Number.isFinite(n) || n < 0) throw new HttpError(400, 'invalid ?count');\n return Math.trunc(n);\n}\n\nasync function readJsonBody(req: Request, maxBytes?: number): Promise<unknown> {\n const body = await req.text();\n if (maxBytes !== undefined && body.length > maxBytes) {\n throw new HttpError(413, `domain query body exceeds ${maxBytes} bytes`);\n }\n try {\n return JSON.parse(body) as unknown;\n } catch {\n throw new HttpError(400, 'body must be valid JSON');\n }\n}\n\nfunction txOps(value: unknown): TxOp[] {\n if (!Array.isArray(value)) throw new HttpError(400, 'tx body must be {ops: []}');\n return value.map((op: unknown): TxOp => {\n if (op === null || typeof op !== 'object' || Array.isArray(op) || !('store' in op) || typeof op.store !== 'string' || !('op' in op)) {\n throw new HttpError(400, 'tx op must have a store and operation');\n }\n const def = storeDefinition(op.store);\n const store = def.name;\n switch (op.op) {\n case 'put':\n if (!('value' in op)) throw new HttpError(400, 'put requires value');\n return { op: 'put', store, value: op.value };\n case 'delete':\n if (!('key' in op)) throw new HttpError(400, 'delete requires key');\n return { op: 'delete', store, key: op.key as KeyDTO };\n case 'clear':\n return { op: 'clear', store };\n case 'check': {\n if (!('key' in op) || !('value' in op)) throw new HttpError(400, 'check requires key and value');\n const scalar = (key: unknown): key is string | number => typeof key === 'string' || (typeof key === 'number' && Number.isFinite(key));\n if (!scalar(op.key) && !(Array.isArray(op.key) && op.key.length > 0 && op.key.every(scalar))) {\n throw new HttpError(400, 'check requires a scalar key or non-empty scalar tuple');\n }\n if (op.value !== null && (typeof op.value !== 'object' || Array.isArray(op.value))) {\n throw new HttpError(400, 'check value must be a row or null');\n }\n const parts = Array.isArray(op.key) ? op.key : [op.key];\n if (parts.length !== def.keyPath.length) throw new HttpError(400, 'check key arity must match the store');\n if (op.value !== null) {\n let rowKey: KeyDTO;\n try { rowKey = keyFromValue(def, op.value); }\n catch { throw new HttpError(400, 'check row must have its primary key'); }\n if (JSON.stringify(rowKey) !== JSON.stringify(op.key)) throw new HttpError(400, 'check row primary key must match key');\n }\n return { op: 'check', store, key: op.key, value: op.value };\n }\n default:\n throw new HttpError(400, 'tx op must be put | delete | clear | check');\n }\n });\n}\n\n// ── Static SPA serving ───────────────────────────────────────────────────────\n\nconst MIME: Record<string, string> = {\n html: 'text/html; charset=utf-8',\n js: 'text/javascript; charset=utf-8',\n mjs: 'text/javascript; charset=utf-8',\n css: 'text/css; charset=utf-8',\n json: 'application/json; charset=utf-8',\n map: 'application/json; charset=utf-8',\n svg: 'image/svg+xml',\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n webp: 'image/webp',\n ico: 'image/x-icon',\n woff: 'font/woff',\n woff2: 'font/woff2',\n ttf: 'font/ttf',\n mp3: 'audio/mpeg',\n wav: 'audio/wav',\n webmanifest: 'application/manifest+json',\n xml: 'application/xml',\n txt: 'text/plain; charset=utf-8',\n};\n\nasync function realpathSafe(path: string): Promise<string | undefined> {\n try {\n return await realpath(path);\n } catch {\n return undefined;\n }\n}\n\nasync function serveFile(real: string, rel: string): Promise<Response> {\n // Seed chunks are content-hashed; everything else revalidates.\n const cache = rel.includes('seed/chunks/')\n ? 'public, max-age=31536000, immutable'\n : 'no-cache';\n const ext = rel.slice(rel.lastIndexOf('.') + 1).toLowerCase();\n return new Response(bunFile(real), {\n headers: { 'content-type': MIME[ext] ?? 'application/octet-stream', 'cache-control': cache },\n });\n}\n\nasync function serveSpaShell(distRoot: string): Promise<Response | undefined> {\n const index = await realpathSafe(join(distRoot, 'index.html'));\n // Same symlink confinement as assets: a dist/index.html symlink pointing\n // outside must not become the shell.\n if (index && (index === distRoot || index.startsWith(distRoot + sep))\n && (await stat(index).then((s) => s.isFile(), () => false))) {\n return new Response(bunFile(index), {\n headers: { 'content-type': MIME.html, 'cache-control': 'no-cache' },\n });\n }\n return undefined;\n}\n\nasync function staticResponse(distRoot: string | undefined, method: string, pathname: string): Promise<Response> {\n if (method !== 'GET') return plain('method not allowed', 405);\n if (!distRoot) {\n return plain('wod-wiki-api: playground dist not found. Set PLAYGROUND_DIST or run the playground server build.', 503);\n }\n let rel: string;\n try {\n rel = decodeURIComponent(pathname).replace(/^\\/+/, '');\n } catch {\n return plain('bad request', 400);\n }\n if (rel === '') rel = 'index.html';\n const parts = rel.split('/');\n if (parts.some((p) => p === '..')) return plain('forbidden', 403);\n const base = parts[parts.length - 1] ?? '';\n const hasExtension = base.includes('.');\n\n const abs = resolve(distRoot, ...parts);\n const real = await realpathSafe(abs);\n // Symlink confinement: serve only paths that really live under the dist root.\n if (real && (real === distRoot || real.startsWith(distRoot + sep))) {\n const info = await stat(real).then((s) => s.isFile(), () => false);\n if (info) return serveFile(real, rel);\n }\n // SPA fallback: extensionless paths render the app shell.\n if (!hasExtension) {\n const shell = await serveSpaShell(distRoot);\n if (shell) return shell;\n }\n return plain('not found', 404);\n}\n\n// ── API routing ──────────────────────────────────────────────────────────────\n\nasync function storeRoutes(db: Database, method: string, sub: string, url: URL, req: Request): Promise<Response> {\n let inner = sub.slice('/v1/'.length);\n if (inner.endsWith('/')) inner = inner.slice(0, -1);\n if (inner === '') return notFound();\n const segments = inner.split('/');\n let name: string;\n let action: string | undefined;\n if (segments.length === 1) {\n name = segments[0];\n } else if (segments.length === 2) {\n [name, action] = segments;\n } else if (segments.length === 3 && segments[1] === 'index' && segments[2] !== '') {\n [name, , action] = segments;\n action = `index/${action}`;\n } else {\n return notFound();\n }\n const def = storeDefinition(name); // 404 for unknown stores\n\n const range = rangeParam(url);\n const count = countParam(url);\n\n if (action === 'get' && method === 'GET') {\n const key = jsonParam(url, 'key');\n if (key === undefined) throw new HttpError(400, 'missing ?key');\n const row = await getRow(db, def, key as KeyDTO);\n return row === undefined ? notFound() : json(row, 200);\n }\n if (action === 'all' && method === 'GET') {\n return json(await listRows(db, def, range, count), 200);\n }\n if (action === 'count' && method === 'GET') {\n const indexName = qget(url, 'index');\n const n = indexName\n ? await countRows(db, def, range, indexName)\n : await countRows(db, def, range);\n return json({ count: Number(n) }, 200);\n }\n if (action === 'clear' && method === 'POST') {\n await db.transaction((tx) => refreshDomain(tx, [{ op: 'clear', store: def.name }]));\n return noContent();\n }\n if (action?.startsWith('index/') && method === 'GET') {\n return json(await listRows(db, def, range, count, action.slice('index/'.length)), 200);\n }\n if (action === undefined && method === 'PUT') {\n const parsed = await readJsonBody(req);\n const value = typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed) && 'value' in parsed\n ? parsed.value\n : undefined;\n if (value === undefined) throw new HttpError(400, 'body must be {value: ...}');\n const [key] = await db.transaction((tx) => refreshDomain(tx, [{ op: 'put', store: def.name, value }]));\n return json({ key }, 200);\n }\n if (action === undefined && method === 'DELETE') {\n const keyParam = jsonParam(url, 'key');\n const target = keyParam !== undefined ? keyParam : range;\n if (target === undefined || target === null) throw new HttpError(400, 'DELETE requires ?key or ?range');\n // Wire DELETE accepts a key or a range; the TxOp delete variant types the\n // narrower KeyDTO even though deleteRows honours both.\n await db.transaction((tx) => refreshDomain(tx, [{ op: 'delete', store: def.name, key: target as KeyDTO }]));\n return noContent();\n }\n return methodNotAllowed();\n}\n\nasync function apiRoutes(db: Database, req: Request, method: string, sub: string, url: URL): Promise<Response> {\n if (sub === '/health') {\n return json({ ok: true, driver: db.dialect }, 200);\n }\n if (sub === '/v1/wipe' && method === 'POST') {\n await db.transaction(async (tx) => {\n // System memberships survive; derived projections follow (empty is consistent).\n await wipeRows(tx);\n await wipeDomain(tx);\n });\n return noContent();\n }\n if (sub === '/v1/tx' && method === 'POST') {\n const parsed = await readJsonBody(req);\n const ops = typeof parsed === 'object' && parsed !== null && 'ops' in parsed ? parsed.ops : undefined;\n const parsedOps = txOps(ops);\n const results = await db.transaction((tx) => refreshDomain(tx, parsedOps));\n return json({ results }, 200);\n }\n if (sub === '/v1/query') {\n if (method !== 'POST') return methodNotAllowed();\n const body = await readJsonBody(req, MAX_BODY);\n return json(await handleDomainQuery(db, body), 200);\n }\n if (sub.startsWith('/v1/')) return storeRoutes(db, method, sub, url, req);\n return notFound();\n}\n\n// ── Handler factory ──────────────────────────────────────────────────────────\n\nfunction distRootOf(playgroundDist: string | undefined): string | undefined {\n if (!playgroundDist) return undefined;\n try {\n return realpathSync(playgroundDist);\n } catch {\n return undefined;\n }\n}\n\nexport function createHandler(\n db: Database | null,\n options: { playgroundDist: string },\n): (request: Request) => Promise<Response> {\n const distRoot = distRootOf(options.playgroundDist);\n return async (request: Request): Promise<Response> => {\n try {\n const url = new URL(request.url);\n const method = request.method === 'HEAD' ? 'GET' : request.method; // Bun strips HEAD bodies\n const path = url.pathname;\n const response = path === '/api' || path.startsWith('/api/')\n ? db\n ? await apiRoutes(db, request, method, path.slice('/api'.length) || '/', url)\n : notFound()\n : await staticResponse(distRoot, method, path);\n return response;\n } catch (e) {\n if (e instanceof HttpError || e instanceof StorageError) {\n const status = e.status;\n if (status >= 500) console.error('[wod-wiki-api]', e.message);\n const body: { error: string; code?: string } = { error: status >= 500 ? 'internal error' : e.message };\n if (e.code) body.code = e.code;\n return json(body, status);\n }\n console.error('[wod-wiki-api]', e);\n return json({ error: 'internal error' }, 500);\n }\n };\n}\n"]}
@@ -0,0 +1,24 @@
1
+ import { Database } from '@bitcobblers/wod-wiki-storage/sql';
2
+
3
+ /**
4
+ * app.ts — one native fetch handler for the whole surface:
5
+ * GET /api/health
6
+ * GET /api/v1/:store/get?key= GET /api/v1/:store/all?range=&count=
7
+ * GET /api/v1/:store/index/:idx?range=&count=
8
+ * GET /api/v1/:store/count?range=&index=
9
+ * PUT /api/v1/:store {value} DELETE /api/v1/:store?key=
10
+ * POST /api/v1/:store/clear POST /api/v1/tx {ops}
11
+ * POST /api/v1/wipe POST /api/v1/query
12
+ * everything else: static SPA serving from playgroundDist (asset miss with an
13
+ * extension → 404; extensionless → index.html; traversal/symlink escapes →
14
+ * 403; /api never falls through to the SPA).
15
+ *
16
+ * `db` may be null for the CLI playground mode: static SPA serving only, every
17
+ * /api request answers 404 — the playground never touches a server database.
18
+ */
19
+
20
+ declare function createHandler(db: Database | null, options: {
21
+ playgroundDist: string;
22
+ }): (request: Request) => Promise<Response>;
23
+
24
+ export { createHandler };
@@ -0,0 +1,24 @@
1
+ import { Database } from '@bitcobblers/wod-wiki-storage/sql';
2
+
3
+ /**
4
+ * app.ts — one native fetch handler for the whole surface:
5
+ * GET /api/health
6
+ * GET /api/v1/:store/get?key= GET /api/v1/:store/all?range=&count=
7
+ * GET /api/v1/:store/index/:idx?range=&count=
8
+ * GET /api/v1/:store/count?range=&index=
9
+ * PUT /api/v1/:store {value} DELETE /api/v1/:store?key=
10
+ * POST /api/v1/:store/clear POST /api/v1/tx {ops}
11
+ * POST /api/v1/wipe POST /api/v1/query
12
+ * everything else: static SPA serving from playgroundDist (asset miss with an
13
+ * extension → 404; extensionless → index.html; traversal/symlink escapes →
14
+ * 403; /api never falls through to the SPA).
15
+ *
16
+ * `db` may be null for the CLI playground mode: static SPA serving only, every
17
+ * /api request answers 404 — the playground never touches a server database.
18
+ */
19
+
20
+ declare function createHandler(db: Database | null, options: {
21
+ playgroundDist: string;
22
+ }): (request: Request) => Promise<Response>;
23
+
24
+ export { createHandler };