@sfmc-bds/db-server 0.2.0-beta.0 → 0.2.0-beta.10

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 (155) hide show
  1. package/LICENSE +663 -0
  2. package/dist/domain/bridge.d.ts +57 -23
  3. package/dist/domain/bridge.d.ts.map +1 -1
  4. package/dist/domain/bridge.js +77 -6
  5. package/dist/domain/bridge.js.map +2 -2
  6. package/dist/domain/bridge.test.d.ts +2 -0
  7. package/dist/domain/bridge.test.d.ts.map +1 -0
  8. package/dist/domain/bridge.test.js +87 -0
  9. package/dist/domain/bridge.test.js.map +7 -0
  10. package/dist/domain/economy.d.ts +38 -7
  11. package/dist/domain/economy.d.ts.map +1 -1
  12. package/dist/domain/economy.js.map +2 -2
  13. package/dist/domain/messages-bridge-filter.test.d.ts +5 -0
  14. package/dist/domain/messages-bridge-filter.test.d.ts.map +1 -0
  15. package/dist/domain/messages-bridge-filter.test.js +20 -0
  16. package/dist/domain/messages-bridge-filter.test.js.map +7 -0
  17. package/dist/domain/qq-bind-status.test.d.ts +2 -0
  18. package/dist/domain/qq-bind-status.test.d.ts.map +1 -0
  19. package/dist/domain/qq-bind-status.test.js +211 -0
  20. package/dist/domain/qq-bind-status.test.js.map +7 -0
  21. package/dist/domain/qq-events.d.ts +84 -0
  22. package/dist/domain/qq-events.d.ts.map +1 -0
  23. package/dist/domain/qq-events.js +193 -0
  24. package/dist/domain/qq-events.js.map +7 -0
  25. package/dist/domain/qq-events.test.d.ts +5 -0
  26. package/dist/domain/qq-events.test.d.ts.map +1 -0
  27. package/dist/domain/qq-events.test.js +170 -0
  28. package/dist/domain/qq-events.test.js.map +7 -0
  29. package/dist/domain/qq-join.test.d.ts +2 -0
  30. package/dist/domain/qq-join.test.d.ts.map +1 -0
  31. package/dist/domain/qq-join.test.js +353 -0
  32. package/dist/domain/qq-join.test.js.map +7 -0
  33. package/dist/domain/schema.d.ts +15 -18
  34. package/dist/domain/schema.d.ts.map +1 -1
  35. package/dist/domain/schema.js +67 -451
  36. package/dist/domain/schema.js.map +2 -2
  37. package/dist/domain/system-status.d.ts +75 -0
  38. package/dist/domain/system-status.d.ts.map +1 -0
  39. package/dist/domain/system-status.js +181 -0
  40. package/dist/domain/system-status.js.map +7 -0
  41. package/dist/domain/system-status.test.d.ts +5 -0
  42. package/dist/domain/system-status.test.d.ts.map +1 -0
  43. package/dist/domain/system-status.test.js +19 -0
  44. package/dist/domain/system-status.test.js.map +7 -0
  45. package/dist/domain/transaction.d.ts +5 -4
  46. package/dist/domain/transaction.d.ts.map +1 -1
  47. package/dist/domain/transaction.js.map +2 -2
  48. package/dist/env.d.ts +7 -0
  49. package/dist/env.d.ts.map +1 -1
  50. package/dist/env.js +57 -9
  51. package/dist/env.js.map +2 -2
  52. package/dist/index.d.ts +0 -1
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +105 -19
  55. package/dist/index.js.map +2 -2
  56. package/dist/lib/idempotency-store.d.ts +17 -11
  57. package/dist/lib/idempotency-store.d.ts.map +1 -1
  58. package/dist/lib/idempotency-store.js.map +2 -2
  59. package/dist/lib/idempotency.d.ts +3 -4
  60. package/dist/lib/idempotency.d.ts.map +1 -1
  61. package/dist/lib/idempotency.js.map +2 -2
  62. package/dist/lib/log.d.ts.map +1 -1
  63. package/dist/lib/log.js +3 -6
  64. package/dist/lib/log.js.map +2 -2
  65. package/dist/lib/module-state.d.ts +38 -8
  66. package/dist/lib/module-state.d.ts.map +1 -1
  67. package/dist/lib/module-state.js.map +2 -2
  68. package/dist/lib/order-by.d.ts +8 -6
  69. package/dist/lib/order-by.d.ts.map +1 -1
  70. package/dist/lib/order-by.js.map +2 -2
  71. package/dist/lib/sql-helpers.d.ts +25 -13
  72. package/dist/lib/sql-helpers.d.ts.map +1 -1
  73. package/dist/lib/sql-helpers.js.map +2 -2
  74. package/dist/lib/sqlite.d.ts +14 -5
  75. package/dist/lib/sqlite.d.ts.map +1 -1
  76. package/dist/lib/sqlite.js.map +2 -2
  77. package/dist/manifest-loader.d.ts +5 -2
  78. package/dist/manifest-loader.d.ts.map +1 -1
  79. package/dist/manifest-loader.js +16 -6
  80. package/dist/manifest-loader.js.map +2 -2
  81. package/dist/manifest.d.ts +1 -2
  82. package/dist/manifest.d.ts.map +1 -1
  83. package/dist/manifest.js +13 -8
  84. package/dist/manifest.js.map +2 -2
  85. package/dist/module-auth.d.ts.map +1 -1
  86. package/dist/module-auth.js +3 -7
  87. package/dist/module-auth.js.map +2 -2
  88. package/dist/permission-gate.d.ts.map +1 -1
  89. package/dist/permission-gate.js +8 -2
  90. package/dist/permission-gate.js.map +2 -2
  91. package/dist/project-root.d.ts +6 -0
  92. package/dist/project-root.d.ts.map +1 -0
  93. package/dist/project-root.js +9 -0
  94. package/dist/project-root.js.map +7 -0
  95. package/dist/routes/_shared.d.ts +14 -5
  96. package/dist/routes/_shared.d.ts.map +1 -1
  97. package/dist/routes/_shared.js.map +2 -2
  98. package/dist/routes/config.d.ts +8 -11
  99. package/dist/routes/config.d.ts.map +1 -1
  100. package/dist/routes/config.js +17 -23
  101. package/dist/routes/config.js.map +2 -2
  102. package/dist/routes/db-routes.d.ts +8 -2
  103. package/dist/routes/db-routes.d.ts.map +1 -1
  104. package/dist/routes/db-routes.js +5 -12
  105. package/dist/routes/db-routes.js.map +2 -2
  106. package/dist/routes/messages.d.ts +6 -2
  107. package/dist/routes/messages.d.ts.map +1 -1
  108. package/dist/routes/messages.js +17 -3
  109. package/dist/routes/messages.js.map +2 -2
  110. package/dist/routes/module-config-routes.d.ts +13 -9
  111. package/dist/routes/module-config-routes.d.ts.map +1 -1
  112. package/dist/routes/module-config-routes.js.map +2 -2
  113. package/dist/routes/modules.d.ts +6 -0
  114. package/dist/routes/modules.d.ts.map +1 -1
  115. package/dist/routes/modules.js.map +2 -2
  116. package/dist/routes/qq-bind.d.ts +24 -0
  117. package/dist/routes/qq-bind.d.ts.map +1 -0
  118. package/dist/routes/qq-bind.js +179 -0
  119. package/dist/routes/qq-bind.js.map +7 -0
  120. package/dist/routes/qq-events.d.ts +20 -0
  121. package/dist/routes/qq-events.d.ts.map +1 -0
  122. package/dist/routes/qq-events.js +39 -0
  123. package/dist/routes/qq-events.js.map +7 -0
  124. package/dist/routes/qq-join.d.ts +46 -0
  125. package/dist/routes/qq-join.d.ts.map +1 -0
  126. package/dist/routes/qq-join.js +277 -0
  127. package/dist/routes/qq-join.js.map +7 -0
  128. package/dist/routes/service-routes.d.ts +6 -0
  129. package/dist/routes/service-routes.d.ts.map +1 -1
  130. package/dist/routes/service-routes.js.map +2 -2
  131. package/dist/routes/status.d.ts +19 -0
  132. package/dist/routes/status.d.ts.map +1 -0
  133. package/dist/routes/status.js +74 -0
  134. package/dist/routes/status.js.map +7 -0
  135. package/dist/runtime.test.js +216 -4
  136. package/dist/runtime.test.js.map +2 -2
  137. package/dist/schema-registry.d.ts +17 -16
  138. package/dist/schema-registry.d.ts.map +1 -1
  139. package/dist/schema-registry.js +76 -6
  140. package/dist/schema-registry.js.map +2 -2
  141. package/dist/server.d.ts +19 -0
  142. package/dist/server.d.ts.map +1 -1
  143. package/dist/server.js.map +2 -2
  144. package/dist/service-registry.d.ts +39 -9
  145. package/dist/service-registry.d.ts.map +1 -1
  146. package/dist/service-registry.js +30 -2
  147. package/dist/service-registry.js.map +2 -2
  148. package/dist/services/builtin-handlers.d.ts +27 -7
  149. package/dist/services/builtin-handlers.d.ts.map +1 -1
  150. package/dist/services/builtin-handlers.js.map +2 -2
  151. package/dist/tx-runner.d.ts +48 -9
  152. package/dist/tx-runner.d.ts.map +1 -1
  153. package/dist/tx-runner.js +99 -27
  154. package/dist/tx-runner.js.map +2 -2
  155. package/package.json +26 -24
@@ -107,9 +107,7 @@ test("jsonV2Ok: \u4E0E Fail \u5BF9\u79F0 ok \u65B9\u8A00(LSP/DRY)", async () =>
107
107
  test("normalizeOrderBy: SDK field \u4E0E\u9057\u7559 col / \u6570\u7EC4\u4E92\u901A(LSP)", async () => {
108
108
  const { normalizeOrderBy } = await import("./lib/order-by.js");
109
109
  deepEqual(normalizeOrderBy(void 0), []);
110
- deepEqual(normalizeOrderBy({ field: "created_at", dir: "desc" }), [
111
- { col: "created_at", dir: "desc" }
112
- ]);
110
+ deepEqual(normalizeOrderBy({ field: "created_at", dir: "desc" }), [{ col: "created_at", dir: "desc" }]);
113
111
  deepEqual(normalizeOrderBy({ col: "id" }), [{ col: "id", dir: "asc" }]);
114
112
  deepEqual(normalizeOrderBy([{ field: "a" }, { col: "b", dir: "desc" }]), [
115
113
  { col: "a", dir: "asc" },
@@ -236,7 +234,7 @@ test("TxRunner \u4EA4\u4E92\u4F1A\u8BDD: step \u4E2D\u9014\u8BFB\u56DE insert/ge
236
234
  serviceRegistry: new ServiceRegistry(),
237
235
  enabled
238
236
  });
239
- const begin = runner.beginSession("feature-demo");
237
+ const begin = await runner.beginSession("feature-demo");
240
238
  equal(begin.ok, true);
241
239
  if (!begin.ok) return;
242
240
  const { txId } = begin;
@@ -262,4 +260,218 @@ test("TxRunner \u4EA4\u4E92\u4F1A\u8BDD: step \u4E2D\u9014\u8BFB\u56DE insert/ge
262
260
  equal(rows[0]?.name, "alpha");
263
261
  db.close();
264
262
  });
263
+ test("TxRunner \u4EA4\u4E92\u4F1A\u8BDD: \u5E76\u53D1 beginSession \u81EA\u52A8\u6392\u961F\u4E92\u65A5\u6267\u884C", async () => {
264
+ const { DatabaseSync } = await import("node:sqlite");
265
+ const { createQuery } = await import("./lib/sqlite.js");
266
+ const { SchemaRegistry } = await import("./schema-registry.js");
267
+ const { TxRunner } = await import("./tx-runner.js");
268
+ const db = new DatabaseSync(":memory:");
269
+ const query = createQuery(db);
270
+ const schema = new SchemaRegistry(db);
271
+ schema.define("feature-demo", {
272
+ name: "queue_items",
273
+ columns: {
274
+ id: { type: "text", primary: true },
275
+ val: { type: "text", notNull: true }
276
+ },
277
+ softDelete: false
278
+ });
279
+ const enabled = /* @__PURE__ */ new Map([
280
+ [
281
+ "feature-demo",
282
+ {
283
+ id: "feature-demo",
284
+ version: "1.0.0",
285
+ permissions: ["db:read:queue_items", "db:write:queue_items"],
286
+ services: { provides: [], requires: [] },
287
+ db: { tables: [] },
288
+ config: { key: "demo" }
289
+ }
290
+ ]
291
+ ]);
292
+ const runner = new TxRunner({
293
+ db,
294
+ query,
295
+ schema,
296
+ serviceRegistry: new ServiceRegistry(),
297
+ enabled
298
+ });
299
+ const s1 = await runner.beginSession("feature-demo");
300
+ equal(s1.ok, true);
301
+ if (!s1.ok) return;
302
+ let s2Started = false;
303
+ const s2Promise = runner.beginSession("feature-demo").then((res) => {
304
+ s2Started = true;
305
+ return res;
306
+ });
307
+ equal(s2Started, false);
308
+ const s1Step = await runner.stepSession(s1.txId, "feature-demo", {
309
+ op: "insert",
310
+ table: "queue_items",
311
+ row: { id: "1", val: "one" }
312
+ });
313
+ equal(s1Step.ok, true);
314
+ const s1Commit = runner.commitSession(s1.txId, "feature-demo");
315
+ equal(s1Commit.ok, true);
316
+ const s2 = await s2Promise;
317
+ equal(s2.ok, true);
318
+ equal(s2Started, true);
319
+ if (!s2.ok) return;
320
+ const s2Step = await runner.stepSession(s2.txId, "feature-demo", {
321
+ op: "insert",
322
+ table: "queue_items",
323
+ row: { id: "2", val: "two" }
324
+ });
325
+ equal(s2Step.ok, true);
326
+ const s2Commit = runner.commitSession(s2.txId, "feature-demo");
327
+ equal(s2Commit.ok, true);
328
+ const rows = db.prepare("SELECT val FROM queue_items ORDER BY id ASC").all();
329
+ equal(rows.length, 2);
330
+ equal(rows[0]?.val, "one");
331
+ equal(rows[1]?.val, "two");
332
+ db.close();
333
+ });
334
+ test("TxRunner \u5355\u8FDE\u63A5: run \u4E0E\u4EA4\u4E92\u4F1A\u8BDD\u5171\u7528\u4E8B\u52A1\u961F\u5217", async () => {
335
+ const { DatabaseSync } = await import("node:sqlite");
336
+ const { createQuery } = await import("./lib/sqlite.js");
337
+ const { SchemaRegistry } = await import("./schema-registry.js");
338
+ const { TxRunner } = await import("./tx-runner.js");
339
+ const db = new DatabaseSync(":memory:");
340
+ const query = createQuery(db);
341
+ const schema = new SchemaRegistry(db);
342
+ schema.define("feature-demo", {
343
+ name: "queue_items",
344
+ columns: {
345
+ id: { type: "text", primary: true },
346
+ val: { type: "text", notNull: true }
347
+ },
348
+ softDelete: false
349
+ });
350
+ const enabled = /* @__PURE__ */ new Map([
351
+ [
352
+ "feature-demo",
353
+ {
354
+ id: "feature-demo",
355
+ version: "1.0.0",
356
+ permissions: ["db:read:queue_items", "db:write:queue_items"],
357
+ services: { provides: [], requires: [] },
358
+ db: { tables: [] },
359
+ config: { key: "demo" }
360
+ }
361
+ ]
362
+ ]);
363
+ const runner = new TxRunner({
364
+ db,
365
+ query,
366
+ schema,
367
+ serviceRegistry: new ServiceRegistry(),
368
+ enabled
369
+ });
370
+ const session = await runner.beginSession("feature-demo");
371
+ equal(session.ok, true);
372
+ if (!session.ok) return;
373
+ let runFinished = false;
374
+ const queuedRun = runner.run({
375
+ moduleId: "feature-demo",
376
+ steps: [{ op: "query", table: "queue_items" }]
377
+ }).then((result) => {
378
+ runFinished = true;
379
+ return result;
380
+ });
381
+ equal(runFinished, false);
382
+ equal(runner.commitSession(session.txId, "feature-demo").ok, true);
383
+ equal((await queuedRun).ok, true);
384
+ equal(runFinished, true);
385
+ const running = runner.run({
386
+ moduleId: "feature-demo",
387
+ steps: [{ op: "query", table: "queue_items" }]
388
+ });
389
+ let sessionStarted = false;
390
+ const queuedSession = runner.beginSession("feature-demo").then((result) => {
391
+ sessionStarted = true;
392
+ return result;
393
+ });
394
+ equal(sessionStarted, false);
395
+ equal((await running).ok, true);
396
+ const nextSession = await queuedSession;
397
+ equal(nextSession.ok, true);
398
+ equal(sessionStarted, true);
399
+ if (nextSession.ok) equal(runner.rollbackSession(nextSession.txId, "feature-demo").ok, true);
400
+ db.close();
401
+ });
402
+ test("TxRunner \u4F1A\u56DE\u6536\u7A7A\u95F2\u4EA4\u4E92\u4E8B\u52A1\u5E76\u91CA\u653E\u7B49\u5F85\u8005", async () => {
403
+ const { DatabaseSync } = await import("node:sqlite");
404
+ const { createQuery } = await import("./lib/sqlite.js");
405
+ const { SchemaRegistry } = await import("./schema-registry.js");
406
+ const { TxRunner } = await import("./tx-runner.js");
407
+ const db = new DatabaseSync(":memory:");
408
+ const schema = new SchemaRegistry(db);
409
+ const enabled = /* @__PURE__ */ new Map([
410
+ [
411
+ "feature-demo",
412
+ {
413
+ id: "feature-demo",
414
+ version: "1.0.0",
415
+ permissions: [],
416
+ services: { provides: [], requires: [] },
417
+ db: { tables: [] },
418
+ config: { key: "demo" }
419
+ }
420
+ ]
421
+ ]);
422
+ const runner = new TxRunner(
423
+ {
424
+ db,
425
+ query: createQuery(db),
426
+ schema,
427
+ serviceRegistry: new ServiceRegistry(),
428
+ enabled
429
+ },
430
+ { sessionIdleTimeoutMs: 20, slotWaitTimeoutMs: 200 }
431
+ );
432
+ const orphan = await runner.beginSession("feature-demo");
433
+ equal(orphan.ok, true);
434
+ const recovered = await runner.beginSession("feature-demo");
435
+ equal(recovered.ok, true);
436
+ if (recovered.ok) equal(runner.rollbackSession(recovered.txId, "feature-demo").ok, true);
437
+ db.close();
438
+ });
439
+ test("TxRunner \u4E8B\u52A1\u69FD\u7B49\u5F85\u8D85\u65F6\u8FD4\u56DE transaction_busy", async () => {
440
+ const { DatabaseSync } = await import("node:sqlite");
441
+ const { createQuery } = await import("./lib/sqlite.js");
442
+ const { SchemaRegistry } = await import("./schema-registry.js");
443
+ const { TxRunner } = await import("./tx-runner.js");
444
+ const db = new DatabaseSync(":memory:");
445
+ const schema = new SchemaRegistry(db);
446
+ const enabled = /* @__PURE__ */ new Map([
447
+ [
448
+ "feature-demo",
449
+ {
450
+ id: "feature-demo",
451
+ version: "1.0.0",
452
+ permissions: [],
453
+ services: { provides: [], requires: [] },
454
+ db: { tables: [] },
455
+ config: { key: "demo" }
456
+ }
457
+ ]
458
+ ]);
459
+ const runner = new TxRunner(
460
+ {
461
+ db,
462
+ query: createQuery(db),
463
+ schema,
464
+ serviceRegistry: new ServiceRegistry(),
465
+ enabled
466
+ },
467
+ { sessionIdleTimeoutMs: 200, slotWaitTimeoutMs: 20 }
468
+ );
469
+ const owner = await runner.beginSession("feature-demo");
470
+ equal(owner.ok, true);
471
+ const blocked = await runner.beginSession("feature-demo");
472
+ equal(blocked.ok, false);
473
+ if (!blocked.ok) equal(blocked.code, "transaction_busy");
474
+ if (owner.ok) equal(runner.rollbackSession(owner.txId, "feature-demo").ok, true);
475
+ db.close();
476
+ });
265
477
  //# sourceMappingURL=runtime.test.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/runtime.test.ts"],
4
- "sourcesContent": ["import { deepEqual, equal, rejects, throws } from \"node:assert/strict\";\nimport test from \"node:test\";\nimport { SQL } from \"sql-template-strings\";\nimport { assertIdentifier, quoteIdentifier } from \"./lib/identifiers.js\";\nimport { parseNodeVersion } from \"./lib/runtime.js\";\nimport { raw, sql } from \"./lib/sql-helpers.js\";\nimport { DispatchError, ServiceRegistry } from \"./service-registry.js\";\n\ntest(\"parses Node versions\", () => {\n deepEqual(parseNodeVersion(\"22.5.1\"), { major: 22, minor: 5, patch: 1 });\n deepEqual(parseNodeVersion(\"v22.5.1\"), { major: 22, minor: 5, patch: 1 });\n});\n\ntest(\"accepts only safe SQL identifiers\", () => {\n equal(quoteIdentifier(\"sfmc_players\", \"table\"), '\"sfmc_players\"');\n throws(() => assertIdentifier(\"users; DROP TABLE x\"), /Invalid SQL/);\n});\n\ntest(\"sql() embeds trusted identifiers and binds values\", () => {\n const TABLE = \"sfmc_economy_accounts\";\n const q = sql(`SELECT * FROM ${TABLE} WHERE player_id = ?`, [\"p1\"]);\n equal(q.sql, \"SELECT * FROM sfmc_economy_accounts WHERE player_id = ?\");\n deepEqual(q.values, [\"p1\"]);\n});\n\ntest(\"raw() only works via append \u2014 template interpolation still becomes ?\", () => {\n const TABLE = \"sfmc_economy_accounts\";\n const broken = SQL`SELECT * FROM ${raw(TABLE)} WHERE id = ${\"p1\"}`;\n equal(broken.sql, \"SELECT * FROM ? WHERE id = ?\");\n\n const ok = SQL`SELECT * FROM `.append(raw(TABLE)).append(SQL` WHERE id = ${\"p1\"}`);\n equal(ok.sql, \"SELECT * FROM sfmc_economy_accounts WHERE id = ?\");\n deepEqual(ok.values, [\"p1\"]);\n});\n\ntest(\"ServiceRegistry: provider self-call skips requires (LSP with tx.call path)\", async () => {\n const reg = new ServiceRegistry();\n reg.registerHandler(\"feature-economy\", \"economy.stats.monthly\", async () => ({ ok: true }));\n\n const enabled = new Map([\n [\n \"feature-economy\",\n {\n id: \"feature-economy\",\n version: \"1.0.0\",\n permissions: [] as string[],\n services: { provides: [{ name: \"economy.stats.monthly\" }], requires: [] as Array<{ name: string }> },\n db: { tables: [] as unknown[] },\n config: { key: \"economy\" },\n } as unknown as import(\"./manifest-loader.js\").ModuleManifestV2,\n ],\n ]);\n\n const out = await reg.dispatch(enabled, \"feature-economy\", \"economy.stats.monthly\", {});\n deepEqual(out, { ok: true, result: { ok: true } });\n});\n\ntest(\"ServiceRegistry: missing service \u2192 no_such_service (not not_in_requires)\", async () => {\n const reg = new ServiceRegistry();\n const enabled = new Map([\n [\n \"feature-chat\",\n {\n id: \"feature-chat\",\n version: \"1.0.0\",\n permissions: [] as string[],\n services: { provides: [], requires: [] },\n db: { tables: [] },\n config: { key: \"chat\" },\n } as unknown as import(\"./manifest-loader.js\").ModuleManifestV2,\n ],\n ]);\n\n await rejects(\n () => reg.dispatch(enabled, \"feature-chat\", \"economy.account.get\", {}),\n (e: unknown) => e instanceof DispatchError && e.code === \"no_such_service\"\n );\n});\n\ntest(\"jsonV2Fail: ok \u65B9\u8A00 + extra(step) \u5408\u5E76(LSP/DRY)\", async () => {\n const { jsonV2Fail } = await import(\"./routes/_shared.js\");\n const chunks: Buffer[] = [];\n let statusCode = 0;\n const res = {\n writeHead(code: number) {\n statusCode = code;\n },\n end(body?: string) {\n if (body) chunks.push(Buffer.from(body));\n },\n setHeader() {},\n } as unknown as import(\"node:http\").ServerResponse;\n\n jsonV2Fail(res, \"boom\", 400, \"bad_step\", { step: 2 });\n const payload = JSON.parse(Buffer.concat(chunks).toString(\"utf8\"));\n equal(statusCode, 400);\n deepEqual(payload, { ok: false, error: \"boom\", code: \"bad_step\", step: 2 });\n});\n\ntest(\"jsonV2Ok: \u4E0E Fail \u5BF9\u79F0 ok \u65B9\u8A00(LSP/DRY)\", async () => {\n const { jsonV2Ok } = await import(\"./routes/_shared.js\");\n const chunks: Buffer[] = [];\n let statusCode = 0;\n const res = {\n writeHead(code: number) {\n statusCode = code;\n },\n end(body?: string) {\n if (body) chunks.push(Buffer.from(body));\n },\n setHeader() {},\n } as unknown as import(\"node:http\").ServerResponse;\n\n jsonV2Ok(res, { rows: [1] });\n const payload = JSON.parse(Buffer.concat(chunks).toString(\"utf8\"));\n equal(statusCode, 200);\n deepEqual(payload, { ok: true, rows: [1] });\n});\n\ntest(\"normalizeOrderBy: SDK field \u4E0E\u9057\u7559 col / \u6570\u7EC4\u4E92\u901A(LSP)\", async () => {\n const { normalizeOrderBy } = await import(\"./lib/order-by.js\");\n deepEqual(normalizeOrderBy(undefined), []);\n deepEqual(normalizeOrderBy({ field: \"created_at\", dir: \"desc\" }), [\n { col: \"created_at\", dir: \"desc\" },\n ]);\n deepEqual(normalizeOrderBy({ col: \"id\" }), [{ col: \"id\", dir: \"asc\" }]);\n deepEqual(normalizeOrderBy([{ field: \"a\" }, { col: \"b\", dir: \"desc\" }]), [\n { col: \"a\", dir: \"asc\" },\n { col: \"b\", dir: \"desc\" },\n ]);\n throws(() => normalizeOrderBy({ dir: \"asc\" }), /field\\/col/);\n});\n\ntest(\"syncModuleRuntimeState: enable/disable \u70ED\u66F4\u65B0 token+enabledSet(DIP)\", async () => {\n const { mkdtempSync, rmSync, readFileSync } = await import(\"node:fs\");\n const { join } = await import(\"node:path\");\n const { tmpdir } = await import(\"node:os\");\n const { syncModuleRuntimeState } = await import(\"./module-runtime-sync.js\");\n const { deriveToken } = await import(\"./module-auth.js\");\n const { unregisterBuiltinPluginForModule } = await import(\"./services/builtin-handlers.js\");\n\n const root = mkdtempSync(join(tmpdir(), \"sfmc-runtime-sync-\"));\n const dbPath = join(root, \"data\", \"sfmc_data.db\");\n try {\n const enabledSet = new Set<string>([\"feature-a\"]);\n const enabledManifests = new Map();\n const moduleAuth = { tokens: { \"feature-a\": \"old\" } as Record<string, string>, secret: \"test-secret\" };\n const registry = new ServiceRegistry();\n const fakeManifest = {\n id: \"feature-b\",\n version: \"1.0.0\",\n permissions: [] as string[],\n services: { provides: [], requires: [] },\n db: { tables: [] },\n config: { key: \"b\" },\n } as unknown as import(\"./manifest-loader.js\").ModuleManifestV2;\n\n syncModuleRuntimeState({\n moduleId: \"feature-b\",\n enabled: true,\n dbPath,\n envAuthToken: \"fixed-auth\",\n enabledSet,\n enabledManifests,\n loadedManifest: { modules: { \"feature-b\": fakeManifest } },\n moduleAuth,\n serviceRegistry: registry,\n builtinDeps: { query: (() => []) as never, db: {} as never },\n });\n\n equal(enabledSet.has(\"feature-b\"), true);\n equal(enabledManifests.has(\"feature-b\"), true);\n equal(moduleAuth.tokens[\"feature-b\"], deriveToken(\"feature-b\", \"test-secret\"));\n const store = JSON.parse(readFileSync(join(root, \"data\", \"module-tokens.json\"), \"utf8\"));\n equal(store.tokens[\"feature-b\"], moduleAuth.tokens[\"feature-b\"]);\n\n syncModuleRuntimeState({\n moduleId: \"feature-b\",\n enabled: false,\n dbPath,\n envAuthToken: \"fixed-auth\",\n enabledSet,\n enabledManifests,\n loadedManifest: { modules: { \"feature-b\": fakeManifest } },\n moduleAuth,\n serviceRegistry: registry,\n builtinDeps: { query: (() => []) as never, db: {} as never },\n });\n\n equal(enabledSet.has(\"feature-b\"), false);\n equal(enabledManifests.has(\"feature-b\"), false);\n equal(moduleAuth.tokens[\"feature-b\"], undefined);\n equal(unregisterBuiltinPluginForModule(registry, \"feature-b\"), 0);\n } finally {\n rmSync(root, { recursive: true, force: true });\n }\n});\n\ntest(\"module-auth: ensureModuleToken / revoke \u590D\u7528 secret(DRY)\", async () => {\n const { deriveToken, ensureModuleToken, revokeModuleToken } = await import(\"./module-auth.js\");\n const secret = \"test-secret\";\n const auth = { secret, tokens: {} as Record<string, string> };\n equal(ensureModuleToken(auth, \"feature-afk\"), true);\n equal(auth.tokens[\"feature-afk\"], deriveToken(\"feature-afk\", secret));\n equal(ensureModuleToken(auth, \"feature-afk\"), false);\n equal(revokeModuleToken(auth, \"feature-afk\"), true);\n equal(auth.tokens[\"feature-afk\"], undefined);\n equal(revokeModuleToken(auth, \"feature-afk\"), false);\n});\n\ntest(\"builtin-handlers: \u70ED\u7981\u7528\u6309 moduleId \u5378\u8F7D(DRY/OCP)\", async () => {\n const { unregisterBuiltinPluginForModule, registerBuiltinPluginForModule } = await import(\n \"./services/builtin-handlers.js\"\n );\n\n const reg = new ServiceRegistry();\n reg.registerHandler(\"feature-economy\", \"economy.account.get\", async () => ({}));\n reg.registerHandler(\"feature-economy\", \"economy.account.debit\", async () => ({}));\n reg.registerHandler(\"other-mod\", \"other.ping\", async () => ({}));\n equal(unregisterBuiltinPluginForModule(reg, \"feature-economy\"), 2);\n equal(reg.list().length, 1);\n equal(reg.list()[0]?.moduleId, \"other-mod\");\n equal(unregisterBuiltinPluginForModule(reg, \"feature-unknown\"), 0);\n\n // \u5DF2\u6709\u540C moduleId handler \u65F6\u70ED\u542F\u7528\u8DF3\u8FC7\n equal(registerBuiltinPluginForModule(reg, { query: (() => []) as never, db: {} as never }, \"feature-economy\"), true);\n equal(registerBuiltinPluginForModule(reg, { query: (() => []) as never, db: {} as never }, \"feature-economy\"), false);\n});\n\ntest(\"TxRunner \u4EA4\u4E92\u4F1A\u8BDD: step \u4E2D\u9014\u8BFB\u56DE insert/get(PR #31 leftover)\", async () => {\n const { DatabaseSync } = await import(\"node:sqlite\");\n const { createQuery } = await import(\"./lib/sqlite.js\");\n const { SchemaRegistry } = await import(\"./schema-registry.js\");\n const { TxRunner } = await import(\"./tx-runner.js\");\n\n const db = new DatabaseSync(\":memory:\");\n const query = createQuery(db);\n const schema = new SchemaRegistry(db);\n schema.define(\"feature-demo\", {\n name: \"demo_items\",\n columns: {\n id: { type: \"text\", primary: true },\n name: { type: \"text\", notNull: true },\n },\n softDelete: false,\n });\n\n const enabled = new Map([\n [\n \"feature-demo\",\n {\n id: \"feature-demo\",\n version: \"1.0.0\",\n permissions: [\"db:read:demo_items\", \"db:write:demo_items\"],\n services: { provides: [], requires: [] },\n db: { tables: [] },\n config: { key: \"demo\" },\n } as unknown as import(\"./manifest-loader.js\").ModuleManifestV2,\n ],\n ]);\n\n const runner = new TxRunner({\n db,\n query,\n schema,\n serviceRegistry: new ServiceRegistry(),\n enabled,\n });\n\n const begin = runner.beginSession(\"feature-demo\");\n equal(begin.ok, true);\n if (!begin.ok) return;\n const { txId } = begin;\n\n const ins = await runner.stepSession(txId, \"feature-demo\", {\n op: \"insert\",\n table: \"demo_items\",\n row: { id: \"a1\", name: \"alpha\" },\n });\n equal(ins.ok, true);\n if (!ins.ok) return;\n equal((ins.result as { op: string; row: { id: string } }).row.id, \"a1\");\n\n const got = await runner.stepSession(txId, \"feature-demo\", {\n op: \"get\",\n table: \"demo_items\",\n id: \"a1\",\n });\n equal(got.ok, true);\n if (!got.ok) return;\n equal((got.result as { row: { name: string } | null }).row?.name, \"alpha\");\n\n const committed = runner.commitSession(txId, \"feature-demo\");\n equal(committed.ok, true);\n\n const rows = db.prepare(\"SELECT name FROM demo_items WHERE id = ?\").all(\"a1\") as Array<{ name: string }>;\n equal(rows[0]?.name, \"alpha\");\n db.close();\n});\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { deepEqual, equal, rejects, throws } from \"node:assert/strict\";\nimport test from \"node:test\";\nimport { SQL } from \"sql-template-strings\";\nimport { assertIdentifier, quoteIdentifier } from \"./lib/identifiers.js\";\nimport { parseNodeVersion } from \"./lib/runtime.js\";\nimport { raw, sql } from \"./lib/sql-helpers.js\";\nimport { DispatchError, ServiceRegistry } from \"./service-registry.js\";\n\ntest(\"parses Node versions\", () => {\n deepEqual(parseNodeVersion(\"22.5.1\"), { major: 22, minor: 5, patch: 1 });\n deepEqual(parseNodeVersion(\"v22.5.1\"), { major: 22, minor: 5, patch: 1 });\n});\n\ntest(\"accepts only safe SQL identifiers\", () => {\n equal(quoteIdentifier(\"sfmc_players\", \"table\"), '\"sfmc_players\"');\n throws(() => assertIdentifier(\"users; DROP TABLE x\"), /Invalid SQL/);\n});\n\ntest(\"sql() embeds trusted identifiers and binds values\", () => {\n const TABLE = \"sfmc_economy_accounts\";\n const q = sql(`SELECT * FROM ${TABLE} WHERE player_id = ?`, [\"p1\"]);\n equal(q.sql, \"SELECT * FROM sfmc_economy_accounts WHERE player_id = ?\");\n deepEqual(q.values, [\"p1\"]);\n});\n\ntest(\"raw() only works via append \u2014 template interpolation still becomes ?\", () => {\n const TABLE = \"sfmc_economy_accounts\";\n const broken = SQL`SELECT * FROM ${raw(TABLE)} WHERE id = ${\"p1\"}`;\n equal(broken.sql, \"SELECT * FROM ? WHERE id = ?\");\n\n const ok = SQL`SELECT * FROM `.append(raw(TABLE)).append(SQL` WHERE id = ${\"p1\"}`);\n equal(ok.sql, \"SELECT * FROM sfmc_economy_accounts WHERE id = ?\");\n deepEqual(ok.values, [\"p1\"]);\n});\n\ntest(\"ServiceRegistry: provider self-call skips requires (LSP with tx.call path)\", async () => {\n const reg = new ServiceRegistry();\n reg.registerHandler(\"feature-economy\", \"economy.stats.monthly\", async () => ({ ok: true }));\n\n const enabled = new Map([\n [\n \"feature-economy\",\n {\n id: \"feature-economy\",\n version: \"1.0.0\",\n permissions: [] as string[],\n services: { provides: [{ name: \"economy.stats.monthly\" }], requires: [] as Array<{ name: string }> },\n db: { tables: [] as unknown[] },\n config: { key: \"economy\" },\n } as unknown as import(\"./manifest-loader.js\").ModuleManifestV2,\n ],\n ]);\n\n const out = await reg.dispatch(enabled, \"feature-economy\", \"economy.stats.monthly\", {});\n deepEqual(out, { ok: true, result: { ok: true } });\n});\n\ntest(\"ServiceRegistry: missing service \u2192 no_such_service (not not_in_requires)\", async () => {\n const reg = new ServiceRegistry();\n const enabled = new Map([\n [\n \"feature-chat\",\n {\n id: \"feature-chat\",\n version: \"1.0.0\",\n permissions: [] as string[],\n services: { provides: [], requires: [] },\n db: { tables: [] },\n config: { key: \"chat\" },\n } as unknown as import(\"./manifest-loader.js\").ModuleManifestV2,\n ],\n ]);\n\n await rejects(\n () => reg.dispatch(enabled, \"feature-chat\", \"economy.account.get\", {}),\n (e: unknown) => e instanceof DispatchError && e.code === \"no_such_service\"\n );\n});\n\ntest(\"jsonV2Fail: ok \u65B9\u8A00 + extra(step) \u5408\u5E76(LSP/DRY)\", async () => {\n const { jsonV2Fail } = await import(\"./routes/_shared.js\");\n const chunks: Buffer[] = [];\n let statusCode = 0;\n const res = {\n writeHead(code: number) {\n statusCode = code;\n },\n end(body?: string) {\n if (body) chunks.push(Buffer.from(body));\n },\n setHeader() {},\n } as unknown as import(\"node:http\").ServerResponse;\n\n jsonV2Fail(res, \"boom\", 400, \"bad_step\", { step: 2 });\n const payload = JSON.parse(Buffer.concat(chunks).toString(\"utf8\"));\n equal(statusCode, 400);\n deepEqual(payload, { ok: false, error: \"boom\", code: \"bad_step\", step: 2 });\n});\n\ntest(\"jsonV2Ok: \u4E0E Fail \u5BF9\u79F0 ok \u65B9\u8A00(LSP/DRY)\", async () => {\n const { jsonV2Ok } = await import(\"./routes/_shared.js\");\n const chunks: Buffer[] = [];\n let statusCode = 0;\n const res = {\n writeHead(code: number) {\n statusCode = code;\n },\n end(body?: string) {\n if (body) chunks.push(Buffer.from(body));\n },\n setHeader() {},\n } as unknown as import(\"node:http\").ServerResponse;\n\n jsonV2Ok(res, { rows: [1] });\n const payload = JSON.parse(Buffer.concat(chunks).toString(\"utf8\"));\n equal(statusCode, 200);\n deepEqual(payload, { ok: true, rows: [1] });\n});\n\ntest(\"normalizeOrderBy: SDK field \u4E0E\u9057\u7559 col / \u6570\u7EC4\u4E92\u901A(LSP)\", async () => {\n const { normalizeOrderBy } = await import(\"./lib/order-by.js\");\n deepEqual(normalizeOrderBy(undefined), []);\n deepEqual(normalizeOrderBy({ field: \"created_at\", dir: \"desc\" }), [{ col: \"created_at\", dir: \"desc\" }]);\n deepEqual(normalizeOrderBy({ col: \"id\" }), [{ col: \"id\", dir: \"asc\" }]);\n deepEqual(normalizeOrderBy([{ field: \"a\" }, { col: \"b\", dir: \"desc\" }]), [\n { col: \"a\", dir: \"asc\" },\n { col: \"b\", dir: \"desc\" },\n ]);\n throws(() => normalizeOrderBy({ dir: \"asc\" }), /field\\/col/);\n});\n\ntest(\"syncModuleRuntimeState: enable/disable \u70ED\u66F4\u65B0 token+enabledSet(DIP)\", async () => {\n const { mkdtempSync, rmSync, readFileSync } = await import(\"node:fs\");\n const { join } = await import(\"node:path\");\n const { tmpdir } = await import(\"node:os\");\n const { syncModuleRuntimeState } = await import(\"./module-runtime-sync.js\");\n const { deriveToken } = await import(\"./module-auth.js\");\n const { unregisterBuiltinPluginForModule } = await import(\"./services/builtin-handlers.js\");\n\n const root = mkdtempSync(join(tmpdir(), \"sfmc-runtime-sync-\"));\n const dbPath = join(root, \"data\", \"sfmc_data.db\");\n try {\n const enabledSet = new Set<string>([\"feature-a\"]);\n const enabledManifests = new Map();\n const moduleAuth = { tokens: { \"feature-a\": \"old\" } as Record<string, string>, secret: \"test-secret\" };\n const registry = new ServiceRegistry();\n const fakeManifest = {\n id: \"feature-b\",\n version: \"1.0.0\",\n permissions: [] as string[],\n services: { provides: [], requires: [] },\n db: { tables: [] },\n config: { key: \"b\" },\n } as unknown as import(\"./manifest-loader.js\").ModuleManifestV2;\n\n syncModuleRuntimeState({\n moduleId: \"feature-b\",\n enabled: true,\n dbPath,\n envAuthToken: \"fixed-auth\",\n enabledSet,\n enabledManifests,\n loadedManifest: { modules: { \"feature-b\": fakeManifest } },\n moduleAuth,\n serviceRegistry: registry,\n builtinDeps: { query: (() => []) as never, db: {} as never },\n });\n\n equal(enabledSet.has(\"feature-b\"), true);\n equal(enabledManifests.has(\"feature-b\"), true);\n equal(moduleAuth.tokens[\"feature-b\"], deriveToken(\"feature-b\", \"test-secret\"));\n const store = JSON.parse(readFileSync(join(root, \"data\", \"module-tokens.json\"), \"utf8\"));\n equal(store.tokens[\"feature-b\"], moduleAuth.tokens[\"feature-b\"]);\n\n syncModuleRuntimeState({\n moduleId: \"feature-b\",\n enabled: false,\n dbPath,\n envAuthToken: \"fixed-auth\",\n enabledSet,\n enabledManifests,\n loadedManifest: { modules: { \"feature-b\": fakeManifest } },\n moduleAuth,\n serviceRegistry: registry,\n builtinDeps: { query: (() => []) as never, db: {} as never },\n });\n\n equal(enabledSet.has(\"feature-b\"), false);\n equal(enabledManifests.has(\"feature-b\"), false);\n equal(moduleAuth.tokens[\"feature-b\"], undefined);\n equal(unregisterBuiltinPluginForModule(registry, \"feature-b\"), 0);\n } finally {\n rmSync(root, { recursive: true, force: true });\n }\n});\n\ntest(\"module-auth: ensureModuleToken / revoke \u590D\u7528 secret(DRY)\", async () => {\n const { deriveToken, ensureModuleToken, revokeModuleToken } = await import(\"./module-auth.js\");\n const secret = \"test-secret\";\n const auth = { secret, tokens: {} as Record<string, string> };\n equal(ensureModuleToken(auth, \"feature-afk\"), true);\n equal(auth.tokens[\"feature-afk\"], deriveToken(\"feature-afk\", secret));\n equal(ensureModuleToken(auth, \"feature-afk\"), false);\n equal(revokeModuleToken(auth, \"feature-afk\"), true);\n equal(auth.tokens[\"feature-afk\"], undefined);\n equal(revokeModuleToken(auth, \"feature-afk\"), false);\n});\n\ntest(\"builtin-handlers: \u70ED\u7981\u7528\u6309 moduleId \u5378\u8F7D(DRY/OCP)\", async () => {\n const { unregisterBuiltinPluginForModule, registerBuiltinPluginForModule } =\n await import(\"./services/builtin-handlers.js\");\n\n const reg = new ServiceRegistry();\n reg.registerHandler(\"feature-economy\", \"economy.account.get\", async () => ({}));\n reg.registerHandler(\"feature-economy\", \"economy.account.debit\", async () => ({}));\n reg.registerHandler(\"other-mod\", \"other.ping\", async () => ({}));\n equal(unregisterBuiltinPluginForModule(reg, \"feature-economy\"), 2);\n equal(reg.list().length, 1);\n equal(reg.list()[0]?.moduleId, \"other-mod\");\n equal(unregisterBuiltinPluginForModule(reg, \"feature-unknown\"), 0);\n\n // \u5DF2\u6709\u540C moduleId handler \u65F6\u70ED\u542F\u7528\u8DF3\u8FC7\n equal(registerBuiltinPluginForModule(reg, { query: (() => []) as never, db: {} as never }, \"feature-economy\"), true);\n equal(registerBuiltinPluginForModule(reg, { query: (() => []) as never, db: {} as never }, \"feature-economy\"), false);\n});\n\ntest(\"TxRunner \u4EA4\u4E92\u4F1A\u8BDD: step \u4E2D\u9014\u8BFB\u56DE insert/get(PR #31 leftover)\", async () => {\n const { DatabaseSync } = await import(\"node:sqlite\");\n const { createQuery } = await import(\"./lib/sqlite.js\");\n const { SchemaRegistry } = await import(\"./schema-registry.js\");\n const { TxRunner } = await import(\"./tx-runner.js\");\n\n const db = new DatabaseSync(\":memory:\");\n const query = createQuery(db);\n const schema = new SchemaRegistry(db);\n schema.define(\"feature-demo\", {\n name: \"demo_items\",\n columns: {\n id: { type: \"text\", primary: true },\n name: { type: \"text\", notNull: true },\n },\n softDelete: false,\n });\n\n const enabled = new Map([\n [\n \"feature-demo\",\n {\n id: \"feature-demo\",\n version: \"1.0.0\",\n permissions: [\"db:read:demo_items\", \"db:write:demo_items\"],\n services: { provides: [], requires: [] },\n db: { tables: [] },\n config: { key: \"demo\" },\n } as unknown as import(\"./manifest-loader.js\").ModuleManifestV2,\n ],\n ]);\n\n const runner = new TxRunner({\n db,\n query,\n schema,\n serviceRegistry: new ServiceRegistry(),\n enabled,\n });\n\n const begin = await runner.beginSession(\"feature-demo\");\n equal(begin.ok, true);\n if (!begin.ok) return;\n const { txId } = begin;\n\n const ins = await runner.stepSession(txId, \"feature-demo\", {\n op: \"insert\",\n table: \"demo_items\",\n row: { id: \"a1\", name: \"alpha\" },\n });\n equal(ins.ok, true);\n if (!ins.ok) return;\n equal((ins.result as { op: string; row: { id: string } }).row.id, \"a1\");\n\n const got = await runner.stepSession(txId, \"feature-demo\", {\n op: \"get\",\n table: \"demo_items\",\n id: \"a1\",\n });\n equal(got.ok, true);\n if (!got.ok) return;\n equal((got.result as { row: { name: string } | null }).row?.name, \"alpha\");\n\n const committed = runner.commitSession(txId, \"feature-demo\");\n equal(committed.ok, true);\n\n const rows = db.prepare(\"SELECT name FROM demo_items WHERE id = ?\").all(\"a1\") as Array<{ name: string }>;\n equal(rows[0]?.name, \"alpha\");\n db.close();\n});\n\ntest(\"TxRunner \u4EA4\u4E92\u4F1A\u8BDD: \u5E76\u53D1 beginSession \u81EA\u52A8\u6392\u961F\u4E92\u65A5\u6267\u884C\", async () => {\n const { DatabaseSync } = await import(\"node:sqlite\");\n const { createQuery } = await import(\"./lib/sqlite.js\");\n const { SchemaRegistry } = await import(\"./schema-registry.js\");\n const { TxRunner } = await import(\"./tx-runner.js\");\n\n const db = new DatabaseSync(\":memory:\");\n const query = createQuery(db);\n const schema = new SchemaRegistry(db);\n schema.define(\"feature-demo\", {\n name: \"queue_items\",\n columns: {\n id: { type: \"text\", primary: true },\n val: { type: \"text\", notNull: true },\n },\n softDelete: false,\n });\n\n const enabled = new Map([\n [\n \"feature-demo\",\n {\n id: \"feature-demo\",\n version: \"1.0.0\",\n permissions: [\"db:read:queue_items\", \"db:write:queue_items\"],\n services: { provides: [], requires: [] },\n db: { tables: [] },\n config: { key: \"demo\" },\n } as unknown as import(\"./manifest-loader.js\").ModuleManifestV2,\n ],\n ]);\n\n const runner = new TxRunner({\n db,\n query,\n schema,\n serviceRegistry: new ServiceRegistry(),\n enabled,\n });\n\n // Session 1 \u542F\u52A8\n const s1 = await runner.beginSession(\"feature-demo\");\n equal(s1.ok, true);\n if (!s1.ok) return;\n\n // Session 2 \u5E76\u53D1\u542F\u52A8\uFF08\u4F1A\u5F02\u6B65\u6392\u961F\u7B49\u5F85 Session 1 \u5B8C\u6210\uFF09\n let s2Started = false;\n const s2Promise = runner.beginSession(\"feature-demo\").then((res) => {\n s2Started = true;\n return res;\n });\n\n // \u6B64\u65F6 Session 2 \u5E94\u5904\u4E8E\u7B49\u5F85\u4E2D\uFF0C\u672A\u8FDB\u5165\n equal(s2Started, false);\n\n // Session 1 \u63D2\u5165\u6B65\u9AA4\n const s1Step = await runner.stepSession(s1.txId, \"feature-demo\", {\n op: \"insert\",\n table: \"queue_items\",\n row: { id: \"1\", val: \"one\" },\n });\n equal(s1Step.ok, true);\n\n // Session 1 \u63D0\u4EA4\n const s1Commit = runner.commitSession(s1.txId, \"feature-demo\");\n equal(s1Commit.ok, true);\n\n // Session 1 \u63D0\u4EA4\u540E\uFF0CSession 2 \u5E94\u8BE5\u88AB\u5524\u9192\u5E76\u6210\u529F\u83B7\u5F97\u4E8B\u52A1\n const s2 = await s2Promise;\n equal(s2.ok, true);\n equal(s2Started, true);\n if (!s2.ok) return;\n\n // Session 2 \u63D2\u5165\u6B65\u9AA4\u5E76\u63D0\u4EA4\n const s2Step = await runner.stepSession(s2.txId, \"feature-demo\", {\n op: \"insert\",\n table: \"queue_items\",\n row: { id: \"2\", val: \"two\" },\n });\n equal(s2Step.ok, true);\n\n const s2Commit = runner.commitSession(s2.txId, \"feature-demo\");\n equal(s2Commit.ok, true);\n\n const rows = db.prepare(\"SELECT val FROM queue_items ORDER BY id ASC\").all() as Array<{ val: string }>;\n equal(rows.length, 2);\n equal(rows[0]?.val, \"one\");\n equal(rows[1]?.val, \"two\");\n db.close();\n});\n\ntest(\"TxRunner \u5355\u8FDE\u63A5: run \u4E0E\u4EA4\u4E92\u4F1A\u8BDD\u5171\u7528\u4E8B\u52A1\u961F\u5217\", async () => {\n const { DatabaseSync } = await import(\"node:sqlite\");\n const { createQuery } = await import(\"./lib/sqlite.js\");\n const { SchemaRegistry } = await import(\"./schema-registry.js\");\n const { TxRunner } = await import(\"./tx-runner.js\");\n\n const db = new DatabaseSync(\":memory:\");\n const query = createQuery(db);\n const schema = new SchemaRegistry(db);\n schema.define(\"feature-demo\", {\n name: \"queue_items\",\n columns: {\n id: { type: \"text\", primary: true },\n val: { type: \"text\", notNull: true },\n },\n softDelete: false,\n });\n\n const enabled = new Map([\n [\n \"feature-demo\",\n {\n id: \"feature-demo\",\n version: \"1.0.0\",\n permissions: [\"db:read:queue_items\", \"db:write:queue_items\"],\n services: { provides: [], requires: [] },\n db: { tables: [] },\n config: { key: \"demo\" },\n } as unknown as import(\"./manifest-loader.js\").ModuleManifestV2,\n ],\n ]);\n\n const runner = new TxRunner({\n db,\n query,\n schema,\n serviceRegistry: new ServiceRegistry(),\n enabled,\n });\n\n const session = await runner.beginSession(\"feature-demo\");\n equal(session.ok, true);\n if (!session.ok) return;\n\n let runFinished = false;\n const queuedRun = runner\n .run({\n moduleId: \"feature-demo\",\n steps: [{ op: \"query\", table: \"queue_items\" }],\n })\n .then((result) => {\n runFinished = true;\n return result;\n });\n equal(runFinished, false);\n\n equal(runner.commitSession(session.txId, \"feature-demo\").ok, true);\n equal((await queuedRun).ok, true);\n equal(runFinished, true);\n\n const running = runner.run({\n moduleId: \"feature-demo\",\n steps: [{ op: \"query\", table: \"queue_items\" }],\n });\n let sessionStarted = false;\n const queuedSession = runner.beginSession(\"feature-demo\").then((result) => {\n sessionStarted = true;\n return result;\n });\n equal(sessionStarted, false);\n equal((await running).ok, true);\n\n const nextSession = await queuedSession;\n equal(nextSession.ok, true);\n equal(sessionStarted, true);\n if (nextSession.ok) equal(runner.rollbackSession(nextSession.txId, \"feature-demo\").ok, true);\n\n db.close();\n});\n\ntest(\"TxRunner \u4F1A\u56DE\u6536\u7A7A\u95F2\u4EA4\u4E92\u4E8B\u52A1\u5E76\u91CA\u653E\u7B49\u5F85\u8005\", async () => {\n const { DatabaseSync } = await import(\"node:sqlite\");\n const { createQuery } = await import(\"./lib/sqlite.js\");\n const { SchemaRegistry } = await import(\"./schema-registry.js\");\n const { TxRunner } = await import(\"./tx-runner.js\");\n\n const db = new DatabaseSync(\":memory:\");\n const schema = new SchemaRegistry(db);\n const enabled = new Map([\n [\n \"feature-demo\",\n {\n id: \"feature-demo\",\n version: \"1.0.0\",\n permissions: [],\n services: { provides: [], requires: [] },\n db: { tables: [] },\n config: { key: \"demo\" },\n } as unknown as import(\"./manifest-loader.js\").ModuleManifestV2,\n ],\n ]);\n const runner = new TxRunner(\n {\n db,\n query: createQuery(db),\n schema,\n serviceRegistry: new ServiceRegistry(),\n enabled,\n },\n { sessionIdleTimeoutMs: 20, slotWaitTimeoutMs: 200 }\n );\n\n const orphan = await runner.beginSession(\"feature-demo\");\n equal(orphan.ok, true);\n const recovered = await runner.beginSession(\"feature-demo\");\n equal(recovered.ok, true);\n if (recovered.ok) equal(runner.rollbackSession(recovered.txId, \"feature-demo\").ok, true);\n db.close();\n});\n\ntest(\"TxRunner \u4E8B\u52A1\u69FD\u7B49\u5F85\u8D85\u65F6\u8FD4\u56DE transaction_busy\", async () => {\n const { DatabaseSync } = await import(\"node:sqlite\");\n const { createQuery } = await import(\"./lib/sqlite.js\");\n const { SchemaRegistry } = await import(\"./schema-registry.js\");\n const { TxRunner } = await import(\"./tx-runner.js\");\n\n const db = new DatabaseSync(\":memory:\");\n const schema = new SchemaRegistry(db);\n const enabled = new Map([\n [\n \"feature-demo\",\n {\n id: \"feature-demo\",\n version: \"1.0.0\",\n permissions: [],\n services: { provides: [], requires: [] },\n db: { tables: [] },\n config: { key: \"demo\" },\n } as unknown as import(\"./manifest-loader.js\").ModuleManifestV2,\n ],\n ]);\n const runner = new TxRunner(\n {\n db,\n query: createQuery(db),\n schema,\n serviceRegistry: new ServiceRegistry(),\n enabled,\n },\n { sessionIdleTimeoutMs: 200, slotWaitTimeoutMs: 20 }\n );\n\n const owner = await runner.beginSession(\"feature-demo\");\n equal(owner.ok, true);\n const blocked = await runner.beginSession(\"feature-demo\");\n equal(blocked.ok, false);\n if (!blocked.ok) equal(blocked.code, \"transaction_busy\");\n if (owner.ok) equal(runner.rollbackSession(owner.txId, \"feature-demo\").ok, true);\n db.close();\n});\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,23 +1,18 @@
1
1
  /**
2
- * schema-registry.ts — 收集模块 defineTable,统一建表
2
+ * schema-registry.ts — 动态数据表结构注册与创建管理器
3
3
  *
4
- * 协议:
5
- * - 模块 SAPI 启动时调 db.defineTable(name, columns, {softDelete})
6
- * → POST /api/sfmc/db/define-table {moduleId, name, columns, softDelete}
7
- * - schema-registry 收集所有调用,CAS 锁
8
- * - 等所有"已注册模块"都调过(超时 fallback 或 explicit `finalize()`),
9
- * 一次性 CREATE TABLE IF NOT EXISTS + CREATE INDEX IF NOT EXISTS
10
- * - 同一 module 重复 define 同名表 = 幂等(同名 columns 允许)
11
- * - 不同 module 抢同名表 = 启动失败
12
- * - 平台表(moduleId = "__platform__")由 db-tables.ts 在 initSchema 直接 bootstrap
4
+ * 协议与流程:
5
+ * 1. 模块启动时调用 `db.defineTable(name, columns, { softDelete })` 向服务端注册表结构定义。
6
+ * 2. `SchemaRegistry` 校验列定义合法性、外键引用与主键唯一性。
7
+ * 3. 校验通过后自动执行 `CREATE TABLE IF NOT EXISTS` 及索引创建语句。
13
8
  *
14
- * 列类型白名单:
15
- * text / integer / real / blob
16
- * 修饰: primary / notNull / unique / default / index
17
- * softDelete = 自动加 _deleted_at INTEGER(列名带下划线) 和 _version INTEGER
9
+ * 冲突与隔离控制:
10
+ * - 相同模块重复定义同名表:具备幂等性(要求列结构完全一致)
11
+ * - 不同模块定义相同表名:抛出冲突异常拒绝注册
12
+ * - 软删除支持:开启 `softDelete`(默认开启)时自动在末尾统一追加 `_deleted_at` 与乐观锁 `_version`
18
13
  */
19
14
  import type { DatabaseSync } from "node:sqlite";
20
- export type ColumnType = "text" | "integer" | "real" | "blob";
15
+ export type ColumnType = "text" | "integer" | "real" | "blob" | "TEXT" | "INTEGER" | "REAL" | "BLOB";
21
16
  export interface ColumnDef {
22
17
  type: ColumnType;
23
18
  primary?: boolean;
@@ -44,7 +39,13 @@ export declare class SchemaRegistry {
44
39
  private readonly tables;
45
40
  private finalized;
46
41
  constructor(db: DatabaseSync);
47
- /** 模块 SAPI 启动时调。 */
42
+ /**
43
+ * 注册并创建模块数据表结构。
44
+ *
45
+ * @param moduleId 注册模块唯一标识符。
46
+ * @param req 表结构定义请求对象。
47
+ * @returns 建表结果与创建的索引列表。
48
+ */
48
49
  define(moduleId: string, req: DefineTableRequest): DefineTableResult;
49
50
  /** 幂等地物理建表 + 建索引(供 define / finalize 共用)。 */
50
51
  private createPhysical;
@@ -1 +1 @@
1
- {"version":3,"file":"schema-registry.d.ts","sourceRoot":"","sources":["../src/schema-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9D,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;AAEzD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACnC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AASD,eAAO,MAAM,kBAAkB,iBAAiB,CAAC;AA6DjD,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAe;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;IAC1D,OAAO,CAAC,SAAS,CAAS;IAE1B,YAAY,EAAE,EAAE,YAAY,EAE3B;IAED,oBAAoB;IACpB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,kBAAkB,GAAG,iBAAiB,CAsCnE;IAED,6CAA6C;IAC7C,OAAO,CAAC,cAAc;IAWtB;;;OAGG;IACH,QAAQ,IAAI;QAAE,UAAU,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CA0BtD;IAED,UAAU,IAAI,MAAM,EAAE,CAErB;IAED,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElC;IAED,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEzC;CACF"}
1
+ {"version":3,"file":"schema-registry.d.ts","sourceRoot":"","sources":["../src/schema-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,MAAM,MAAM,UAAU,GAClB,MAAM,GACN,SAAS,GACT,MAAM,GACN,MAAM,GACN,MAAM,GACN,SAAS,GACT,MAAM,GACN,MAAM,CAAC;AAEX,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;AAEzD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACnC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AASD,eAAO,MAAM,kBAAkB,iBAAiB,CAAC;AA6EjD,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAe;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;IAC1D,OAAO,CAAC,SAAS,CAAS;IAE1B,YAAY,EAAE,EAAE,YAAY,EAE3B;IAED;;;;;;OAMG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,kBAAkB,GAAG,iBAAiB,CAuCnE;IAED,6CAA6C;IAC7C,OAAO,CAAC,cAAc;IAyEtB;;;OAGG;IACH,QAAQ,IAAI;QAAE,UAAU,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CA0BtD;IAED,UAAU,IAAI,MAAM,EAAE,CAErB;IAED,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElC;IAED,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEzC;CACF"}
@@ -1,7 +1,16 @@
1
1
  import { log } from "./lib/log.js";
2
2
  const PLATFORM_MODULE_ID = "__platform__";
3
3
  const IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;
4
- const VALID_TYPES = /* @__PURE__ */ new Set(["text", "integer", "real", "blob"]);
4
+ const VALID_TYPES = /* @__PURE__ */ new Set([
5
+ "text",
6
+ "integer",
7
+ "real",
8
+ "blob",
9
+ "TEXT",
10
+ "INTEGER",
11
+ "REAL",
12
+ "BLOB"
13
+ ]);
5
14
  function assertValid(req, moduleId) {
6
15
  if (!IDENT.test(req.name)) throw new Error(`[schema] ${moduleId}: invalid table name "${req.name}"`);
7
16
  if (!req.columns || Object.keys(req.columns).length === 0) {
@@ -10,9 +19,11 @@ function assertValid(req, moduleId) {
10
19
  let pks = 0;
11
20
  for (const [colName, def] of Object.entries(req.columns)) {
12
21
  if (!IDENT.test(colName)) throw new Error(`[schema] ${moduleId}: invalid column name "${colName}" in ${req.name}`);
13
- if (!VALID_TYPES.has(def.type)) {
22
+ const rawType = String(def.type || "");
23
+ if (!VALID_TYPES.has(rawType)) {
14
24
  throw new Error(`[schema] ${moduleId}: ${req.name}.${colName} bad type "${def.type}"`);
15
25
  }
26
+ def.type = rawType.toLowerCase();
16
27
  if (def.primary) pks++;
17
28
  if (def.ref) {
18
29
  const [t, c] = def.ref.split(".");
@@ -52,7 +63,13 @@ class SchemaRegistry {
52
63
  constructor(db) {
53
64
  this.db = db;
54
65
  }
55
- /** 模块 SAPI 启动时调。 */
66
+ /**
67
+ * 注册并创建模块数据表结构。
68
+ *
69
+ * @param moduleId 注册模块唯一标识符。
70
+ * @param req 表结构定义请求对象。
71
+ * @returns 建表结果与创建的索引列表。
72
+ */
56
73
  define(moduleId, req) {
57
74
  if (this.finalized) {
58
75
  throw new Error(`[schema] registry \u5DF2 finalize,\u6A21\u5757 ${moduleId} \u4E0D\u80FD\u518D defineTable`);
@@ -87,12 +104,65 @@ class SchemaRegistry {
87
104
  }
88
105
  /** 幂等地物理建表 + 建索引(供 define / finalize 共用)。 */
89
106
  createPhysical(t) {
90
- const cols = buildColumnList(t.columns, t.softDelete);
91
- this.db.exec(`CREATE TABLE IF NOT EXISTS "${t.name}" (${cols.join(", ")})`);
107
+ const existingTableInfo = this.db.prepare(`PRAGMA table_info("${t.name}")`).all();
108
+ if (existingTableInfo.length > 0) {
109
+ let rowCount = 0;
110
+ try {
111
+ const countRes = this.db.prepare(`SELECT COUNT(*) AS c FROM "${t.name}"`).get();
112
+ rowCount = countRes?.c ?? 0;
113
+ } catch {
114
+ rowCount = 0;
115
+ }
116
+ const existingColNames = new Set(existingTableInfo.map((c) => c.name));
117
+ const requiredCols = Object.keys(t.columns);
118
+ const hasMissingRequiredCol = requiredCols.some((c) => !existingColNames.has(c));
119
+ const pkDef = Object.entries(t.columns).find(([_, c]) => c.primary);
120
+ const pkMatches = !pkDef || existingTableInfo.some((c) => c.pk === 1 && c.name === pkDef[0]);
121
+ if (rowCount === 0 && (hasMissingRequiredCol || !pkMatches)) {
122
+ try {
123
+ this.db.exec(`DROP TABLE "${t.name}"`);
124
+ } catch {
125
+ }
126
+ const cols = buildColumnList(t.columns, t.softDelete);
127
+ this.db.exec(`CREATE TABLE "${t.name}" (${cols.join(", ")})`);
128
+ } else {
129
+ for (const [colName, colDef] of Object.entries(t.columns)) {
130
+ if (!existingColNames.has(colName)) {
131
+ const clause = buildColumnClause(colName, colDef);
132
+ try {
133
+ this.db.exec(`ALTER TABLE "${t.name}" ADD COLUMN ${clause};`);
134
+ } catch (e) {
135
+ log.warn(`[schema] ${t.moduleId}: ALTER TABLE "${t.name}" ADD COLUMN "${colName}" \u5F02\u5E38: ${e.message}`);
136
+ }
137
+ }
138
+ }
139
+ if (t.softDelete) {
140
+ if (!existingColNames.has("_deleted_at")) {
141
+ try {
142
+ this.db.exec(`ALTER TABLE "${t.name}" ADD COLUMN "_deleted_at" INTEGER;`);
143
+ } catch {
144
+ }
145
+ }
146
+ if (!existingColNames.has("_version")) {
147
+ try {
148
+ this.db.exec(`ALTER TABLE "${t.name}" ADD COLUMN "_version" INTEGER DEFAULT 0;`);
149
+ } catch {
150
+ }
151
+ }
152
+ }
153
+ }
154
+ } else {
155
+ const cols = buildColumnList(t.columns, t.softDelete);
156
+ this.db.exec(`CREATE TABLE IF NOT EXISTS "${t.name}" (${cols.join(", ")})`);
157
+ }
92
158
  for (const [n, def] of Object.entries(t.columns)) {
93
159
  if (def.index) {
94
160
  const idxName = `idx_${t.name}_${n}`.slice(0, 60);
95
- this.db.exec(`CREATE INDEX IF NOT EXISTS "${idxName}" ON "${t.name}"("${n}")`);
161
+ try {
162
+ this.db.exec(`CREATE INDEX IF NOT EXISTS "${idxName}" ON "${t.name}"("${n}")`);
163
+ } catch (e) {
164
+ log.warn(`[schema] ${t.moduleId}: \u521B\u5EFA\u7D22\u5F15 "${idxName}" \u5931\u8D25: ${e.message}`);
165
+ }
96
166
  }
97
167
  }
98
168
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/schema-registry.ts"],
4
- "sourcesContent": ["/**\n * schema-registry.ts \u2014 \u6536\u96C6\u6A21\u5757 defineTable,\u7EDF\u4E00\u5EFA\u8868\n *\n * \u534F\u8BAE:\n * - \u6A21\u5757 SAPI \u542F\u52A8\u65F6\u8C03 db.defineTable(name, columns, {softDelete})\n * \u2192 POST /api/sfmc/db/define-table {moduleId, name, columns, softDelete}\n * - schema-registry \u6536\u96C6\u6240\u6709\u8C03\u7528,CAS \u9501\n * - \u7B49\u6240\u6709\"\u5DF2\u6CE8\u518C\u6A21\u5757\"\u90FD\u8C03\u8FC7(\u8D85\u65F6 fallback \u6216 explicit `finalize()`),\n * \u4E00\u6B21\u6027 CREATE TABLE IF NOT EXISTS + CREATE INDEX IF NOT EXISTS\n * - \u540C\u4E00 module \u91CD\u590D define \u540C\u540D\u8868 = \u5E42\u7B49(\u540C\u540D columns \u5141\u8BB8)\n * - \u4E0D\u540C module \u62A2\u540C\u540D\u8868 = \u542F\u52A8\u5931\u8D25\n * - \u5E73\u53F0\u8868(moduleId = \"__platform__\")\u7531 db-tables.ts \u5728 initSchema \u76F4\u63A5 bootstrap\n *\n * \u5217\u7C7B\u578B\u767D\u540D\u5355:\n * text / integer / real / blob\n * \u4FEE\u9970: primary / notNull / unique / default / index\n * softDelete = \u81EA\u52A8\u52A0 _deleted_at INTEGER(\u5217\u540D\u5E26\u4E0B\u5212\u7EBF) \u548C _version INTEGER\n */\n\nimport type { DatabaseSync } from \"node:sqlite\";\nimport { log } from \"./lib/log.js\";\n\nexport type ColumnType = \"text\" | \"integer\" | \"real\" | \"blob\";\n\nexport interface ColumnDef {\n type: ColumnType;\n primary?: boolean;\n notNull?: boolean;\n unique?: boolean;\n default?: Primitive;\n index?: boolean;\n ref?: string;\n}\n\nexport type Primitive = string | number | boolean | null;\n\nexport interface DefineTableRequest {\n name: string;\n columns: Record<string, ColumnDef>;\n softDelete?: boolean;\n}\n\nexport interface DefineTableResult {\n table: string;\n created: boolean;\n indices: string[];\n}\n\ninterface DefinedTable {\n moduleId: string;\n name: string;\n columns: Record<string, ColumnDef>;\n softDelete: boolean;\n}\n\nexport const PLATFORM_MODULE_ID = \"__platform__\";\n\nconst IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;\nconst VALID_TYPES: Set<ColumnType> = new Set([\"text\", \"integer\", \"real\", \"blob\"]);\n\nfunction assertValid(req: DefineTableRequest, moduleId: string): void {\n if (!IDENT.test(req.name)) throw new Error(`[schema] ${moduleId}: invalid table name \"${req.name}\"`);\n if (!req.columns || Object.keys(req.columns).length === 0) {\n throw new Error(`[schema] ${moduleId}: ${req.name} has no columns`);\n }\n let pks = 0;\n for (const [colName, def] of Object.entries(req.columns)) {\n if (!IDENT.test(colName)) throw new Error(`[schema] ${moduleId}: invalid column name \"${colName}\" in ${req.name}`);\n if (!VALID_TYPES.has(def.type)) {\n throw new Error(`[schema] ${moduleId}: ${req.name}.${colName} bad type \"${def.type}\"`);\n }\n if (def.primary) pks++;\n if (def.ref) {\n const [t, c] = def.ref.split(\".\");\n if (!t || !c || !IDENT.test(t) || !IDENT.test(c)) {\n throw new Error(`[schema] ${moduleId}: ${req.name}.${colName} invalid ref \"${def.ref}\"`);\n }\n }\n }\n if (pks > 1) throw new Error(`[schema] ${moduleId}: ${req.name} has ${pks} primary keys (max 1)`);\n}\n\n/**\n * \u4E00\u6B21\u6027\u628A columns \u7FFB\u8BD1\u6210 CREATE TABLE\u3002\n * \u4F8B\u5982 {id:{type:\"text\",primary:true},name:{type:\"text\",notNull:true}}\n * \u2192 \"id\" TEXT PRIMARY KEY, \"name\" TEXT NOT NULL\n */\nfunction buildColumnClause(name: string, def: ColumnDef): string {\n const parts: string[] = [`\"${name}\"`, def.type.toUpperCase()];\n if (def.primary) parts.push(\"PRIMARY KEY\");\n if (def.notNull && !def.primary) parts.push(\"NOT NULL\");\n if (def.unique) parts.push(\"UNIQUE\");\n if (def.default !== undefined && def.default !== null) {\n if (typeof def.default === \"string\") parts.push(`DEFAULT '${def.default.replace(/'/g, \"''\")}'`);\n else parts.push(`DEFAULT ${def.default}`);\n }\n if (def.ref) parts.push(`REFERENCES ${def.ref}`);\n return parts.join(\" \");\n}\n\n/**\n * \u628A\u4E00\u5F20\u8868\u7684\u6240\u6709\u5217(\u542B softDelete \u9690\u5F0F\u5217)\u62FC\u6210 CREATE TABLE \u7684\u5217\u5B9A\u4E49\u6570\u7EC4\u3002\n * \u9690\u5F0F _deleted_at / _version \u4F5C\u4E3A\u72EC\u7ACB\u5217\u5728\u672B\u5C3E\u8FFD\u52A0\u300C\u4E00\u6B21\u300D(\u6B64\u524D\u8BEF\u5728\u6BCF\u4E2A\u5217\n * \u5B50\u53E5\u91CC\u5404\u8FFD\u52A0\u4E00\u6B21,\u4E14\u65E0\u9017\u53F7\u5206\u9694,\u5BFC\u81F4 softDelete \u8868\u5EFA\u8868 SQL \u8BED\u6CD5\u9519\u8BEF)\u3002\n */\nfunction buildColumnList(columns: Record<string, ColumnDef>, softDelete: boolean): string[] {\n const cols: string[] = [];\n for (const [n, def] of Object.entries(columns)) {\n cols.push(buildColumnClause(n, def));\n }\n if (softDelete) {\n cols.push('\"_deleted_at\" INTEGER', '\"_version\" INTEGER DEFAULT 0');\n }\n return cols;\n}\n\nexport class SchemaRegistry {\n private readonly db: DatabaseSync;\n private readonly tables = new Map<string, DefinedTable>();\n private finalized = false;\n\n constructor(db: DatabaseSync) {\n this.db = db;\n }\n\n /** \u6A21\u5757 SAPI \u542F\u52A8\u65F6\u8C03\u3002 */\n define(moduleId: string, req: DefineTableRequest): DefineTableResult {\n if (this.finalized) {\n throw new Error(`[schema] registry \u5DF2 finalize,\u6A21\u5757 ${moduleId} \u4E0D\u80FD\u518D defineTable`);\n }\n assertValid(req, moduleId);\n\n const existing = this.tables.get(req.name);\n if (existing) {\n if (existing.moduleId === moduleId) {\n // \u540C\u6A21\u5757\u91CD\u590D\u5B9A\u4E49:\u6821\u9A8C columns \u4E00\u81F4\n if (JSON.stringify(existing.columns) !== JSON.stringify(req.columns)) {\n throw new Error(`[schema] ${moduleId} \u91CD\u590D defineTable \"${req.name}\",columns \u4E0D\u4E00\u81F4`);\n }\n if (existing.softDelete !== (req.softDelete ?? true)) {\n throw new Error(`[schema] ${moduleId} \u91CD\u590D defineTable \"${req.name}\",softDelete \u4E0D\u4E00\u81F4`);\n }\n log.info(`[schema] ${moduleId} \u91CD\u590D define \"${req.name}\" \u8DF3\u8FC7`);\n return { table: req.name, created: false, indices: [] };\n }\n throw new Error(\n `[schema] \u8868 \"${req.name}\" \u5DF2\u88AB\u6A21\u5757 ${existing.moduleId} \u5B9A\u4E49,${moduleId} \u4E0D\u80FD\u62A2\u540C\u540D\u8868`\n );\n }\n\n const softDelete = req.softDelete ?? true;\n const defined: DefinedTable = {\n moduleId,\n name: req.name,\n columns: req.columns,\n softDelete,\n };\n this.tables.set(req.name, defined);\n // \u7ACB\u5373\u7269\u7406\u5EFA\u8868(\u5E42\u7B49 CREATE TABLE IF NOT EXISTS)\u3002\n // \u539F\u8BBE\u8BA1\u662F\u300Cdefine \u6536\u96C6 \u2192 finalize \u7EDF\u4E00\u5EFA\u8868\u300D,\u4F46 finalize() \u4ECE\u672A\u88AB\u8C03\u7528,\n // \u5BFC\u81F4\u6A21\u5757\u8868\u6C38\u8FDC\u4E0D\u4F1A\u843D\u5730\u3001\u540E\u7EED insert/query \u5168\u90E8 no such table\u3002\n // \u91C7\u7528 define \u5373\u5EFA\u8868:\u78B0\u649E\u68C0\u6D4B\u5DF2\u5728\u4E0A\u65B9\u6309\u5185\u5B58\u8868\u5B8C\u6210,\u4E0D\u5F71\u54CD\u5355\u4E00 owner \u4FDD\u8BC1\u3002\n this.createPhysical(defined);\n return { table: req.name, created: true, indices: [] };\n }\n\n /** \u5E42\u7B49\u5730\u7269\u7406\u5EFA\u8868 + \u5EFA\u7D22\u5F15(\u4F9B define / finalize \u5171\u7528)\u3002 */\n private createPhysical(t: DefinedTable): void {\n const cols = buildColumnList(t.columns, t.softDelete);\n this.db.exec(`CREATE TABLE IF NOT EXISTS \"${t.name}\" (${cols.join(\", \")})`);\n for (const [n, def] of Object.entries(t.columns)) {\n if (def.index) {\n const idxName = `idx_${t.name}_${n}`.slice(0, 60);\n this.db.exec(`CREATE INDEX IF NOT EXISTS \"${idxName}\" ON \"${t.name}\"(\"${n}\")`);\n }\n }\n }\n\n /**\n * \u771F\u6B63\u5EFA\u8868\u7684\u65F6\u523B\u3002\u6240\u6709 enabled \u6A21\u5757 init \u5B8C\u6210(\u6216\u660E\u786E\u8C03)\u540E\u518D finalize\u3002\n * CREATE TABLE IF NOT EXISTS + CREATE INDEX IF NOT EXISTS,\u5E42\u7B49\u3002\n */\n finalize(): { tableNames: string[]; created: string[] } {\n if (this.finalized) return { tableNames: [...this.tables.keys()], created: [] };\n this.finalized = true;\n const created: string[] = [];\n\n for (const t of this.tables.values()) {\n const cols = buildColumnList(t.columns, t.softDelete);\n const ddl = `CREATE TABLE IF NOT EXISTS \"${t.name}\" (${cols.join(\", \")})`;\n this.db.exec(ddl);\n\n for (const [n, def] of Object.entries(t.columns)) {\n if (def.index) {\n const idxName = `idx_${t.name}_${n}`.slice(0, 60);\n this.db.exec(`CREATE INDEX IF NOT EXISTS \"${idxName}\" ON \"${t.name}\"(\"${n}\")`);\n }\n }\n\n const exists =\n (this.db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name=?\")\n .get(t.name) as { name?: string } | undefined)?.name === t.name;\n if (exists) created.push(t.name);\n log.success(`[schema] ${t.moduleId} \u2192 ${t.name} (softDelete=${t.softDelete})`);\n }\n\n return { tableNames: [...this.tables.keys()], created };\n }\n\n listTables(): string[] {\n return [...this.tables.keys()];\n }\n\n isRegistered(name: string): boolean {\n return this.tables.has(name);\n }\n\n getOwner(name: string): string | undefined {\n return this.tables.get(name)?.moduleId;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * schema-registry.ts \u2014 \u52A8\u6001\u6570\u636E\u8868\u7ED3\u6784\u6CE8\u518C\u4E0E\u521B\u5EFA\u7BA1\u7406\u5668\n *\n * \u534F\u8BAE\u4E0E\u6D41\u7A0B\uFF1A\n * 1. \u6A21\u5757\u542F\u52A8\u65F6\u8C03\u7528 `db.defineTable(name, columns, { softDelete })` \u5411\u670D\u52A1\u7AEF\u6CE8\u518C\u8868\u7ED3\u6784\u5B9A\u4E49\u3002\n * 2. `SchemaRegistry` \u6821\u9A8C\u5217\u5B9A\u4E49\u5408\u6CD5\u6027\u3001\u5916\u952E\u5F15\u7528\u4E0E\u4E3B\u952E\u552F\u4E00\u6027\u3002\n * 3. \u6821\u9A8C\u901A\u8FC7\u540E\u81EA\u52A8\u6267\u884C `CREATE TABLE IF NOT EXISTS` \u53CA\u7D22\u5F15\u521B\u5EFA\u8BED\u53E5\u3002\n *\n * \u51B2\u7A81\u4E0E\u9694\u79BB\u63A7\u5236\uFF1A\n * - \u76F8\u540C\u6A21\u5757\u91CD\u590D\u5B9A\u4E49\u540C\u540D\u8868\uFF1A\u5177\u5907\u5E42\u7B49\u6027\uFF08\u8981\u6C42\u5217\u7ED3\u6784\u5B8C\u5168\u4E00\u81F4\uFF09\n * - \u4E0D\u540C\u6A21\u5757\u5B9A\u4E49\u76F8\u540C\u8868\u540D\uFF1A\u629B\u51FA\u51B2\u7A81\u5F02\u5E38\u62D2\u7EDD\u6CE8\u518C\n * - \u8F6F\u5220\u9664\u652F\u6301\uFF1A\u5F00\u542F `softDelete`\uFF08\u9ED8\u8BA4\u5F00\u542F\uFF09\u65F6\u81EA\u52A8\u5728\u672B\u5C3E\u7EDF\u4E00\u8FFD\u52A0 `_deleted_at` \u4E0E\u4E50\u89C2\u9501 `_version`\n */\n\nimport type { DatabaseSync } from \"node:sqlite\";\nimport { log } from \"./lib/log.js\";\n\nexport type ColumnType =\n | \"text\"\n | \"integer\"\n | \"real\"\n | \"blob\"\n | \"TEXT\"\n | \"INTEGER\"\n | \"REAL\"\n | \"BLOB\";\n\nexport interface ColumnDef {\n type: ColumnType;\n primary?: boolean;\n notNull?: boolean;\n unique?: boolean;\n default?: Primitive;\n index?: boolean;\n ref?: string;\n}\n\nexport type Primitive = string | number | boolean | null;\n\nexport interface DefineTableRequest {\n name: string;\n columns: Record<string, ColumnDef>;\n softDelete?: boolean;\n}\n\nexport interface DefineTableResult {\n table: string;\n created: boolean;\n indices: string[];\n}\n\ninterface DefinedTable {\n moduleId: string;\n name: string;\n columns: Record<string, ColumnDef>;\n softDelete: boolean;\n}\n\nexport const PLATFORM_MODULE_ID = \"__platform__\";\n\nconst IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;\nconst VALID_TYPES: Set<string> = new Set([\n \"text\",\n \"integer\",\n \"real\",\n \"blob\",\n \"TEXT\",\n \"INTEGER\",\n \"REAL\",\n \"BLOB\",\n]);\n\nfunction assertValid(req: DefineTableRequest, moduleId: string): void {\n if (!IDENT.test(req.name)) throw new Error(`[schema] ${moduleId}: invalid table name \"${req.name}\"`);\n if (!req.columns || Object.keys(req.columns).length === 0) {\n throw new Error(`[schema] ${moduleId}: ${req.name} has no columns`);\n }\n let pks = 0;\n for (const [colName, def] of Object.entries(req.columns)) {\n if (!IDENT.test(colName)) throw new Error(`[schema] ${moduleId}: invalid column name \"${colName}\" in ${req.name}`);\n const rawType = String(def.type || \"\");\n if (!VALID_TYPES.has(rawType)) {\n throw new Error(`[schema] ${moduleId}: ${req.name}.${colName} bad type \"${def.type}\"`);\n }\n def.type = rawType.toLowerCase() as ColumnType;\n if (def.primary) pks++;\n if (def.ref) {\n const [t, c] = def.ref.split(\".\");\n if (!t || !c || !IDENT.test(t) || !IDENT.test(c)) {\n throw new Error(`[schema] ${moduleId}: ${req.name}.${colName} invalid ref \"${def.ref}\"`);\n }\n }\n }\n if (pks > 1) throw new Error(`[schema] ${moduleId}: ${req.name} has ${pks} primary keys (max 1)`);\n}\n\n/**\n * \u5C06\u5355\u5217\u5B9A\u4E49\u7F16\u8BD1\u4E3A SQL \u5217\u5B9A\u4E49\u5B50\u53E5\uFF08\u5982 `\"id\" TEXT PRIMARY KEY`\uFF09\u3002\n *\n * @param name \u5217\u540D\u3002\n * @param def \u5217\u5C5E\u6027\u5B9A\u4E49\u3002\n * @returns \u7F16\u8BD1\u540E\u7684 SQL \u7247\u6BB5\u3002\n */\nfunction buildColumnClause(name: string, def: ColumnDef): string {\n const parts: string[] = [`\"${name}\"`, def.type.toUpperCase()];\n if (def.primary) parts.push(\"PRIMARY KEY\");\n if (def.notNull && !def.primary) parts.push(\"NOT NULL\");\n if (def.unique) parts.push(\"UNIQUE\");\n if (def.default !== undefined && def.default !== null) {\n if (typeof def.default === \"string\") parts.push(`DEFAULT '${def.default.replace(/'/g, \"''\")}'`);\n else parts.push(`DEFAULT ${def.default}`);\n }\n if (def.ref) parts.push(`REFERENCES ${def.ref}`);\n return parts.join(\" \");\n}\n\n/**\n * \u5C06\u6574\u5F20\u8868\u7684\u6240\u6709\u5217\u5B9A\u4E49\uFF08\u5305\u542B softDelete \u9690\u5F0F\u5217\uFF09\u7F16\u8BD1\u4E3A\u5217\u5B9A\u4E49\u5B57\u7B26\u4E32\u6570\u7EC4\u3002\n * \u8F6F\u5220\u9664\u5217 `_deleted_at` \u4E0E\u4E50\u89C2\u9501\u7248\u672C\u53F7 `_version` \u4F5C\u4E3A\u72EC\u7ACB\u5217\u5728\u672B\u5C3E\u7EDF\u4E00\u8FFD\u52A0\u3002\n *\n * @param columns \u5217\u7ED3\u6784\u5B9A\u4E49\u5B57\u5178\u3002\n * @param softDelete \u662F\u5426\u542F\u7528\u8F6F\u5220\u9664\u6269\u5C55\u5217\u3002\n * @returns \u7F16\u8BD1\u540E\u7684\u5217\u5B9A\u4E49\u5B50\u53E5\u6570\u7EC4\u3002\n */\nfunction buildColumnList(columns: Record<string, ColumnDef>, softDelete: boolean): string[] {\n const cols: string[] = [];\n for (const [n, def] of Object.entries(columns)) {\n cols.push(buildColumnClause(n, def));\n }\n if (softDelete) {\n cols.push('\"_deleted_at\" INTEGER', '\"_version\" INTEGER DEFAULT 0');\n }\n return cols;\n}\n\nexport class SchemaRegistry {\n private readonly db: DatabaseSync;\n private readonly tables = new Map<string, DefinedTable>();\n private finalized = false;\n\n constructor(db: DatabaseSync) {\n this.db = db;\n }\n\n /**\n * \u6CE8\u518C\u5E76\u521B\u5EFA\u6A21\u5757\u6570\u636E\u8868\u7ED3\u6784\u3002\n *\n * @param moduleId \u6CE8\u518C\u6A21\u5757\u552F\u4E00\u6807\u8BC6\u7B26\u3002\n * @param req \u8868\u7ED3\u6784\u5B9A\u4E49\u8BF7\u6C42\u5BF9\u8C61\u3002\n * @returns \u5EFA\u8868\u7ED3\u679C\u4E0E\u521B\u5EFA\u7684\u7D22\u5F15\u5217\u8868\u3002\n */\n define(moduleId: string, req: DefineTableRequest): DefineTableResult {\n\n if (this.finalized) {\n throw new Error(`[schema] registry \u5DF2 finalize,\u6A21\u5757 ${moduleId} \u4E0D\u80FD\u518D defineTable`);\n }\n assertValid(req, moduleId);\n\n const existing = this.tables.get(req.name);\n if (existing) {\n if (existing.moduleId === moduleId) {\n // \u540C\u6A21\u5757\u91CD\u590D\u5B9A\u4E49:\u6821\u9A8C columns \u4E00\u81F4\n if (JSON.stringify(existing.columns) !== JSON.stringify(req.columns)) {\n throw new Error(`[schema] ${moduleId} \u91CD\u590D defineTable \"${req.name}\",columns \u4E0D\u4E00\u81F4`);\n }\n if (existing.softDelete !== (req.softDelete ?? true)) {\n throw new Error(`[schema] ${moduleId} \u91CD\u590D defineTable \"${req.name}\",softDelete \u4E0D\u4E00\u81F4`);\n }\n log.info(`[schema] ${moduleId} \u91CD\u590D define \"${req.name}\" \u8DF3\u8FC7`);\n return { table: req.name, created: false, indices: [] };\n }\n throw new Error(\n `[schema] \u8868 \"${req.name}\" \u5DF2\u88AB\u6A21\u5757 ${existing.moduleId} \u5B9A\u4E49,${moduleId} \u4E0D\u80FD\u62A2\u540C\u540D\u8868`\n );\n }\n\n const softDelete = req.softDelete ?? true;\n const defined: DefinedTable = {\n moduleId,\n name: req.name,\n columns: req.columns,\n softDelete,\n };\n this.tables.set(req.name, defined);\n // \u7ACB\u5373\u7269\u7406\u5EFA\u8868(\u5E42\u7B49 CREATE TABLE IF NOT EXISTS)\u3002\n // \u539F\u8BBE\u8BA1\u662F\u300Cdefine \u6536\u96C6 \u2192 finalize \u7EDF\u4E00\u5EFA\u8868\u300D,\u4F46 finalize() \u4ECE\u672A\u88AB\u8C03\u7528,\n // \u5BFC\u81F4\u6A21\u5757\u8868\u6C38\u8FDC\u4E0D\u4F1A\u843D\u5730\u3001\u540E\u7EED insert/query \u5168\u90E8 no such table\u3002\n // \u91C7\u7528 define \u5373\u5EFA\u8868:\u78B0\u649E\u68C0\u6D4B\u5DF2\u5728\u4E0A\u65B9\u6309\u5185\u5B58\u8868\u5B8C\u6210,\u4E0D\u5F71\u54CD\u5355\u4E00 owner \u4FDD\u8BC1\u3002\n this.createPhysical(defined);\n return { table: req.name, created: true, indices: [] };\n }\n\n /** \u5E42\u7B49\u5730\u7269\u7406\u5EFA\u8868 + \u5EFA\u7D22\u5F15(\u4F9B define / finalize \u5171\u7528)\u3002 */\n private createPhysical(t: DefinedTable): void {\n const existingTableInfo = this.db.prepare(`PRAGMA table_info(\"${t.name}\")`).all() as Array<{\n cid: number;\n name: string;\n type: string;\n notnull: number;\n dflt_value: unknown;\n pk: number;\n }>;\n\n if (existingTableInfo.length > 0) {\n let rowCount = 0;\n try {\n const countRes = this.db.prepare(`SELECT COUNT(*) AS c FROM \"${t.name}\"`).get() as { c: number } | undefined;\n rowCount = countRes?.c ?? 0;\n } catch {\n rowCount = 0;\n }\n const existingColNames = new Set(existingTableInfo.map((c) => c.name));\n const requiredCols = Object.keys(t.columns);\n const hasMissingRequiredCol = requiredCols.some((c) => !existingColNames.has(c));\n const pkDef = Object.entries(t.columns).find(([_, c]) => c.primary);\n const pkMatches = !pkDef || existingTableInfo.some((c) => c.pk === 1 && c.name === pkDef[0]);\n\n if (rowCount === 0 && (hasMissingRequiredCol || !pkMatches)) {\n // \u7A7A\u8868\u4E14\u7ED3\u6784\u4E0E\u5F53\u524D\u5B9A\u4E49\u4E0D\u517C\u5BB9\uFF08\u5982\u65E7\u7248\u672C\u9057\u7559\u7A7A\u8868\uFF09\uFF1A\u5B89\u5168\u5220\u9664\u5E76\u4EE5\u65B0\u7ED3\u6784\u91CD\u5EFA\n try {\n this.db.exec(`DROP TABLE \"${t.name}\"`);\n } catch {}\n const cols = buildColumnList(t.columns, t.softDelete);\n this.db.exec(`CREATE TABLE \"${t.name}\" (${cols.join(\", \")})`);\n } else {\n // \u8868\u4E2D\u6709\u6570\u636E\u6216\u7ED3\u6784\u517C\u5BB9\uFF1A\u901A\u8FC7 ALTER TABLE ADD COLUMN \u589E\u91CF\u8865\u9F50\u7F3A\u5931\u5217\n for (const [colName, colDef] of Object.entries(t.columns)) {\n if (!existingColNames.has(colName)) {\n const clause = buildColumnClause(colName, colDef);\n try {\n this.db.exec(`ALTER TABLE \"${t.name}\" ADD COLUMN ${clause};`);\n } catch (e) {\n log.warn(`[schema] ${t.moduleId}: ALTER TABLE \"${t.name}\" ADD COLUMN \"${colName}\" \u5F02\u5E38: ${(e as Error).message}`);\n }\n }\n }\n if (t.softDelete) {\n if (!existingColNames.has(\"_deleted_at\")) {\n try {\n this.db.exec(`ALTER TABLE \"${t.name}\" ADD COLUMN \"_deleted_at\" INTEGER;`);\n } catch {}\n }\n if (!existingColNames.has(\"_version\")) {\n try {\n this.db.exec(`ALTER TABLE \"${t.name}\" ADD COLUMN \"_version\" INTEGER DEFAULT 0;`);\n } catch {}\n }\n }\n }\n } else {\n const cols = buildColumnList(t.columns, t.softDelete);\n this.db.exec(`CREATE TABLE IF NOT EXISTS \"${t.name}\" (${cols.join(\", \")})`);\n }\n\n for (const [n, def] of Object.entries(t.columns)) {\n if (def.index) {\n const idxName = `idx_${t.name}_${n}`.slice(0, 60);\n try {\n this.db.exec(`CREATE INDEX IF NOT EXISTS \"${idxName}\" ON \"${t.name}\"(\"${n}\")`);\n } catch (e) {\n log.warn(`[schema] ${t.moduleId}: \u521B\u5EFA\u7D22\u5F15 \"${idxName}\" \u5931\u8D25: ${(e as Error).message}`);\n }\n }\n }\n }\n\n /**\n * \u771F\u6B63\u5EFA\u8868\u7684\u65F6\u523B\u3002\u6240\u6709 enabled \u6A21\u5757 init \u5B8C\u6210(\u6216\u660E\u786E\u8C03)\u540E\u518D finalize\u3002\n * CREATE TABLE IF NOT EXISTS + CREATE INDEX IF NOT EXISTS,\u5E42\u7B49\u3002\n */\n finalize(): { tableNames: string[]; created: string[] } {\n if (this.finalized) return { tableNames: [...this.tables.keys()], created: [] };\n this.finalized = true;\n const created: string[] = [];\n\n for (const t of this.tables.values()) {\n const cols = buildColumnList(t.columns, t.softDelete);\n const ddl = `CREATE TABLE IF NOT EXISTS \"${t.name}\" (${cols.join(\", \")})`;\n this.db.exec(ddl);\n\n for (const [n, def] of Object.entries(t.columns)) {\n if (def.index) {\n const idxName = `idx_${t.name}_${n}`.slice(0, 60);\n this.db.exec(`CREATE INDEX IF NOT EXISTS \"${idxName}\" ON \"${t.name}\"(\"${n}\")`);\n }\n }\n\n const exists =\n (this.db\n .prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name=?\")\n .get(t.name) as { name?: string } | undefined)?.name === t.name;\n if (exists) created.push(t.name);\n log.success(`[schema] ${t.moduleId} \u2192 ${t.name} (softDelete=${t.softDelete})`);\n }\n\n return { tableNames: [...this.tables.keys()], created };\n }\n\n listTables(): string[] {\n return [...this.tables.keys()];\n }\n\n isRegistered(name: string): boolean {\n return this.tables.has(name);\n }\n\n getOwner(name: string): string | undefined {\n return this.tables.get(name)?.moduleId;\n }\n}\n"],
5
+ "mappings": "AAeA,SAAS,WAAW;AA2Cb,MAAM,qBAAqB;AAElC,MAAM,QAAQ;AACd,MAAM,cAA2B,oBAAI,IAAI;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,YAAY,KAAyB,UAAwB;AACpE,MAAI,CAAC,MAAM,KAAK,IAAI,IAAI,EAAG,OAAM,IAAI,MAAM,YAAY,QAAQ,yBAAyB,IAAI,IAAI,GAAG;AACnG,MAAI,CAAC,IAAI,WAAW,OAAO,KAAK,IAAI,OAAO,EAAE,WAAW,GAAG;AACzD,UAAM,IAAI,MAAM,YAAY,QAAQ,KAAK,IAAI,IAAI,iBAAiB;AAAA,EACpE;AACA,MAAI,MAAM;AACV,aAAW,CAAC,SAAS,GAAG,KAAK,OAAO,QAAQ,IAAI,OAAO,GAAG;AACxD,QAAI,CAAC,MAAM,KAAK,OAAO,EAAG,OAAM,IAAI,MAAM,YAAY,QAAQ,0BAA0B,OAAO,QAAQ,IAAI,IAAI,EAAE;AACjH,UAAM,UAAU,OAAO,IAAI,QAAQ,EAAE;AACrC,QAAI,CAAC,YAAY,IAAI,OAAO,GAAG;AAC7B,YAAM,IAAI,MAAM,YAAY,QAAQ,KAAK,IAAI,IAAI,IAAI,OAAO,cAAc,IAAI,IAAI,GAAG;AAAA,IACvF;AACA,QAAI,OAAO,QAAQ,YAAY;AAC/B,QAAI,IAAI,QAAS;AACjB,QAAI,IAAI,KAAK;AACX,YAAM,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,MAAM,GAAG;AAChC,UAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,GAAG;AAChD,cAAM,IAAI,MAAM,YAAY,QAAQ,KAAK,IAAI,IAAI,IAAI,OAAO,iBAAiB,IAAI,GAAG,GAAG;AAAA,MACzF;AAAA,IACF;AAAA,EACF;AACA,MAAI,MAAM,EAAG,OAAM,IAAI,MAAM,YAAY,QAAQ,KAAK,IAAI,IAAI,QAAQ,GAAG,uBAAuB;AAClG;AASA,SAAS,kBAAkB,MAAc,KAAwB;AAC/D,QAAM,QAAkB,CAAC,IAAI,IAAI,KAAK,IAAI,KAAK,YAAY,CAAC;AAC5D,MAAI,IAAI,QAAS,OAAM,KAAK,aAAa;AACzC,MAAI,IAAI,WAAW,CAAC,IAAI,QAAS,OAAM,KAAK,UAAU;AACtD,MAAI,IAAI,OAAQ,OAAM,KAAK,QAAQ;AACnC,MAAI,IAAI,YAAY,UAAa,IAAI,YAAY,MAAM;AACrD,QAAI,OAAO,IAAI,YAAY,SAAU,OAAM,KAAK,YAAY,IAAI,QAAQ,QAAQ,MAAM,IAAI,CAAC,GAAG;AAAA,QACzF,OAAM,KAAK,WAAW,IAAI,OAAO,EAAE;AAAA,EAC1C;AACA,MAAI,IAAI,IAAK,OAAM,KAAK,cAAc,IAAI,GAAG,EAAE;AAC/C,SAAO,MAAM,KAAK,GAAG;AACvB;AAUA,SAAS,gBAAgB,SAAoC,YAA+B;AAC1F,QAAM,OAAiB,CAAC;AACxB,aAAW,CAAC,GAAG,GAAG,KAAK,OAAO,QAAQ,OAAO,GAAG;AAC9C,SAAK,KAAK,kBAAkB,GAAG,GAAG,CAAC;AAAA,EACrC;AACA,MAAI,YAAY;AACd,SAAK,KAAK,yBAAyB,8BAA8B;AAAA,EACnE;AACA,SAAO;AACT;AAEO,MAAM,eAAe;AAAA,EACT;AAAA,EACA,SAAS,oBAAI,IAA0B;AAAA,EAChD,YAAY;AAAA,EAEpB,YAAY,IAAkB;AAC5B,SAAK,KAAK;AAAA,EACZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAO,UAAkB,KAA4C;AAEnE,QAAI,KAAK,WAAW;AAClB,YAAM,IAAI,MAAM,kDAAmC,QAAQ,iCAAkB;AAAA,IAC/E;AACA,gBAAY,KAAK,QAAQ;AAEzB,UAAM,WAAW,KAAK,OAAO,IAAI,IAAI,IAAI;AACzC,QAAI,UAAU;AACZ,UAAI,SAAS,aAAa,UAAU;AAElC,YAAI,KAAK,UAAU,SAAS,OAAO,MAAM,KAAK,UAAU,IAAI,OAAO,GAAG;AACpE,gBAAM,IAAI,MAAM,YAAY,QAAQ,8BAAoB,IAAI,IAAI,8BAAe;AAAA,QACjF;AACA,YAAI,SAAS,gBAAgB,IAAI,cAAc,OAAO;AACpD,gBAAM,IAAI,MAAM,YAAY,QAAQ,8BAAoB,IAAI,IAAI,iCAAkB;AAAA,QACpF;AACA,YAAI,KAAK,YAAY,QAAQ,yBAAe,IAAI,IAAI,gBAAM;AAC1D,eAAO,EAAE,OAAO,IAAI,MAAM,SAAS,OAAO,SAAS,CAAC,EAAE;AAAA,MACxD;AACA,YAAM,IAAI;AAAA,QACR,oBAAe,IAAI,IAAI,8BAAU,SAAS,QAAQ,iBAAO,QAAQ;AAAA,MACnE;AAAA,IACF;AAEA,UAAM,aAAa,IAAI,cAAc;AACrC,UAAM,UAAwB;AAAA,MAC5B;AAAA,MACA,MAAM,IAAI;AAAA,MACV,SAAS,IAAI;AAAA,MACb;AAAA,IACF;AACA,SAAK,OAAO,IAAI,IAAI,MAAM,OAAO;AAKjC,SAAK,eAAe,OAAO;AAC3B,WAAO,EAAE,OAAO,IAAI,MAAM,SAAS,MAAM,SAAS,CAAC,EAAE;AAAA,EACvD;AAAA;AAAA,EAGQ,eAAe,GAAuB;AAC5C,UAAM,oBAAoB,KAAK,GAAG,QAAQ,sBAAsB,EAAE,IAAI,IAAI,EAAE,IAAI;AAShF,QAAI,kBAAkB,SAAS,GAAG;AAChC,UAAI,WAAW;AACf,UAAI;AACF,cAAM,WAAW,KAAK,GAAG,QAAQ,8BAA8B,EAAE,IAAI,GAAG,EAAE,IAAI;AAC9E,mBAAW,UAAU,KAAK;AAAA,MAC5B,QAAQ;AACN,mBAAW;AAAA,MACb;AACA,YAAM,mBAAmB,IAAI,IAAI,kBAAkB,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AACrE,YAAM,eAAe,OAAO,KAAK,EAAE,OAAO;AAC1C,YAAM,wBAAwB,aAAa,KAAK,CAAC,MAAM,CAAC,iBAAiB,IAAI,CAAC,CAAC;AAC/E,YAAM,QAAQ,OAAO,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO;AAClE,YAAM,YAAY,CAAC,SAAS,kBAAkB,KAAK,CAAC,MAAM,EAAE,OAAO,KAAK,EAAE,SAAS,MAAM,CAAC,CAAC;AAE3F,UAAI,aAAa,MAAM,yBAAyB,CAAC,YAAY;AAE3D,YAAI;AACF,eAAK,GAAG,KAAK,eAAe,EAAE,IAAI,GAAG;AAAA,QACvC,QAAQ;AAAA,QAAC;AACT,cAAM,OAAO,gBAAgB,EAAE,SAAS,EAAE,UAAU;AACpD,aAAK,GAAG,KAAK,iBAAiB,EAAE,IAAI,MAAM,KAAK,KAAK,IAAI,CAAC,GAAG;AAAA,MAC9D,OAAO;AAEL,mBAAW,CAAC,SAAS,MAAM,KAAK,OAAO,QAAQ,EAAE,OAAO,GAAG;AACzD,cAAI,CAAC,iBAAiB,IAAI,OAAO,GAAG;AAClC,kBAAM,SAAS,kBAAkB,SAAS,MAAM;AAChD,gBAAI;AACF,mBAAK,GAAG,KAAK,gBAAgB,EAAE,IAAI,gBAAgB,MAAM,GAAG;AAAA,YAC9D,SAAS,GAAG;AACV,kBAAI,KAAK,YAAY,EAAE,QAAQ,kBAAkB,EAAE,IAAI,iBAAiB,OAAO,mBAAU,EAAY,OAAO,EAAE;AAAA,YAChH;AAAA,UACF;AAAA,QACF;AACA,YAAI,EAAE,YAAY;AAChB,cAAI,CAAC,iBAAiB,IAAI,aAAa,GAAG;AACxC,gBAAI;AACF,mBAAK,GAAG,KAAK,gBAAgB,EAAE,IAAI,qCAAqC;AAAA,YAC1E,QAAQ;AAAA,YAAC;AAAA,UACX;AACA,cAAI,CAAC,iBAAiB,IAAI,UAAU,GAAG;AACrC,gBAAI;AACF,mBAAK,GAAG,KAAK,gBAAgB,EAAE,IAAI,4CAA4C;AAAA,YACjF,QAAQ;AAAA,YAAC;AAAA,UACX;AAAA,QACF;AAAA,MACF;AAAA,IACF,OAAO;AACL,YAAM,OAAO,gBAAgB,EAAE,SAAS,EAAE,UAAU;AACpD,WAAK,GAAG,KAAK,+BAA+B,EAAE,IAAI,MAAM,KAAK,KAAK,IAAI,CAAC,GAAG;AAAA,IAC5E;AAEA,eAAW,CAAC,GAAG,GAAG,KAAK,OAAO,QAAQ,EAAE,OAAO,GAAG;AAChD,UAAI,IAAI,OAAO;AACb,cAAM,UAAU,OAAO,EAAE,IAAI,IAAI,CAAC,GAAG,MAAM,GAAG,EAAE;AAChD,YAAI;AACF,eAAK,GAAG,KAAK,+BAA+B,OAAO,SAAS,EAAE,IAAI,MAAM,CAAC,IAAI;AAAA,QAC/E,SAAS,GAAG;AACV,cAAI,KAAK,YAAY,EAAE,QAAQ,+BAAW,OAAO,mBAAU,EAAY,OAAO,EAAE;AAAA,QAClF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAAwD;AACtD,QAAI,KAAK,UAAW,QAAO,EAAE,YAAY,CAAC,GAAG,KAAK,OAAO,KAAK,CAAC,GAAG,SAAS,CAAC,EAAE;AAC9E,SAAK,YAAY;AACjB,UAAM,UAAoB,CAAC;AAE3B,eAAW,KAAK,KAAK,OAAO,OAAO,GAAG;AACpC,YAAM,OAAO,gBAAgB,EAAE,SAAS,EAAE,UAAU;AACpD,YAAM,MAAM,+BAA+B,EAAE,IAAI,MAAM,KAAK,KAAK,IAAI,CAAC;AACtE,WAAK,GAAG,KAAK,GAAG;AAEhB,iBAAW,CAAC,GAAG,GAAG,KAAK,OAAO,QAAQ,EAAE,OAAO,GAAG;AAChD,YAAI,IAAI,OAAO;AACb,gBAAM,UAAU,OAAO,EAAE,IAAI,IAAI,CAAC,GAAG,MAAM,GAAG,EAAE;AAChD,eAAK,GAAG,KAAK,+BAA+B,OAAO,SAAS,EAAE,IAAI,MAAM,CAAC,IAAI;AAAA,QAC/E;AAAA,MACF;AAEA,YAAM,SACH,KAAK,GACH,QAAQ,8DAA8D,EACtE,IAAI,EAAE,IAAI,GAAqC,SAAS,EAAE;AAC/D,UAAI,OAAQ,SAAQ,KAAK,EAAE,IAAI;AAC/B,UAAI,QAAQ,YAAY,EAAE,QAAQ,WAAM,EAAE,IAAI,gBAAgB,EAAE,UAAU,GAAG;AAAA,IAC/E;AAEA,WAAO,EAAE,YAAY,CAAC,GAAG,KAAK,OAAO,KAAK,CAAC,GAAG,QAAQ;AAAA,EACxD;AAAA,EAEA,aAAuB;AACrB,WAAO,CAAC,GAAG,KAAK,OAAO,KAAK,CAAC;AAAA,EAC/B;AAAA,EAEA,aAAa,MAAuB;AAClC,WAAO,KAAK,OAAO,IAAI,IAAI;AAAA,EAC7B;AAAA,EAEA,SAAS,MAAkC;AACzC,WAAO,KAAK,OAAO,IAAI,IAAI,GAAG;AAAA,EAChC;AACF;",
6
6
  "names": []
7
7
  }