@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.
- package/dist/bin/wod.cjs +758 -194
- package/dist/bin/wod.cjs.map +1 -1
- package/dist/bin/wod.d.cts +1 -1
- package/dist/bin/wod.d.ts +1 -1
- package/dist/bin/wod.mjs +5 -3
- package/dist/bin/wod.mjs.map +1 -1
- package/dist/chunk-6L5ZDZJ7.mjs +15 -0
- package/dist/chunk-6L5ZDZJ7.mjs.map +1 -0
- package/dist/{chunk-Y6WO6MXS.mjs → chunk-BMHE7DDG.mjs} +3 -9
- package/dist/chunk-BMHE7DDG.mjs.map +1 -0
- package/dist/chunk-EUQWEX6Z.mjs +253 -0
- package/dist/chunk-EUQWEX6Z.mjs.map +1 -0
- package/dist/{chunk-N4KFWY6Z.mjs → chunk-UKBQ36RW.mjs} +377 -193
- package/dist/chunk-UKBQ36RW.mjs.map +1 -0
- package/dist/{compiler-SXUCF7AU.mjs → compiler-NODHKWJJ.mjs} +4 -3
- package/dist/compiler-NODHKWJJ.mjs.map +1 -0
- package/dist/index.cjs +763 -199
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -8
- package/dist/index.d.ts +27 -8
- package/dist/index.mjs +10 -8
- package/dist/index.mjs.map +1 -1
- package/dist/server/app.cjs +299 -0
- package/dist/server/app.cjs.map +1 -0
- package/dist/server/app.d.cts +24 -0
- package/dist/server/app.d.ts +24 -0
- package/dist/server/app.mjs +301 -0
- package/dist/server/app.mjs.map +1 -0
- package/dist/server-4P6AJAGT.mjs +128 -0
- package/dist/server-4P6AJAGT.mjs.map +1 -0
- package/dist/web/playground/ai-skills/parse.md +656 -0
- package/dist/web/playground/assets/main-Bhn9NHgQ.css +2 -0
- package/dist/web/playground/assets/main-CGSJY1vM.js +212 -0
- package/dist/web/playground/assets/rolldown-runtime-C0FnF6B9.js +1 -0
- package/dist/web/playground/assets/statementBuilderFlow-PTgOOzz5.js +2 -0
- package/dist/web/playground/assets/vendor-BlJqgFME.js +201 -0
- package/dist/web/playground/assets/vendor-codemirror-BtJgoBjf.js +40 -0
- package/dist/web/playground/fonts/Inter-Regular.woff2 +0 -0
- package/dist/web/playground/fonts/Inter-SemiBold.woff2 +0 -0
- package/dist/web/playground/images/hero/editing.png +0 -0
- package/dist/web/playground/images/hero/journal.png +0 -0
- package/dist/web/playground/images/hero/results.png +0 -0
- package/dist/web/playground/images/hero/timer.png +0 -0
- package/dist/web/playground/images/wod-wiki-logo-dark.png +0 -0
- package/dist/web/playground/images/wod-wiki-logo-light.png +0 -0
- package/dist/web/playground/index.html +60 -0
- package/dist/web/playground/seed/core/chunks/canvas.e6c77ad0.json +1 -0
- package/dist/web/playground/seed/core/chunks/template.e5e0e82d.json +1 -0
- package/dist/web/playground/seed/core/manifest.json +24 -0
- package/dist/web/playground/seed/core/report.html +43 -0
- package/dist/web/server/ai-skills/parse.md +656 -0
- package/dist/web/server/assets/main-Bhn9NHgQ.css +2 -0
- package/dist/web/server/assets/main-DhN2J5Ax.js +211 -0
- package/dist/web/server/assets/rolldown-runtime-C0FnF6B9.js +1 -0
- package/dist/web/server/assets/statementBuilderFlow-Dj0o2Wpk.js +2 -0
- package/dist/web/server/assets/vendor-BlJqgFME.js +201 -0
- package/dist/web/server/assets/vendor-codemirror-BtJgoBjf.js +40 -0
- package/dist/web/server/fonts/Inter-Regular.woff2 +0 -0
- package/dist/web/server/fonts/Inter-SemiBold.woff2 +0 -0
- package/dist/web/server/images/hero/editing.png +0 -0
- package/dist/web/server/images/hero/journal.png +0 -0
- package/dist/web/server/images/hero/results.png +0 -0
- package/dist/web/server/images/hero/timer.png +0 -0
- package/dist/web/server/images/wod-wiki-logo-dark.png +0 -0
- package/dist/web/server/images/wod-wiki-logo-light.png +0 -0
- package/dist/web/server/index.html +60 -0
- package/dist/web/server/seed/core/chunks/canvas.e6c77ad0.json +1 -0
- package/dist/web/server/seed/core/chunks/template.e5e0e82d.json +1 -0
- package/dist/web/server/seed/core/manifest.json +24 -0
- package/dist/web/server/seed/core/report.html +43 -0
- package/package.json +11 -5
- package/dist/chunk-N4KFWY6Z.mjs.map +0 -1
- package/dist/chunk-Y6WO6MXS.mjs.map +0 -1
- 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" | "
|
|
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.
|
|
402
|
-
declare const GIT_SHA = "
|
|
403
|
-
declare const BUILD_TIME = "2026-10-
|
|
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.
|
|
406
|
-
readonly gitSha: "
|
|
407
|
-
readonly buildTime: "2026-10-
|
|
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" | "
|
|
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.
|
|
402
|
-
declare const GIT_SHA = "
|
|
403
|
-
declare const BUILD_TIME = "2026-10-
|
|
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.
|
|
406
|
-
readonly gitSha: "
|
|
407
|
-
readonly buildTime: "2026-10-
|
|
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-
|
|
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-
|
|
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.
|
|
15
|
-
var GIT_SHA = "
|
|
16
|
-
var BUILD_TIME = "2026-10-
|
|
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.
|
|
19
|
-
gitSha: "
|
|
20
|
-
buildTime: "2026-10-
|
|
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 };
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/version.ts"],"names":[],"mappings":"
|
|
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":";;;;;;;;;;;AAIO,IAAM,SAAA,GAAN,cAAwB,KAAA,CAAM;AAAA,EACnC,WAAA,CAAqB,MAAA,EAAgB,OAAA,EAA0B,IAAA,EAAe;AAC5E,IAAA,KAAA,CAAM,OAAO,CAAA;AADM,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAA0C,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AAE7D,IAAA,IAAA,CAAK,IAAA,GAAO,WAAA;AAAA,EACd;AAAA,EAHqB,MAAA;AAAA,EAA0C,IAAA;AAIjE,CAAA;AC2BA,IAAM,OAAO,CAAC,KAAA,EAAgB,MAAA,KAC5B,IAAI,SAAS,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA,EAAG,EAAE,MAAA,EAAQ,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,IAAsB,CAAA;AAIjG,SAAS,QAAQ,IAAA,EAAwB;AAEvC,EAAA,MAAM,GAAA,GAAM,UAAA;AACZ,EAAA,IAAI,CAAC,GAAA,CAAI,GAAA,EAAK,MAAM,IAAI,MAAM,uEAAuE,CAAA;AACrG,EAAA,OAAO,GAAA,CAAI,GAAA,CAAI,IAAA,CAAK,IAAI,CAAA;AAC1B;AAEA,IAAM,KAAA,GAAQ,CAAC,IAAA,EAAc,MAAA,KAC3B,IAAI,QAAA,CAAS,IAAA,EAAM,EAAE,MAAA,EAAQ,OAAA,EAAS,EAAE,cAAA,EAAgB,2BAAA,IAA+B,CAAA;AAEzF,IAAM,WAAW,MAAgB,IAAA,CAAK,EAAE,KAAA,EAAO,WAAA,IAAe,GAAG,CAAA;AACjE,IAAM,mBAAmB,MAAgB,IAAA,CAAK,EAAE,KAAA,EAAO,oBAAA,IAAwB,GAAG,CAAA;AAElF,IAAM,SAAA,GAAY,MAAgB,IAAI,QAAA,CAAS,MAAM,EAAE,MAAA,EAAQ,KAAK,CAAA;AAIpE,IAAM,IAAA,GAAO,CAAC,GAAA,EAAU,IAAA,KAAqC,IAAI,YAAA,CAAa,GAAA,CAAI,IAAI,CAAA,IAAK,MAAA;AAE3F,SAAS,SAAA,CAAU,KAAU,IAAA,EAAuB;AAClD,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,EAAK,IAAI,CAAA;AAC1B,EAAA,IAAI,GAAA,KAAQ,QAAW,OAAO,MAAA;AAC9B,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,CAAK,MAAM,GAAG,CAAA;AAAA,EACvB,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,CAAA,iBAAA,EAAoB,IAAI,CAAA,CAAE,CAAA;AAAA,EACrD;AACF;AAGA,SAAS,WAAW,GAAA,EAAgC;AAClD,EAAA,MAAM,CAAA,GAAI,SAAA,CAAU,GAAA,EAAK,OAAO,CAAA;AAChC,EAAA,IAAI,CAAA,KAAM,QAAW,OAAO,MAAA;AAC5B,EAAA,IAAI,CAAA,KAAM,IAAA,IAAQ,OAAO,CAAA,KAAM,QAAA,IAAY,CAAC,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA,EAAG,OAAO,CAAA;AACrE,EAAA,OAAO,EAAE,MAAM,CAAA,EAAY;AAC7B;AAEA,SAAS,WAAW,GAAA,EAA8B;AAChD,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,EAAK,OAAO,CAAA;AAC7B,EAAA,IAAI,GAAA,KAAQ,QAAW,OAAO,MAAA;AAC9B,EAAA,MAAM,CAAA,GAAI,OAAO,GAAG,CAAA;AACpB,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,CAAC,CAAA,IAAK,CAAA,GAAI,CAAA,EAAG,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,gBAAgB,CAAA;AAC3E,EAAA,OAAO,IAAA,CAAK,MAAM,CAAC,CAAA;AACrB;AAEA,eAAe,YAAA,CAAa,KAAc,QAAA,EAAqC;AAC7E,EAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAC5B,EAAA,IAAI,QAAA,KAAa,MAAA,IAAa,IAAA,CAAK,MAAA,GAAS,QAAA,EAAU;AACpD,IAAA,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,CAAA,0BAAA,EAA6B,QAAQ,CAAA,MAAA,CAAQ,CAAA;AAAA,EACxE;AACA,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,EACxB,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,yBAAyB,CAAA;AAAA,EACpD;AACF;AAEA,SAAS,MAAM,KAAA,EAAwB;AACrC,EAAA,IAAI,CAAC,MAAM,OAAA,CAAQ,KAAK,GAAG,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,2BAA2B,CAAA;AAC/E,EAAA,OAAO,KAAA,CAAM,GAAA,CAAI,CAAC,EAAA,KAAsB;AACtC,IAAA,IAAI,OAAO,IAAA,IAAQ,OAAO,OAAO,QAAA,IAAY,KAAA,CAAM,QAAQ,EAAE,CAAA,IAAK,EAAE,OAAA,IAAW,OAAO,OAAO,EAAA,CAAG,UAAU,QAAA,IAAY,EAAE,QAAQ,EAAA,CAAA,EAAK;AACnI,MAAA,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,uCAAuC,CAAA;AAAA,IAClE;AACA,IAAA,MAAM,GAAA,GAAMA,mBAAA,CAAgB,EAAA,CAAG,KAAK,CAAA;AACpC,IAAA,MAAM,QAAQ,GAAA,CAAI,IAAA;AAClB,IAAA,QAAQ,GAAG,EAAA;AAAI,MACb,KAAK,KAAA;AACH,QAAA,IAAI,EAAE,OAAA,IAAW,EAAA,CAAA,QAAW,IAAI,SAAA,CAAU,KAAK,oBAAoB,CAAA;AACnE,QAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,KAAA,EAAO,KAAA,EAAO,GAAG,KAAA,EAAM;AAAA,MAC7C,KAAK,QAAA;AACH,QAAA,IAAI,EAAE,KAAA,IAAS,EAAA,CAAA,QAAW,IAAI,SAAA,CAAU,KAAK,qBAAqB,CAAA;AAClE,QAAA,OAAO,EAAE,EAAA,EAAI,QAAA,EAAU,KAAA,EAAO,GAAA,EAAK,GAAG,GAAA,EAAc;AAAA,MACtD,KAAK,OAAA;AACH,QAAA,OAAO,EAAE,EAAA,EAAI,OAAA,EAAS,KAAA,EAAM;AAAA,MAC9B,KAAK,OAAA,EAAS;AACZ,QAAA,IAAI,EAAE,KAAA,IAAS,EAAA,CAAA,IAAO,EAAE,OAAA,IAAW,KAAK,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,8BAA8B,CAAA;AAC/F,QAAA,MAAM,MAAA,GAAS,CAAC,GAAA,KAAyC,OAAO,GAAA,KAAQ,QAAA,IAAa,OAAO,GAAA,KAAQ,QAAA,IAAY,MAAA,CAAO,QAAA,CAAS,GAAG,CAAA;AACnI,QAAA,IAAI,CAAC,MAAA,CAAO,EAAA,CAAG,GAAG,CAAA,IAAK,EAAE,MAAM,OAAA,CAAQ,EAAA,CAAG,GAAG,CAAA,IAAK,EAAA,CAAG,IAAI,MAAA,GAAS,CAAA,IAAK,GAAG,GAAA,CAAI,KAAA,CAAM,MAAM,CAAA,CAAA,EAAI;AAC5F,UAAA,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,uDAAuD,CAAA;AAAA,QAClF;AACA,QAAA,IAAI,EAAA,CAAG,KAAA,KAAU,IAAA,KAAS,OAAO,EAAA,CAAG,KAAA,KAAU,QAAA,IAAY,KAAA,CAAM,OAAA,CAAQ,EAAA,CAAG,KAAK,CAAA,CAAA,EAAI;AAClF,UAAA,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,mCAAmC,CAAA;AAAA,QAC9D;AACA,QAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,OAAA,CAAQ,EAAA,CAAG,GAAG,IAAI,EAAA,CAAG,GAAA,GAAM,CAAC,EAAA,CAAG,GAAG,CAAA;AACtD,QAAA,IAAI,KAAA,CAAM,WAAW,GAAA,CAAI,OAAA,CAAQ,QAAQ,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,sCAAsC,CAAA;AACxG,QAAA,IAAI,EAAA,CAAG,UAAU,IAAA,EAAM;AACrB,UAAA,IAAI,MAAA;AACJ,UAAA,IAAI;AAAE,YAAA,MAAA,GAASC,2BAAA,CAAa,GAAA,EAAK,EAAA,CAAG,KAAK,CAAA;AAAA,UAAG,CAAA,CAAA,MACtC;AAAE,YAAA,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,qCAAqC,CAAA;AAAA,UAAG;AACzE,UAAA,IAAI,IAAA,CAAK,SAAA,CAAU,MAAM,CAAA,KAAM,IAAA,CAAK,SAAA,CAAU,EAAA,CAAG,GAAG,CAAA,EAAG,MAAM,IAAI,SAAA,CAAU,KAAK,sCAAsC,CAAA;AAAA,QACxH;AACA,QAAA,OAAO,EAAE,IAAI,OAAA,EAAS,KAAA,EAAO,KAAK,EAAA,CAAG,GAAA,EAAK,KAAA,EAAO,EAAA,CAAG,KAAA,EAAM;AAAA,MAC5D;AAAA,MACA;AACE,QAAA,MAAM,IAAI,SAAA,CAAU,GAAA,EAAK,4CAA4C,CAAA;AAAA;AACzE,EACF,CAAC,CAAA;AACH;AAIA,IAAM,IAAA,GAA+B;AAAA,EACnC,IAAA,EAAM,0BAAA;AAAA,EACN,EAAA,EAAI,gCAAA;AAAA,EACJ,GAAA,EAAK,gCAAA;AAAA,EACL,GAAA,EAAK,yBAAA;AAAA,EACL,IAAA,EAAM,iCAAA;AAAA,EACN,GAAA,EAAK,iCAAA;AAAA,EACL,GAAA,EAAK,eAAA;AAAA,EACL,GAAA,EAAK,WAAA;AAAA,EACL,GAAA,EAAK,YAAA;AAAA,EACL,IAAA,EAAM,YAAA;AAAA,EACN,IAAA,EAAM,YAAA;AAAA,EACN,GAAA,EAAK,cAAA;AAAA,EACL,IAAA,EAAM,WAAA;AAAA,EACN,KAAA,EAAO,YAAA;AAAA,EACP,GAAA,EAAK,UAAA;AAAA,EACL,GAAA,EAAK,YAAA;AAAA,EACL,GAAA,EAAK,WAAA;AAAA,EACL,WAAA,EAAa,2BAAA;AAAA,EACb,GAAA,EAAK,iBAAA;AAAA,EACL,GAAA,EAAK;AACP,CAAA;AAEA,eAAe,aAAa,IAAA,EAA2C;AACrE,EAAA,IAAI;AACF,IAAA,OAAO,MAAMC,kBAAS,IAAI,CAAA;AAAA,EAC5B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,MAAA;AAAA,EACT;AACF;AAEA,eAAe,SAAA,CAAU,MAAc,GAAA,EAAgC;AAErE,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,QAAA,CAAS,cAAc,IACrC,qCAAA,GACA,UAAA;AACJ,EAAA,MAAM,GAAA,GAAM,IAAI,KAAA,CAAM,GAAA,CAAI,YAAY,GAAG,CAAA,GAAI,CAAC,CAAA,CAAE,WAAA,EAAY;AAC5D,EAAA,OAAO,IAAI,QAAA,CAAS,OAAA,CAAQ,IAAI,CAAA,EAAG;AAAA,IACjC,OAAA,EAAS,EAAE,cAAA,EAAgB,IAAA,CAAK,GAAG,CAAA,IAAK,0BAAA,EAA4B,iBAAiB,KAAA;AAAM,GAC5F,CAAA;AACH;AAEA,eAAe,cAAc,QAAA,EAAiD;AAC5E,EAAA,MAAM,QAAQ,MAAM,YAAA,CAAaC,SAAA,CAAK,QAAA,EAAU,YAAY,CAAC,CAAA;AAG7D,EAAA,IAAI,KAAA,KAAU,UAAU,QAAA,IAAY,KAAA,CAAM,WAAW,QAAA,GAAWC,QAAG,MAC7D,MAAMC,aAAA,CAAK,KAAK,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,QAAO,EAAG,MAAM,KAAK,CAAA,EAAI;AAC7D,IAAA,OAAO,IAAI,QAAA,CAAS,OAAA,CAAQ,KAAK,CAAA,EAAG;AAAA,MAClC,SAAS,EAAE,cAAA,EAAgB,IAAA,CAAK,IAAA,EAAM,iBAAiB,UAAA;AAAW,KACnE,CAAA;AAAA,EACH;AACA,EAAA,OAAO,MAAA;AACT;AAEA,eAAe,cAAA,CAAe,QAAA,EAA8B,MAAA,EAAgB,QAAA,EAAqC;AAC/G,EAAA,IAAI,MAAA,KAAW,KAAA,EAAO,OAAO,KAAA,CAAM,sBAAsB,GAAG,CAAA;AAC5D,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,OAAO,KAAA,CAAM,oGAAoG,GAAG,CAAA;AAAA,EACtH;AACA,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA,GAAA,GAAM,kBAAA,CAAmB,QAAQ,CAAA,CAAE,OAAA,CAAQ,QAAQ,EAAE,CAAA;AAAA,EACvD,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA,CAAM,eAAe,GAAG,CAAA;AAAA,EACjC;AACA,EAAA,IAAI,GAAA,KAAQ,IAAI,GAAA,GAAM,YAAA;AACtB,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,KAAA,CAAM,GAAG,CAAA;AAC3B,EAAA,IAAI,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,KAAM,IAAI,CAAA,EAAG,OAAO,KAAA,CAAM,WAAA,EAAa,GAAG,CAAA;AAChE,EAAA,MAAM,IAAA,GAAO,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,CAAC,CAAA,IAAK,EAAA;AACxC,EAAA,MAAM,YAAA,GAAe,IAAA,CAAK,QAAA,CAAS,GAAG,CAAA;AAEtC,EAAA,MAAM,GAAA,GAAMC,YAAA,CAAQ,QAAA,EAAU,GAAG,KAAK,CAAA;AACtC,EAAA,MAAM,IAAA,GAAO,MAAM,YAAA,CAAa,GAAG,CAAA;AAEnC,EAAA,IAAI,SAAS,IAAA,KAAS,QAAA,IAAY,KAAK,UAAA,CAAW,QAAA,GAAWF,QAAG,CAAA,CAAA,EAAI;AAClE,IAAA,MAAM,IAAA,GAAO,MAAMC,aAAA,CAAK,IAAI,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,EAAO,EAAG,MAAM,KAAK,CAAA;AACjE,IAAA,IAAI,IAAA,EAAM,OAAO,SAAA,CAAU,IAAA,EAAM,GAAG,CAAA;AAAA,EACtC;AAEA,EAAA,IAAI,CAAC,YAAA,EAAc;AACjB,IAAA,MAAM,KAAA,GAAQ,MAAM,aAAA,CAAc,QAAQ,CAAA;AAC1C,IAAA,IAAI,OAAO,OAAO,KAAA;AAAA,EACpB;AACA,EAAA,OAAO,KAAA,CAAM,aAAa,GAAG,CAAA;AAC/B;AAIA,eAAe,WAAA,CAAY,EAAA,EAAc,MAAA,EAAgB,GAAA,EAAa,KAAU,GAAA,EAAiC;AAC/G,EAAA,IAAI,KAAA,GAAQ,GAAA,CAAI,KAAA,CAAM,MAAA,CAAO,MAAM,CAAA;AACnC,EAAA,IAAI,KAAA,CAAM,SAAS,GAAG,CAAA,UAAW,KAAA,CAAM,KAAA,CAAM,GAAG,EAAE,CAAA;AAClD,EAAA,IAAI,KAAA,KAAU,EAAA,EAAI,OAAO,QAAA,EAAS;AAClC,EAAA,MAAM,QAAA,GAAW,KAAA,CAAM,KAAA,CAAM,GAAG,CAAA;AAChC,EAAA,IAAI,IAAA;AACJ,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI,QAAA,CAAS,WAAW,CAAA,EAAG;AACzB,IAAA,IAAA,GAAO,SAAS,CAAC,CAAA;AAAA,EACnB,CAAA,MAAA,IAAW,QAAA,CAAS,MAAA,KAAW,CAAA,EAAG;AAChC,IAAA,CAAC,IAAA,EAAM,MAAM,CAAA,GAAI,QAAA;AAAA,EACnB,CAAA,MAAA,IAAW,QAAA,CAAS,MAAA,KAAW,CAAA,IAAK,QAAA,CAAS,CAAC,CAAA,KAAM,OAAA,IAAW,QAAA,CAAS,CAAC,CAAA,KAAM,EAAA,EAAI;AACjF,IAAA,CAAC,IAAA,IAAQ,MAAM,CAAA,GAAI,QAAA;AACnB,IAAA,MAAA,GAAS,SAAS,MAAM,CAAA,CAAA;AAAA,EAC1B,CAAA,MAAO;AACL,IAAA,OAAO,QAAA,EAAS;AAAA,EAClB;AACA,EAAA,MAAM,GAAA,GAAML,oBAAgB,IAAI,CAAA;AAEhC,EAAA,MAAM,KAAA,GAAQ,WAAW,GAAG,CAAA;AAC5B,EAAA,MAAM,KAAA,GAAQ,WAAW,GAAG,CAAA;AAE5B,EAAA,IAAI,MAAA,KAAW,KAAA,IAAS,MAAA,KAAW,KAAA,EAAO;AACxC,IAAA,MAAM,GAAA,GAAM,SAAA,CAAU,GAAA,EAAK,KAAK,CAAA;AAChC,IAAA,IAAI,QAAQ,MAAA,EAAW,MAAM,IAAI,SAAA,CAAU,KAAK,cAAc,CAAA;AAC9D,IAAA,MAAM,GAAA,GAAM,MAAMO,UAAA,CAAO,EAAA,EAAI,KAAK,GAAa,CAAA;AAC/C,IAAA,OAAO,QAAQ,MAAA,GAAY,QAAA,EAAS,GAAI,IAAA,CAAK,KAAK,GAAG,CAAA;AAAA,EACvD;AACA,EAAA,IAAI,MAAA,KAAW,KAAA,IAAS,MAAA,KAAW,KAAA,EAAO;AACxC,IAAA,OAAO,IAAA,CAAK,MAAMC,YAAA,CAAS,EAAA,EAAI,KAAK,KAAA,EAAO,KAAK,GAAG,GAAG,CAAA;AAAA,EACxD;AACA,EAAA,IAAI,MAAA,KAAW,OAAA,IAAW,MAAA,KAAW,KAAA,EAAO;AAC1C,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,GAAA,EAAK,OAAO,CAAA;AACnC,IAAA,MAAM,CAAA,GAAI,SAAA,GACN,MAAMC,aAAA,CAAU,EAAA,EAAI,GAAA,EAAK,KAAA,EAAO,SAAS,CAAA,GACzC,MAAMA,aAAA,CAAU,EAAA,EAAI,KAAK,KAAK,CAAA;AAClC,IAAA,OAAO,KAAK,EAAE,KAAA,EAAO,OAAO,CAAC,CAAA,IAAK,GAAG,CAAA;AAAA,EACvC;AACA,EAAA,IAAI,MAAA,KAAW,OAAA,IAAW,MAAA,KAAW,MAAA,EAAQ;AAC3C,IAAA,MAAM,EAAA,CAAG,WAAA,CAAY,CAAC,EAAA,KAAOC,kBAAc,EAAA,EAAI,CAAC,EAAE,EAAA,EAAI,SAAS,KAAA,EAAO,GAAA,CAAI,IAAA,EAAM,CAAC,CAAC,CAAA;AAClF,IAAA,OAAO,SAAA,EAAU;AAAA,EACnB;AACA,EAAA,IAAI,MAAA,EAAQ,UAAA,CAAW,QAAQ,CAAA,IAAK,WAAW,KAAA,EAAO;AACpD,IAAA,OAAO,IAAA,CAAK,MAAMF,YAAA,CAAS,EAAA,EAAI,GAAA,EAAK,KAAA,EAAO,KAAA,EAAO,MAAA,CAAO,KAAA,CAAM,QAAA,CAAS,MAAM,CAAC,GAAG,GAAG,CAAA;AAAA,EACvF;AACA,EAAA,IAAI,MAAA,KAAW,MAAA,IAAa,MAAA,KAAW,KAAA,EAAO;AAC5C,IAAA,MAAM,MAAA,GAAS,MAAM,YAAA,CAAa,GAAG,CAAA;AACrC,IAAA,MAAM,KAAA,GAAQ,OAAO,MAAA,KAAW,QAAA,IAAY,WAAW,IAAA,IAAQ,CAAC,KAAA,CAAM,OAAA,CAAQ,MAAM,CAAA,IAAK,OAAA,IAAW,MAAA,GAChG,OAAO,KAAA,GACP,MAAA;AACJ,IAAA,IAAI,UAAU,MAAA,EAAW,MAAM,IAAI,SAAA,CAAU,KAAK,2BAA2B,CAAA;AAC7E,IAAA,MAAM,CAAC,GAAG,CAAA,GAAI,MAAM,GAAG,WAAA,CAAY,CAAC,OAAOE,iBAAA,CAAc,EAAA,EAAI,CAAC,EAAE,EAAA,EAAI,OAAO,KAAA,EAAO,GAAA,CAAI,MAAM,KAAA,EAAO,CAAC,CAAC,CAAA;AACrG,IAAA,OAAO,IAAA,CAAK,EAAE,GAAA,EAAI,EAAG,GAAG,CAAA;AAAA,EAC1B;AACA,EAAA,IAAI,MAAA,KAAW,MAAA,IAAa,MAAA,KAAW,QAAA,EAAU;AAC/C,IAAA,MAAM,QAAA,GAAW,SAAA,CAAU,GAAA,EAAK,KAAK,CAAA;AACrC,IAAA,MAAM,MAAA,GAAS,QAAA,KAAa,MAAA,GAAY,QAAA,GAAW,KAAA;AACnD,IAAA,IAAI,MAAA,KAAW,UAAa,MAAA,KAAW,IAAA,QAAY,IAAI,SAAA,CAAU,KAAK,gCAAgC,CAAA;AAGtG,IAAA,MAAM,GAAG,WAAA,CAAY,CAAC,EAAA,KAAOA,iBAAA,CAAc,IAAI,CAAC,EAAE,EAAA,EAAI,QAAA,EAAU,OAAO,GAAA,CAAI,IAAA,EAAM,KAAK,MAAA,EAAkB,CAAC,CAAC,CAAA;AAC1G,IAAA,OAAO,SAAA,EAAU;AAAA,EACnB;AACA,EAAA,OAAO,gBAAA,EAAiB;AAC1B;AAEA,eAAe,SAAA,CAAU,EAAA,EAAc,GAAA,EAAc,MAAA,EAAgB,KAAa,GAAA,EAA6B;AAC7G,EAAA,IAAI,QAAQ,SAAA,EAAW;AACrB,IAAA,OAAO,IAAA,CAAK,EAAE,EAAA,EAAI,IAAA,EAAM,QAAQ,EAAA,CAAG,OAAA,IAAW,GAAG,CAAA;AAAA,EACnD;AACA,EAAA,IAAI,GAAA,KAAQ,UAAA,IAAc,MAAA,KAAW,MAAA,EAAQ;AAC3C,IAAA,MAAM,EAAA,CAAG,WAAA,CAAY,OAAO,EAAA,KAAO;AAEjC,MAAA,MAAMC,aAAS,EAAE,CAAA;AACjB,MAAA,MAAMC,eAAW,EAAE,CAAA;AAAA,IACrB,CAAC,CAAA;AACD,IAAA,OAAO,SAAA,EAAU;AAAA,EACnB;AACA,EAAA,IAAI,GAAA,KAAQ,QAAA,IAAY,MAAA,KAAW,MAAA,EAAQ;AACzC,IAAA,MAAM,MAAA,GAAS,MAAM,YAAA,CAAa,GAAG,CAAA;AACrC,IAAA,MAAM,GAAA,GAAM,OAAO,MAAA,KAAW,QAAA,IAAY,WAAW,IAAA,IAAQ,KAAA,IAAS,MAAA,GAAS,MAAA,CAAO,GAAA,GAAM,MAAA;AAC5F,IAAA,MAAM,SAAA,GAAY,MAAM,GAAG,CAAA;AAC3B,IAAA,MAAM,OAAA,GAAU,MAAM,EAAA,CAAG,WAAA,CAAY,CAAC,EAAA,KAAOF,iBAAA,CAAc,EAAA,EAAI,SAAS,CAAC,CAAA;AACzE,IAAA,OAAO,IAAA,CAAK,EAAE,OAAA,EAAQ,EAAG,GAAG,CAAA;AAAA,EAC9B;AACA,EAAA,IAAI,QAAQ,WAAA,EAAa;AACvB,IAAA,IAAI,MAAA,KAAW,MAAA,EAAQ,OAAO,gBAAA,EAAiB;AAC/C,IAAA,MAAM,IAAA,GAAO,MAAM,YAAA,CAAa,GAAA,EAAKG,YAAQ,CAAA;AAC7C,IAAA,OAAO,KAAK,MAAMC,qBAAA,CAAkB,EAAA,EAAI,IAAI,GAAG,GAAG,CAAA;AAAA,EACpD;AACA,EAAA,IAAI,GAAA,CAAI,UAAA,CAAW,MAAM,CAAA,EAAG,OAAO,YAAY,EAAA,EAAI,MAAA,EAAQ,GAAA,EAAK,GAAA,EAAK,GAAG,CAAA;AACxE,EAAA,OAAO,QAAA,EAAS;AAClB;AAIA,SAAS,WAAW,cAAA,EAAwD;AAC1E,EAAA,IAAI,CAAC,gBAAgB,OAAO,MAAA;AAC5B,EAAA,IAAI;AACF,IAAA,OAAOC,gBAAa,cAAc,CAAA;AAAA,EACpC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,MAAA;AAAA,EACT;AACF;AAEO,SAAS,aAAA,CACd,IACA,OAAA,EACyC;AACzC,EAAA,MAAM,QAAA,GAAW,UAAA,CAAW,OAAA,CAAQ,cAAc,CAAA;AAClD,EAAA,OAAO,OAAO,OAAA,KAAwC;AACpD,IAAA,IAAI;AACF,MAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,OAAA,CAAQ,GAAG,CAAA;AAC/B,MAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,MAAA,KAAW,MAAA,GAAS,QAAQ,OAAA,CAAQ,MAAA;AAC3D,MAAA,MAAM,OAAO,GAAA,CAAI,QAAA;AACjB,MAAA,MAAM,QAAA,GAAW,IAAA,KAAS,MAAA,IAAU,IAAA,CAAK,UAAA,CAAW,OAAO,CAAA,GACvD,EAAA,GACE,MAAM,SAAA,CAAU,EAAA,EAAI,OAAA,EAAS,MAAA,EAAQ,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,MAAM,CAAA,IAAK,GAAA,EAAK,GAAG,CAAA,GAC1E,QAAA,EAAS,GACX,MAAM,cAAA,CAAe,QAAA,EAAU,MAAA,EAAQ,IAAI,CAAA;AAC/C,MAAA,OAAO,QAAA;AAAA,IACT,SAAS,CAAA,EAAG;AACV,MAAA,IAAI,CAAA,YAAa,SAAA,IAAa,CAAA,YAAaC,gBAAA,EAAc;AACvD,QAAA,MAAM,SAAS,CAAA,CAAE,MAAA;AACjB,QAAA,IAAI,UAAU,GAAA,EAAK,OAAA,CAAQ,KAAA,CAAM,gBAAA,EAAkB,EAAE,OAAO,CAAA;AAC5D,QAAA,MAAM,OAAyC,EAAE,KAAA,EAAO,UAAU,GAAA,GAAM,gBAAA,GAAmB,EAAE,OAAA,EAAQ;AACrG,QAAA,IAAI,CAAA,CAAE,IAAA,EAAM,IAAA,CAAK,IAAA,GAAO,CAAA,CAAE,IAAA;AAC1B,QAAA,OAAO,IAAA,CAAK,MAAM,MAAM,CAAA;AAAA,MAC1B;AACA,MAAA,OAAA,CAAQ,KAAA,CAAM,kBAAkB,CAAC,CAAA;AACjC,MAAA,OAAO,IAAA,CAAK,EAAE,KAAA,EAAO,gBAAA,IAAoB,GAAG,CAAA;AAAA,IAC9C;AAAA,EACF,CAAA;AACF","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 };
|