@sfmc-bds/db-server 0.1.0 → 0.2.0-beta.1
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/README.md +1 -1
- package/dist/db-tables.js +16 -17
- package/dist/db-tables.js.map +7 -1
- package/dist/domain/bridge.js +54 -77
- package/dist/domain/bridge.js.map +7 -1
- package/dist/domain/economy.d.ts +83 -2
- package/dist/domain/economy.d.ts.map +1 -1
- package/dist/domain/economy.js +292 -281
- package/dist/domain/economy.js.map +7 -1
- package/dist/domain/schema.js +480 -426
- package/dist/domain/schema.js.map +7 -1
- package/dist/domain/transaction.js +32 -80
- package/dist/domain/transaction.js.map +7 -1
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +85 -84
- package/dist/env.js.map +7 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +218 -240
- package/dist/index.js.map +7 -1
- package/dist/lib/http.js +30 -39
- package/dist/lib/http.js.map +7 -1
- package/dist/lib/idempotency-store.js +34 -47
- package/dist/lib/idempotency-store.js.map +7 -1
- package/dist/lib/idempotency.js +8 -13
- package/dist/lib/idempotency.js.map +7 -1
- package/dist/lib/identifiers.js +18 -14
- package/dist/lib/identifiers.js.map +7 -1
- package/dist/lib/log.d.ts +3 -6
- package/dist/lib/log.d.ts.map +1 -1
- package/dist/lib/log.js +13 -14
- package/dist/lib/log.js.map +7 -1
- package/dist/lib/module-state.d.ts.map +1 -1
- package/dist/lib/module-state.js +32 -21
- package/dist/lib/module-state.js.map +7 -1
- package/dist/lib/order-by.d.ts +17 -0
- package/dist/lib/order-by.d.ts.map +1 -0
- package/dist/lib/order-by.js +24 -0
- package/dist/lib/order-by.js.map +7 -0
- package/dist/lib/runtime.js +16 -31
- package/dist/lib/runtime.js.map +7 -1
- package/dist/lib/sql-helpers.js +9 -27
- package/dist/lib/sql-helpers.js.map +7 -1
- package/dist/lib/sqlite.js +40 -52
- package/dist/lib/sqlite.js.map +7 -1
- package/dist/manifest-loader.d.ts.map +1 -1
- package/dist/manifest-loader.js +154 -195
- package/dist/manifest-loader.js.map +7 -1
- package/dist/manifest.d.ts +3 -7
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +76 -115
- package/dist/manifest.js.map +7 -1
- package/dist/module-auth.d.ts +17 -6
- package/dist/module-auth.d.ts.map +1 -1
- package/dist/module-auth.js +93 -107
- package/dist/module-auth.js.map +7 -1
- package/dist/module-runtime-sync.d.ts +37 -0
- package/dist/module-runtime-sync.d.ts.map +1 -0
- package/dist/module-runtime-sync.js +54 -0
- package/dist/module-runtime-sync.js.map +7 -0
- package/dist/permission-gate.d.ts.map +1 -1
- package/dist/permission-gate.js +55 -86
- package/dist/permission-gate.js.map +7 -1
- package/dist/routes/_shared.d.ts +7 -2
- package/dist/routes/_shared.d.ts.map +1 -1
- package/dist/routes/_shared.js +16 -25
- package/dist/routes/_shared.js.map +7 -1
- package/dist/routes/config.d.ts +11 -1
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +167 -213
- package/dist/routes/config.js.map +7 -1
- package/dist/routes/db-routes.d.ts +6 -10
- package/dist/routes/db-routes.d.ts.map +1 -1
- package/dist/routes/db-routes.js +198 -172
- package/dist/routes/db-routes.js.map +7 -1
- package/dist/routes/health.js +15 -20
- package/dist/routes/health.js.map +7 -1
- package/dist/routes/messages.d.ts.map +1 -1
- package/dist/routes/messages.js +56 -63
- package/dist/routes/messages.js.map +7 -1
- package/dist/routes/module-config-routes.d.ts.map +1 -1
- package/dist/routes/module-config-routes.js +92 -111
- package/dist/routes/module-config-routes.js.map +7 -1
- package/dist/routes/modules.d.ts.map +1 -1
- package/dist/routes/modules.js +82 -88
- package/dist/routes/modules.js.map +7 -1
- package/dist/routes/service-routes.d.ts.map +1 -1
- package/dist/routes/service-routes.js +54 -61
- package/dist/routes/service-routes.js.map +7 -1
- package/dist/runtime.test.js +248 -49
- package/dist/runtime.test.js.map +7 -1
- package/dist/schema-registry.d.ts.map +1 -1
- package/dist/schema-registry.js +119 -160
- package/dist/schema-registry.js.map +7 -1
- package/dist/server.js +69 -72
- package/dist/server.js.map +7 -1
- package/dist/service-registry.d.ts.map +1 -1
- package/dist/service-registry.js +60 -67
- package/dist/service-registry.js.map +7 -1
- package/dist/services/builtin-handlers.d.ts +4 -0
- package/dist/services/builtin-handlers.d.ts.map +1 -1
- package/dist/services/builtin-handlers.js +34 -20
- package/dist/services/builtin-handlers.js.map +7 -1
- package/dist/services/economy-handlers.js +143 -132
- package/dist/services/economy-handlers.js.map +7 -1
- package/dist/tx-runner.d.ts +35 -11
- package/dist/tx-runner.d.ts.map +1 -1
- package/dist/tx-runner.js +293 -250
- package/dist/tx-runner.js.map +7 -1
- package/dist/where.js +71 -94
- package/dist/where.js.map +7 -1
- package/package.json +4 -4
|
@@ -1,68 +1,61 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* routes/service-routes.ts — /api/sfmc/services/* 处理器
|
|
3
|
-
*
|
|
4
|
-
* 端点:
|
|
5
|
-
* GET /api/sfmc/services → { services: [{name, moduleId}] }
|
|
6
|
-
* GET /api/sfmc/services/:name?input=<urlencoded json>
|
|
7
|
-
* → { ok: true, result } | { ok: false, error, code } (+ HTTP status)
|
|
8
|
-
*/
|
|
9
1
|
import { json as defaultJson } from "../lib/http.js";
|
|
10
2
|
import { assertModulePermission, Perm, PermissionDeniedError } from "../permission-gate.js";
|
|
11
3
|
import { jsonV2Fail } from "./_shared.js";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
4
|
+
function createServiceRoutes(depsIn) {
|
|
5
|
+
const deps = depsIn;
|
|
6
|
+
if (!deps.serviceRegistry || !deps.enabled) {
|
|
7
|
+
throw new Error("createServiceRoutes: \u7F3A\u5C11 serviceRegistry / enabled map");
|
|
8
|
+
}
|
|
9
|
+
const json = deps.json || defaultJson;
|
|
10
|
+
return async (ctx) => {
|
|
11
|
+
const { path, method, params, res } = ctx;
|
|
12
|
+
if (!path.startsWith("/api/sfmc/services")) return false;
|
|
13
|
+
if (method !== "GET") return false;
|
|
14
|
+
const auth = ctx.moduleAuth ?? null;
|
|
15
|
+
if (!auth) {
|
|
16
|
+
jsonV2Fail(res, "unauthorized: module identity missing", 401, "unauthorized");
|
|
17
|
+
return true;
|
|
16
18
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return true;
|
|
19
|
+
if (path === "/api/sfmc/services") {
|
|
20
|
+
json(res, { services: deps.serviceRegistry.list() });
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
const m = path.match(/^\/api\/sfmc\/services\/([A-Za-z0-9_.]+)$/);
|
|
24
|
+
if (m && m[1]) {
|
|
25
|
+
const name = m[1];
|
|
26
|
+
const rawInput = params.get("input");
|
|
27
|
+
let payload = {};
|
|
28
|
+
if (rawInput) {
|
|
29
|
+
try {
|
|
30
|
+
payload = JSON.parse(rawInput);
|
|
31
|
+
} catch (e) {
|
|
32
|
+
jsonV2Fail(res, `invalid input json: ${e.message}`, 400, "bad_request");
|
|
33
|
+
return true;
|
|
33
34
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
// 与 tx-runner.doService 对齐(LSP):HTTP service.get 与 tx.call 必须同契约 —
|
|
50
|
-
// 既要在 services.requires 里(由 dispatch 校验),也要有 service:<name> 权限。
|
|
51
|
-
assertModulePermission(auth.id, auth.permissions, Perm.service(name));
|
|
52
|
-
const out = await deps.serviceRegistry.dispatch(deps.enabled, auth.id, name, payload);
|
|
53
|
-
json(res, { ok: true, result: out.result });
|
|
54
|
-
}
|
|
55
|
-
catch (e) {
|
|
56
|
-
if (e instanceof PermissionDeniedError) {
|
|
57
|
-
json(res, { ok: false, error: e.message, code: "permission_denied" }, 403);
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
const err = e;
|
|
61
|
-
json(res, { ok: false, error: err.message, code: err.code ?? "internal" }, err.status ?? 500);
|
|
62
|
-
}
|
|
63
|
-
return true;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
assertModulePermission(auth.id, auth.permissions, Perm.service(name));
|
|
38
|
+
const out = await deps.serviceRegistry.dispatch(
|
|
39
|
+
deps.enabled,
|
|
40
|
+
auth.id,
|
|
41
|
+
name,
|
|
42
|
+
payload
|
|
43
|
+
);
|
|
44
|
+
json(res, { ok: true, result: out.result });
|
|
45
|
+
} catch (e) {
|
|
46
|
+
if (e instanceof PermissionDeniedError) {
|
|
47
|
+
jsonV2Fail(res, e.message, 403, "permission_denied");
|
|
48
|
+
return true;
|
|
64
49
|
}
|
|
65
|
-
|
|
66
|
-
|
|
50
|
+
const err = e;
|
|
51
|
+
jsonV2Fail(res, err.message, err.status ?? 500, err.code ?? "internal");
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
};
|
|
67
57
|
}
|
|
68
|
-
|
|
58
|
+
export {
|
|
59
|
+
createServiceRoutes
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=service-routes.js.map
|
|
@@ -1 +1,7 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/routes/service-routes.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * routes/service-routes.ts \u2014 /api/sfmc/services/* \u5904\u7406\u5668\n *\n * \u7AEF\u70B9:\n * GET /api/sfmc/services \u2192 { services: [{name, moduleId}] }\n * GET /api/sfmc/services/:name?input=<urlencoded json>\n * \u2192 { ok: true, result } | { ok: false, error, code } (+ HTTP status)\n */\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { json as defaultJson, type Method } from \"../lib/http.js\";\nimport { assertModulePermission, Perm, PermissionDeniedError } from \"../permission-gate.js\";\nimport type { ServiceRegistry } from \"../service-registry.js\";\nimport { jsonV2Fail, type ModuleAuth } from \"./_shared.js\";\n\nexport interface ServiceRoutesDeps {\n serviceRegistry: ServiceRegistry;\n enabled: Map<string, import(\"../manifest-loader.js\").ModuleManifestV2>;\n json?: typeof defaultJson;\n}\n\nexport function createServiceRoutes(depsIn: Partial<ServiceRoutesDeps>) {\n const deps = depsIn as Partial<ServiceRoutesDeps>;\n if (!deps.serviceRegistry || !deps.enabled) {\n throw new Error(\"createServiceRoutes: \u7F3A\u5C11 serviceRegistry / enabled map\");\n }\n const json = deps.json || defaultJson;\n\n return async (ctx: {\n path: string;\n method: Method | string;\n params: URLSearchParams;\n req: IncomingMessage;\n res: ServerResponse;\n moduleAuth?: ModuleAuth;\n }): Promise<boolean> => {\n const { path, method, params, res } = ctx;\n if (!path.startsWith(\"/api/sfmc/services\")) return false;\n if (method !== \"GET\") return false;\n\n const auth = ctx.moduleAuth ?? null;\n if (!auth) {\n // LSP: \u4E0E\u6210\u529F\u8DEF\u5F84\u540C\u4E00 ok \u65B9\u8A00(\u6587\u6863\u7EA6\u5B9A { ok:false, error, code })\n jsonV2Fail(res, \"unauthorized: module identity missing\", 401, \"unauthorized\");\n return true;\n }\n\n if (path === \"/api/sfmc/services\") {\n json(res, { services: deps.serviceRegistry!.list() });\n return true;\n }\n\n const m = path.match(/^\\/api\\/sfmc\\/services\\/([A-Za-z0-9_.]+)$/);\n if (m && m[1]) {\n const name = m[1];\n const rawInput = params.get(\"input\");\n let payload: unknown = {};\n if (rawInput) {\n try {\n payload = JSON.parse(rawInput);\n } catch (e) {\n jsonV2Fail(res, `invalid input json: ${(e as Error).message}`, 400, \"bad_request\");\n return true;\n }\n }\n try {\n // \u4E0E tx-runner.doService \u5BF9\u9F50(LSP):HTTP service.get \u4E0E tx.call \u5FC5\u987B\u540C\u5951\u7EA6 \u2014\n // \u65E2\u8981\u5728 services.requires \u91CC(\u7531 dispatch \u6821\u9A8C),\u4E5F\u8981\u6709 service:<name> \u6743\u9650\u3002\n assertModulePermission(auth.id, auth.permissions, Perm.service(name));\n const out = await deps.serviceRegistry!.dispatch(\n deps.enabled!,\n auth.id,\n name,\n payload\n );\n json(res, { ok: true, result: out.result });\n } catch (e) {\n if (e instanceof PermissionDeniedError) {\n jsonV2Fail(res, e.message, 403, \"permission_denied\");\n return true;\n }\n const err = e as { status?: number; code?: string; message: string };\n jsonV2Fail(res, err.message, err.status ?? 500, err.code ?? \"internal\");\n }\n return true;\n }\n\n return false;\n };\n}\n"],
|
|
5
|
+
"mappings": "AAUA,SAAS,QAAQ,mBAAgC;AACjD,SAAS,wBAAwB,MAAM,6BAA6B;AAEpE,SAAS,kBAAmC;AAQrC,SAAS,oBAAoB,QAAoC;AACtE,QAAM,OAAO;AACb,MAAI,CAAC,KAAK,mBAAmB,CAAC,KAAK,SAAS;AAC1C,UAAM,IAAI,MAAM,iEAAuD;AAAA,EACzE;AACA,QAAM,OAAO,KAAK,QAAQ;AAE1B,SAAO,OAAO,QAOU;AACtB,UAAM,EAAE,MAAM,QAAQ,QAAQ,IAAI,IAAI;AACtC,QAAI,CAAC,KAAK,WAAW,oBAAoB,EAAG,QAAO;AACnD,QAAI,WAAW,MAAO,QAAO;AAE7B,UAAM,OAAO,IAAI,cAAc;AAC/B,QAAI,CAAC,MAAM;AAET,iBAAW,KAAK,yCAAyC,KAAK,cAAc;AAC5E,aAAO;AAAA,IACT;AAEA,QAAI,SAAS,sBAAsB;AACjC,WAAK,KAAK,EAAE,UAAU,KAAK,gBAAiB,KAAK,EAAE,CAAC;AACpD,aAAO;AAAA,IACT;AAEA,UAAM,IAAI,KAAK,MAAM,2CAA2C;AAChE,QAAI,KAAK,EAAE,CAAC,GAAG;AACb,YAAM,OAAO,EAAE,CAAC;AAChB,YAAM,WAAW,OAAO,IAAI,OAAO;AACnC,UAAI,UAAmB,CAAC;AACxB,UAAI,UAAU;AACZ,YAAI;AACF,oBAAU,KAAK,MAAM,QAAQ;AAAA,QAC/B,SAAS,GAAG;AACV,qBAAW,KAAK,uBAAwB,EAAY,OAAO,IAAI,KAAK,aAAa;AACjF,iBAAO;AAAA,QACT;AAAA,MACF;AACA,UAAI;AAGF,+BAAuB,KAAK,IAAI,KAAK,aAAa,KAAK,QAAQ,IAAI,CAAC;AACpE,cAAM,MAAM,MAAM,KAAK,gBAAiB;AAAA,UACtC,KAAK;AAAA,UACL,KAAK;AAAA,UACL;AAAA,UACA;AAAA,QACF;AACA,aAAK,KAAK,EAAE,IAAI,MAAM,QAAQ,IAAI,OAAO,CAAC;AAAA,MAC5C,SAAS,GAAG;AACV,YAAI,aAAa,uBAAuB;AACtC,qBAAW,KAAK,EAAE,SAAS,KAAK,mBAAmB;AACnD,iBAAO;AAAA,QACT;AACA,cAAM,MAAM;AACZ,mBAAW,KAAK,IAAI,SAAS,IAAI,UAAU,KAAK,IAAI,QAAQ,UAAU;AAAA,MACxE;AACA,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/runtime.test.js
CHANGED
|
@@ -6,61 +6,260 @@ import { parseNodeVersion } from "./lib/runtime.js";
|
|
|
6
6
|
import { raw, sql } from "./lib/sql-helpers.js";
|
|
7
7
|
import { DispatchError, ServiceRegistry } from "./service-registry.js";
|
|
8
8
|
test("parses Node versions", () => {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
deepEqual(parseNodeVersion("22.5.1"), { major: 22, minor: 5, patch: 1 });
|
|
10
|
+
deepEqual(parseNodeVersion("v22.5.1"), { major: 22, minor: 5, patch: 1 });
|
|
11
11
|
});
|
|
12
12
|
test("accepts only safe SQL identifiers", () => {
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
equal(quoteIdentifier("sfmc_players", "table"), '"sfmc_players"');
|
|
14
|
+
throws(() => assertIdentifier("users; DROP TABLE x"), /Invalid SQL/);
|
|
15
15
|
});
|
|
16
16
|
test("sql() embeds trusted identifiers and binds values", () => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
const TABLE = "sfmc_economy_accounts";
|
|
18
|
+
const q = sql(`SELECT * FROM ${TABLE} WHERE player_id = ?`, ["p1"]);
|
|
19
|
+
equal(q.sql, "SELECT * FROM sfmc_economy_accounts WHERE player_id = ?");
|
|
20
|
+
deepEqual(q.values, ["p1"]);
|
|
21
21
|
});
|
|
22
|
-
test("raw() only works via append
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
test("raw() only works via append \u2014 template interpolation still becomes ?", () => {
|
|
23
|
+
const TABLE = "sfmc_economy_accounts";
|
|
24
|
+
const broken = SQL`SELECT * FROM ${raw(TABLE)} WHERE id = ${"p1"}`;
|
|
25
|
+
equal(broken.sql, "SELECT * FROM ? WHERE id = ?");
|
|
26
|
+
const ok = SQL`SELECT * FROM `.append(raw(TABLE)).append(SQL` WHERE id = ${"p1"}`);
|
|
27
|
+
equal(ok.sql, "SELECT * FROM sfmc_economy_accounts WHERE id = ?");
|
|
28
|
+
deepEqual(ok.values, ["p1"]);
|
|
29
29
|
});
|
|
30
30
|
test("ServiceRegistry: provider self-call skips requires (LSP with tx.call path)", async () => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
31
|
+
const reg = new ServiceRegistry();
|
|
32
|
+
reg.registerHandler("feature-economy", "economy.stats.monthly", async () => ({ ok: true }));
|
|
33
|
+
const enabled = /* @__PURE__ */ new Map([
|
|
34
|
+
[
|
|
35
|
+
"feature-economy",
|
|
36
|
+
{
|
|
37
|
+
id: "feature-economy",
|
|
38
|
+
version: "1.0.0",
|
|
39
|
+
permissions: [],
|
|
40
|
+
services: { provides: [{ name: "economy.stats.monthly" }], requires: [] },
|
|
41
|
+
db: { tables: [] },
|
|
42
|
+
config: { key: "economy" }
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
]);
|
|
46
|
+
const out = await reg.dispatch(enabled, "feature-economy", "economy.stats.monthly", {});
|
|
47
|
+
deepEqual(out, { ok: true, result: { ok: true } });
|
|
48
48
|
});
|
|
49
|
-
test("ServiceRegistry: missing service
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
49
|
+
test("ServiceRegistry: missing service \u2192 no_such_service (not not_in_requires)", async () => {
|
|
50
|
+
const reg = new ServiceRegistry();
|
|
51
|
+
const enabled = /* @__PURE__ */ new Map([
|
|
52
|
+
[
|
|
53
|
+
"feature-chat",
|
|
54
|
+
{
|
|
55
|
+
id: "feature-chat",
|
|
56
|
+
version: "1.0.0",
|
|
57
|
+
permissions: [],
|
|
58
|
+
services: { provides: [], requires: [] },
|
|
59
|
+
db: { tables: [] },
|
|
60
|
+
config: { key: "chat" }
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
]);
|
|
64
|
+
await rejects(
|
|
65
|
+
() => reg.dispatch(enabled, "feature-chat", "economy.account.get", {}),
|
|
66
|
+
(e) => e instanceof DispatchError && e.code === "no_such_service"
|
|
67
|
+
);
|
|
65
68
|
});
|
|
66
|
-
|
|
69
|
+
test("jsonV2Fail: ok \u65B9\u8A00 + extra(step) \u5408\u5E76(LSP/DRY)", async () => {
|
|
70
|
+
const { jsonV2Fail } = await import("./routes/_shared.js");
|
|
71
|
+
const chunks = [];
|
|
72
|
+
let statusCode = 0;
|
|
73
|
+
const res = {
|
|
74
|
+
writeHead(code) {
|
|
75
|
+
statusCode = code;
|
|
76
|
+
},
|
|
77
|
+
end(body) {
|
|
78
|
+
if (body) chunks.push(Buffer.from(body));
|
|
79
|
+
},
|
|
80
|
+
setHeader() {
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
jsonV2Fail(res, "boom", 400, "bad_step", { step: 2 });
|
|
84
|
+
const payload = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
85
|
+
equal(statusCode, 400);
|
|
86
|
+
deepEqual(payload, { ok: false, error: "boom", code: "bad_step", step: 2 });
|
|
87
|
+
});
|
|
88
|
+
test("jsonV2Ok: \u4E0E Fail \u5BF9\u79F0 ok \u65B9\u8A00(LSP/DRY)", async () => {
|
|
89
|
+
const { jsonV2Ok } = await import("./routes/_shared.js");
|
|
90
|
+
const chunks = [];
|
|
91
|
+
let statusCode = 0;
|
|
92
|
+
const res = {
|
|
93
|
+
writeHead(code) {
|
|
94
|
+
statusCode = code;
|
|
95
|
+
},
|
|
96
|
+
end(body) {
|
|
97
|
+
if (body) chunks.push(Buffer.from(body));
|
|
98
|
+
},
|
|
99
|
+
setHeader() {
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
jsonV2Ok(res, { rows: [1] });
|
|
103
|
+
const payload = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
104
|
+
equal(statusCode, 200);
|
|
105
|
+
deepEqual(payload, { ok: true, rows: [1] });
|
|
106
|
+
});
|
|
107
|
+
test("normalizeOrderBy: SDK field \u4E0E\u9057\u7559 col / \u6570\u7EC4\u4E92\u901A(LSP)", async () => {
|
|
108
|
+
const { normalizeOrderBy } = await import("./lib/order-by.js");
|
|
109
|
+
deepEqual(normalizeOrderBy(void 0), []);
|
|
110
|
+
deepEqual(normalizeOrderBy({ field: "created_at", dir: "desc" }), [
|
|
111
|
+
{ col: "created_at", dir: "desc" }
|
|
112
|
+
]);
|
|
113
|
+
deepEqual(normalizeOrderBy({ col: "id" }), [{ col: "id", dir: "asc" }]);
|
|
114
|
+
deepEqual(normalizeOrderBy([{ field: "a" }, { col: "b", dir: "desc" }]), [
|
|
115
|
+
{ col: "a", dir: "asc" },
|
|
116
|
+
{ col: "b", dir: "desc" }
|
|
117
|
+
]);
|
|
118
|
+
throws(() => normalizeOrderBy({ dir: "asc" }), /field\/col/);
|
|
119
|
+
});
|
|
120
|
+
test("syncModuleRuntimeState: enable/disable \u70ED\u66F4\u65B0 token+enabledSet(DIP)", async () => {
|
|
121
|
+
const { mkdtempSync, rmSync, readFileSync } = await import("node:fs");
|
|
122
|
+
const { join } = await import("node:path");
|
|
123
|
+
const { tmpdir } = await import("node:os");
|
|
124
|
+
const { syncModuleRuntimeState } = await import("./module-runtime-sync.js");
|
|
125
|
+
const { deriveToken } = await import("./module-auth.js");
|
|
126
|
+
const { unregisterBuiltinPluginForModule } = await import("./services/builtin-handlers.js");
|
|
127
|
+
const root = mkdtempSync(join(tmpdir(), "sfmc-runtime-sync-"));
|
|
128
|
+
const dbPath = join(root, "data", "sfmc_data.db");
|
|
129
|
+
try {
|
|
130
|
+
const enabledSet = /* @__PURE__ */ new Set(["feature-a"]);
|
|
131
|
+
const enabledManifests = /* @__PURE__ */ new Map();
|
|
132
|
+
const moduleAuth = { tokens: { "feature-a": "old" }, secret: "test-secret" };
|
|
133
|
+
const registry = new ServiceRegistry();
|
|
134
|
+
const fakeManifest = {
|
|
135
|
+
id: "feature-b",
|
|
136
|
+
version: "1.0.0",
|
|
137
|
+
permissions: [],
|
|
138
|
+
services: { provides: [], requires: [] },
|
|
139
|
+
db: { tables: [] },
|
|
140
|
+
config: { key: "b" }
|
|
141
|
+
};
|
|
142
|
+
syncModuleRuntimeState({
|
|
143
|
+
moduleId: "feature-b",
|
|
144
|
+
enabled: true,
|
|
145
|
+
dbPath,
|
|
146
|
+
envAuthToken: "fixed-auth",
|
|
147
|
+
enabledSet,
|
|
148
|
+
enabledManifests,
|
|
149
|
+
loadedManifest: { modules: { "feature-b": fakeManifest } },
|
|
150
|
+
moduleAuth,
|
|
151
|
+
serviceRegistry: registry,
|
|
152
|
+
builtinDeps: { query: (() => []), db: {} }
|
|
153
|
+
});
|
|
154
|
+
equal(enabledSet.has("feature-b"), true);
|
|
155
|
+
equal(enabledManifests.has("feature-b"), true);
|
|
156
|
+
equal(moduleAuth.tokens["feature-b"], deriveToken("feature-b", "test-secret"));
|
|
157
|
+
const store = JSON.parse(readFileSync(join(root, "data", "module-tokens.json"), "utf8"));
|
|
158
|
+
equal(store.tokens["feature-b"], moduleAuth.tokens["feature-b"]);
|
|
159
|
+
syncModuleRuntimeState({
|
|
160
|
+
moduleId: "feature-b",
|
|
161
|
+
enabled: false,
|
|
162
|
+
dbPath,
|
|
163
|
+
envAuthToken: "fixed-auth",
|
|
164
|
+
enabledSet,
|
|
165
|
+
enabledManifests,
|
|
166
|
+
loadedManifest: { modules: { "feature-b": fakeManifest } },
|
|
167
|
+
moduleAuth,
|
|
168
|
+
serviceRegistry: registry,
|
|
169
|
+
builtinDeps: { query: (() => []), db: {} }
|
|
170
|
+
});
|
|
171
|
+
equal(enabledSet.has("feature-b"), false);
|
|
172
|
+
equal(enabledManifests.has("feature-b"), false);
|
|
173
|
+
equal(moduleAuth.tokens["feature-b"], void 0);
|
|
174
|
+
equal(unregisterBuiltinPluginForModule(registry, "feature-b"), 0);
|
|
175
|
+
} finally {
|
|
176
|
+
rmSync(root, { recursive: true, force: true });
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
test("module-auth: ensureModuleToken / revoke \u590D\u7528 secret(DRY)", async () => {
|
|
180
|
+
const { deriveToken, ensureModuleToken, revokeModuleToken } = await import("./module-auth.js");
|
|
181
|
+
const secret = "test-secret";
|
|
182
|
+
const auth = { secret, tokens: {} };
|
|
183
|
+
equal(ensureModuleToken(auth, "feature-afk"), true);
|
|
184
|
+
equal(auth.tokens["feature-afk"], deriveToken("feature-afk", secret));
|
|
185
|
+
equal(ensureModuleToken(auth, "feature-afk"), false);
|
|
186
|
+
equal(revokeModuleToken(auth, "feature-afk"), true);
|
|
187
|
+
equal(auth.tokens["feature-afk"], void 0);
|
|
188
|
+
equal(revokeModuleToken(auth, "feature-afk"), false);
|
|
189
|
+
});
|
|
190
|
+
test("builtin-handlers: \u70ED\u7981\u7528\u6309 moduleId \u5378\u8F7D(DRY/OCP)", async () => {
|
|
191
|
+
const { unregisterBuiltinPluginForModule, registerBuiltinPluginForModule } = await import("./services/builtin-handlers.js");
|
|
192
|
+
const reg = new ServiceRegistry();
|
|
193
|
+
reg.registerHandler("feature-economy", "economy.account.get", async () => ({}));
|
|
194
|
+
reg.registerHandler("feature-economy", "economy.account.debit", async () => ({}));
|
|
195
|
+
reg.registerHandler("other-mod", "other.ping", async () => ({}));
|
|
196
|
+
equal(unregisterBuiltinPluginForModule(reg, "feature-economy"), 2);
|
|
197
|
+
equal(reg.list().length, 1);
|
|
198
|
+
equal(reg.list()[0]?.moduleId, "other-mod");
|
|
199
|
+
equal(unregisterBuiltinPluginForModule(reg, "feature-unknown"), 0);
|
|
200
|
+
equal(registerBuiltinPluginForModule(reg, { query: (() => []), db: {} }, "feature-economy"), true);
|
|
201
|
+
equal(registerBuiltinPluginForModule(reg, { query: (() => []), db: {} }, "feature-economy"), false);
|
|
202
|
+
});
|
|
203
|
+
test("TxRunner \u4EA4\u4E92\u4F1A\u8BDD: step \u4E2D\u9014\u8BFB\u56DE insert/get(PR #31 leftover)", async () => {
|
|
204
|
+
const { DatabaseSync } = await import("node:sqlite");
|
|
205
|
+
const { createQuery } = await import("./lib/sqlite.js");
|
|
206
|
+
const { SchemaRegistry } = await import("./schema-registry.js");
|
|
207
|
+
const { TxRunner } = await import("./tx-runner.js");
|
|
208
|
+
const db = new DatabaseSync(":memory:");
|
|
209
|
+
const query = createQuery(db);
|
|
210
|
+
const schema = new SchemaRegistry(db);
|
|
211
|
+
schema.define("feature-demo", {
|
|
212
|
+
name: "demo_items",
|
|
213
|
+
columns: {
|
|
214
|
+
id: { type: "text", primary: true },
|
|
215
|
+
name: { type: "text", notNull: true }
|
|
216
|
+
},
|
|
217
|
+
softDelete: false
|
|
218
|
+
});
|
|
219
|
+
const enabled = /* @__PURE__ */ new Map([
|
|
220
|
+
[
|
|
221
|
+
"feature-demo",
|
|
222
|
+
{
|
|
223
|
+
id: "feature-demo",
|
|
224
|
+
version: "1.0.0",
|
|
225
|
+
permissions: ["db:read:demo_items", "db:write:demo_items"],
|
|
226
|
+
services: { provides: [], requires: [] },
|
|
227
|
+
db: { tables: [] },
|
|
228
|
+
config: { key: "demo" }
|
|
229
|
+
}
|
|
230
|
+
]
|
|
231
|
+
]);
|
|
232
|
+
const runner = new TxRunner({
|
|
233
|
+
db,
|
|
234
|
+
query,
|
|
235
|
+
schema,
|
|
236
|
+
serviceRegistry: new ServiceRegistry(),
|
|
237
|
+
enabled
|
|
238
|
+
});
|
|
239
|
+
const begin = runner.beginSession("feature-demo");
|
|
240
|
+
equal(begin.ok, true);
|
|
241
|
+
if (!begin.ok) return;
|
|
242
|
+
const { txId } = begin;
|
|
243
|
+
const ins = await runner.stepSession(txId, "feature-demo", {
|
|
244
|
+
op: "insert",
|
|
245
|
+
table: "demo_items",
|
|
246
|
+
row: { id: "a1", name: "alpha" }
|
|
247
|
+
});
|
|
248
|
+
equal(ins.ok, true);
|
|
249
|
+
if (!ins.ok) return;
|
|
250
|
+
equal(ins.result.row.id, "a1");
|
|
251
|
+
const got = await runner.stepSession(txId, "feature-demo", {
|
|
252
|
+
op: "get",
|
|
253
|
+
table: "demo_items",
|
|
254
|
+
id: "a1"
|
|
255
|
+
});
|
|
256
|
+
equal(got.ok, true);
|
|
257
|
+
if (!got.ok) return;
|
|
258
|
+
equal(got.result.row?.name, "alpha");
|
|
259
|
+
const committed = runner.commitSession(txId, "feature-demo");
|
|
260
|
+
equal(committed.ok, true);
|
|
261
|
+
const rows = db.prepare("SELECT name FROM demo_items WHERE id = ?").all("a1");
|
|
262
|
+
equal(rows[0]?.name, "alpha");
|
|
263
|
+
db.close();
|
|
264
|
+
});
|
|
265
|
+
//# sourceMappingURL=runtime.test.js.map
|
package/dist/runtime.test.js.map
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"version": 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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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -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;
|
|
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"}
|