@objectstack/service-datasource 10.0.0 → 10.3.0

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 (37) hide show
  1. package/.turbo/turbo-build.log +28 -16
  2. package/CHANGELOG.md +100 -0
  3. package/dist/chunk-76HQ74MX.cjs +82 -0
  4. package/dist/chunk-76HQ74MX.cjs.map +1 -0
  5. package/dist/chunk-BI2SYWLC.cjs +9 -0
  6. package/dist/chunk-BI2SYWLC.cjs.map +1 -0
  7. package/dist/chunk-JRBGOCRJ.js +82 -0
  8. package/dist/chunk-JRBGOCRJ.js.map +1 -0
  9. package/dist/chunk-XLS4RP7B.js +9 -0
  10. package/dist/chunk-XLS4RP7B.js.map +1 -0
  11. package/dist/contracts/index.cjs +7 -1
  12. package/dist/contracts/index.cjs.map +1 -1
  13. package/dist/contracts/index.d.cts +59 -1
  14. package/dist/contracts/index.d.ts +59 -1
  15. package/dist/contracts/index.js +6 -0
  16. package/dist/index.cjs +284 -106
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +270 -5
  19. package/dist/index.d.ts +270 -5
  20. package/dist/index.js +216 -38
  21. package/dist/index.js.map +1 -1
  22. package/dist/sqlite-driver-fallback-BPFQYLX7.js +11 -0
  23. package/dist/sqlite-driver-fallback-BPFQYLX7.js.map +1 -0
  24. package/dist/sqlite-driver-fallback-JX4XOICD.cjs +11 -0
  25. package/dist/sqlite-driver-fallback-JX4XOICD.cjs.map +1 -0
  26. package/package.json +8 -7
  27. package/src/__tests__/datasource-connection-service.test.ts +294 -0
  28. package/src/contracts/connect-policy.ts +69 -0
  29. package/src/contracts/index.ts +11 -0
  30. package/src/datasource-admin-plugin.ts +37 -40
  31. package/src/datasource-admin-service.ts +2 -0
  32. package/src/datasource-connection-service.ts +364 -0
  33. package/src/default-datasource-driver-factory.ts +26 -9
  34. package/src/index.ts +29 -0
  35. package/src/logger.ts +2 -0
  36. package/src/sqlite-driver-fallback.test.ts +184 -0
  37. package/src/sqlite-driver-fallback.ts +195 -0
@@ -1,5 +1,5 @@
1
1
 
2
- > @objectstack/service-datasource@10.0.0 build /home/runner/work/framework/framework/packages/services/service-datasource
2
+ > @objectstack/service-datasource@10.3.0 build /home/runner/work/framework/framework/packages/services/service-datasource
3
3
  > tsup
4
4
 
5
5
  CLI Building entry: src/index.ts, src/contracts/index.ts
@@ -10,19 +10,31 @@
10
10
  CLI Cleaning output folder
11
11
  ESM Build start
12
12
  CJS Build start
13
- ESM dist/index.js 45.58 KB
14
- ESM dist/contracts/index.js 33.00 B
15
- ESM dist/index.js.map 110.99 KB
16
- ESM dist/contracts/index.js.map 71.00 B
17
- ESM ⚡️ Build success in 1186ms
18
- CJS dist/index.cjs 55.09 KB
19
- CJS dist/contracts/index.cjs 47.00 B
20
- CJS dist/index.cjs.map 93.99 KB
21
- CJS dist/contracts/index.cjs.map 265.00 B
22
- CJS ⚡️ Build success in 1190ms
13
+ ESM dist/index.js 52.67 KB
14
+ ESM dist/contracts/index.js 133.00 B
15
+ ESM dist/chunk-XLS4RP7B.js 185.00 B
16
+ ESM dist/sqlite-driver-fallback-BPFQYLX7.js 314.00 B
17
+ ESM dist/chunk-JRBGOCRJ.js 2.96 KB
18
+ ESM dist/index.js.map 130.13 KB
19
+ ESM dist/contracts/index.js.map 71.00 B
20
+ ESM dist/chunk-XLS4RP7B.js.map 3.10 KB
21
+ ESM dist/sqlite-driver-fallback-BPFQYLX7.js.map 71.00 B
22
+ ESM dist/chunk-JRBGOCRJ.js.map 9.53 KB
23
+ ESM ⚡️ Build success in 938ms
24
+ CJS dist/index.cjs 64.11 KB
25
+ CJS dist/contracts/index.cjs 242.00 B
26
+ CJS dist/chunk-BI2SYWLC.cjs 280.00 B
27
+ CJS dist/sqlite-driver-fallback-JX4XOICD.cjs 473.00 B
28
+ CJS dist/chunk-76HQ74MX.cjs 4.33 KB
29
+ CJS dist/index.cjs.map 111.29 KB
30
+ CJS dist/contracts/index.cjs.map 304.00 B
31
+ CJS dist/chunk-BI2SYWLC.cjs.map 3.38 KB
32
+ CJS dist/sqlite-driver-fallback-JX4XOICD.cjs.map 356.00 B
33
+ CJS dist/chunk-76HQ74MX.cjs.map 10.74 KB
34
+ CJS ⚡️ Build success in 1087ms
23
35
  DTS Build start
24
- DTS ⚡️ Build success in 16306ms
25
- DTS dist/index.d.ts 20.60 KB
26
- DTS dist/contracts/index.d.ts 8.10 KB
27
- DTS dist/index.d.cts 20.60 KB
28
- DTS dist/contracts/index.d.cts 8.10 KB
36
+ DTS ⚡️ Build success in 17422ms
37
+ DTS dist/index.d.ts 33.63 KB
38
+ DTS dist/contracts/index.d.ts 11.03 KB
39
+ DTS dist/index.d.cts 33.63 KB
40
+ DTS dist/contracts/index.d.cts 11.03 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,105 @@
1
1
  # @objectstack/service-external-datasource
2
2
 
3
+ ## 10.3.0
4
+
5
+ ### Patch Changes
6
+
7
+ - f2063f3: fix(cli): extend native better-sqlite3 → wasm SQLite auto-fallback to the persistent-file / `--artifact` dev path (#2229)
8
+
9
+ The native-`better-sqlite3` → wasm SQLite → in-memory step-down previously only
10
+ guarded the zero-config `:memory:` dev branch of `serve`. A normal
11
+ `objectstack dev` run never reaches it — `dev` injects a persistent `file:` DB
12
+ (so AI-authored data survives restarts) and `--artifact` boots resolve sqlite
13
+ through the datasource factory — both of which constructed
14
+ `better-sqlite3` directly with no probe and no fallback. An ABI mismatch (e.g.
15
+ a cached prebuilt binary built for a different Node version) was therefore not
16
+ caught at boot and surfaced later as a runtime `Find operation failed` on the
17
+ first query.
18
+
19
+ The probe-by-connect + step-down is now hoisted into a shared
20
+ `resolveSqliteDriver` helper (`@objectstack/service-datasource`) and applied to
21
+ both previously-unguarded sqlite construction sites: the explicit `sqlite` /
22
+ `file:` branch in `serve.ts` and the sqlite branch of the default datasource
23
+ driver factory. better-sqlite3 loads its native addon lazily (first query), so
24
+ the helper forces the load with a `SELECT 1` and, **in dev only**, steps down to
25
+ wasm SQLite (real SQL + on-disk persistence — the same `file:` keeps working)
26
+ then to the in-memory driver as a last resort, emitting the existing
27
+ `⚠ native better-sqlite3 unavailable …` warning. In production the native driver
28
+ is returned unprobed so a load failure surfaces loudly (fail-closed) rather than
29
+ silently degrading to a different engine.
30
+
31
+ - @objectstack/spec@10.3.0
32
+ - @objectstack/core@10.3.0
33
+
34
+ ## 10.2.0
35
+
36
+ ### Patch Changes
37
+
38
+ - Updated dependencies [b496498]
39
+ - @objectstack/spec@10.2.0
40
+ - @objectstack/core@10.2.0
41
+
42
+ ## 10.1.0
43
+
44
+ ### Minor Changes
45
+
46
+ - 49da36e: feat(datasource): fail-closed credential resolution at connect (ADR-0062 Phase 2, D3)
47
+
48
+ `DatasourceConnectionService` now treats a declared `external.credentialsRef` as
49
+ **fail-closed**: the credential must resolve to a cleartext secret (via the
50
+ host's `SecretBinder` over `ICryptoProvider`) _before_ the driver is built. An
51
+ absent secret store, or a ref that cannot be resolved/decrypted (missing
52
+ `sys_secret` row, rotated key, or a throwing resolver), leaves the datasource
53
+ **unconnected with a clear message** — never a silent build-without-secret that
54
+ would connect with no/wrong auth or fail later with a confusing driver error.
55
+
56
+ The same policy as connect failures applies: a code-defined `external` datasource
57
+ with `validation.onMismatch: 'fail'` auto-connected at boot fails fast (bricks
58
+ boot); runtime-admin create/update + boot rehydration degrade-with-warning. Code-
59
+ and runtime-origin secrets converge on the one connection path (the same
60
+ `SecretBinder` is threaded through the shared service). New `failed-credentials`
61
+ connect status.
62
+
63
+ - ac79f16: feat(datasource): auto-connect declared external datasources (ADR-0062 Phase 1, D1/D2/D5)
64
+
65
+ A declared external datasource is now connected to a live ObjectQL driver and its
66
+ federated objects are queryable **with zero app code** — no `onEnable` driver
67
+ wiring. Implements ADR-0062 Phase 1.
68
+
69
+ - **D1 — one connect path.** New `DatasourceConnectionService` in
70
+ `@objectstack/service-datasource` owns the single "definition → live driver"
71
+ path: build via the injected driver factory → resolve `external.credentialsRef`
72
+ via the `SecretBinder` → connect → `engine.registerDriver` under the datasource
73
+ name → register the datasource def → sync each bound federated object's read
74
+ metadata (DDL-free). Both origins converge on it: the runtime-admin
75
+ `registerPool` now delegates here, and `AppPlugin` auto-connects code-defined
76
+ datasources. Exposed as the `'datasource-connection'` kernel service.
77
+ - **D2 — opt-in-safe gate.** A declared datasource auto-connects only when it is
78
+ `external`, an object **explicitly** binds to it via `object.datasource`, or it
79
+ sets the new `autoConnect: true` flag. A managed datasource that nothing
80
+ explicitly binds (incl. ones referenced only by a `datasourceMapping` rule, e.g.
81
+ `examples/app-crm`'s `:memory:` datasources) stays metadata-only — existing apps
82
+ are byte-for-byte unchanged. See the ADR-0062 D2 implementation note.
83
+ - **D5 — lifecycle, ordering & policy.** Connect happens in `AppPlugin.start()`
84
+ (before the `kernel:ready` validation gate, relying on the kernel's
85
+ init-all-then-start-all ordering). Fail-fast for a declared `external` datasource
86
+ with `validation.onMismatch: 'fail'`; degrade-with-warning otherwise (and always
87
+ for runtime-admin/rehydrate, so a UI action or replica blip never bricks the
88
+ server). Adds a host-injectable `DatasourceConnectPolicy` (open-core default
89
+ allows; a multi-tenant host binds a stricter fail-closed policy for egress
90
+ isolation) consulted before every connect — one connect path, no cloud fork.
91
+
92
+ Adds `datasource.autoConnect` to the spec. The legacy `onEnable` +
93
+ `ctx.drivers.register` bridge remains supported as an escape hatch (idempotent vs.
94
+ auto-connect). No behavior change for managed apps.
95
+
96
+ ### Patch Changes
97
+
98
+ - Updated dependencies [49da36e]
99
+ - Updated dependencies [ac79f16]
100
+ - @objectstack/spec@10.1.0
101
+ - @objectstack/core@10.1.0
102
+
3
103
  ## 10.0.0
4
104
 
5
105
  ### Patch Changes
@@ -0,0 +1,82 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/sqlite-driver-fallback.ts
2
+ var NATIVE_SQLITE_WASM_FALLBACK_WARNING = " \u26A0 native better-sqlite3 unavailable (ABI mismatch or not built) \u2014 dev using wasm SQLite (real SQL, slower).\n Rebuild better-sqlite3 for native speed, or set OS_DATABASE_DRIVER=sqlite-wasm to silence this.";
3
+ var NATIVE_SQLITE_MEMORY_FALLBACK_WARNING = " \u26A0 neither native nor wasm SQLite available \u2014 dev falling back to InMemoryDriver (mingo, not real SQL).\n Rebuild better-sqlite3, or set OS_DATABASE_URL / OS_DATABASE_DRIVER for SQL fidelity.";
4
+ function isEphemeralFilename(filename) {
5
+ return filename === ":memory:" || filename.startsWith(":");
6
+ }
7
+ async function resolveSqliteDriver(opts) {
8
+ const { filename } = opts;
9
+ const dev = _nullishCoalesce(opts.dev, () => ( process.env.NODE_ENV === "development"));
10
+ const warn = _nullishCoalesce(opts.warn, () => ( ((message) => {
11
+ try {
12
+ console.warn(message);
13
+ } catch (e) {
14
+ }
15
+ })));
16
+ const { SqlDriver } = await Promise.resolve().then(() => _interopRequireWildcard(require("@objectstack/driver-sql")));
17
+ const buildNative = () => new SqlDriver({
18
+ client: "better-sqlite3",
19
+ connection: { filename },
20
+ useNullAsDefault: true,
21
+ ...opts.autoMigrate ? { autoMigrate: opts.autoMigrate } : {},
22
+ ...opts.schemaMode ? { schemaMode: opts.schemaMode } : {}
23
+ });
24
+ if (!dev) {
25
+ return { driver: buildNative(), engine: "better-sqlite3", label: "SqlDriver(sqlite)" };
26
+ }
27
+ let nativeDriver;
28
+ let nativeOk = false;
29
+ try {
30
+ nativeDriver = buildNative();
31
+ await nativeDriver.connect();
32
+ await nativeDriver.execute("SELECT 1");
33
+ nativeOk = true;
34
+ } catch (e2) {
35
+ nativeOk = false;
36
+ if (typeof _optionalChain([nativeDriver, 'optionalAccess', _ => _.disconnect]) === "function") {
37
+ try {
38
+ await nativeDriver.disconnect();
39
+ } catch (e3) {
40
+ }
41
+ }
42
+ }
43
+ if (nativeOk) {
44
+ return { driver: nativeDriver, engine: "better-sqlite3", label: "SqlDriver(sqlite)" };
45
+ }
46
+ let wasmDriver;
47
+ let wasmOk = false;
48
+ try {
49
+ const { SqliteWasmDriver } = await Promise.resolve().then(() => _interopRequireWildcard(require("@objectstack/driver-sqlite-wasm")));
50
+ wasmDriver = new SqliteWasmDriver({
51
+ filename,
52
+ // Match the existing construction sites: ephemeral DBs flush on
53
+ // disconnect; a persistent file flushes on every write so AI-authored
54
+ // data survives an unclean dev-server kill.
55
+ persist: isEphemeralFilename(filename) ? "on-disconnect" : "on-write"
56
+ });
57
+ await wasmDriver.connect();
58
+ wasmOk = true;
59
+ } catch (e4) {
60
+ wasmOk = false;
61
+ if (typeof _optionalChain([wasmDriver, 'optionalAccess', _2 => _2.disconnect]) === "function") {
62
+ try {
63
+ await wasmDriver.disconnect();
64
+ } catch (e5) {
65
+ }
66
+ }
67
+ }
68
+ if (wasmOk) {
69
+ warn(NATIVE_SQLITE_WASM_FALLBACK_WARNING);
70
+ return { driver: wasmDriver, engine: "sqlite-wasm", label: "SqliteWasmDriver" };
71
+ }
72
+ const { InMemoryDriver } = await Promise.resolve().then(() => _interopRequireWildcard(require("@objectstack/driver-memory")));
73
+ warn(NATIVE_SQLITE_MEMORY_FALLBACK_WARNING);
74
+ return { driver: new InMemoryDriver(), engine: "memory", label: "InMemoryDriver" };
75
+ }
76
+
77
+
78
+
79
+
80
+
81
+ exports.NATIVE_SQLITE_WASM_FALLBACK_WARNING = NATIVE_SQLITE_WASM_FALLBACK_WARNING; exports.NATIVE_SQLITE_MEMORY_FALLBACK_WARNING = NATIVE_SQLITE_MEMORY_FALLBACK_WARNING; exports.resolveSqliteDriver = resolveSqliteDriver;
82
+ //# sourceMappingURL=chunk-76HQ74MX.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/framework/framework/packages/services/service-datasource/dist/chunk-76HQ74MX.cjs","../src/sqlite-driver-fallback.ts"],"names":[],"mappings":"AAAA;AC+EO,IAAM,oCAAA,EACX,8NAAA;AAIK,IAAM,sCAAA,EACX,+MAAA;AAIF,SAAS,mBAAA,CAAoB,QAAA,EAA2B;AACtD,EAAA,OAAO,SAAA,IAAa,WAAA,GAAc,QAAA,CAAS,UAAA,CAAW,GAAG,CAAA;AAC3D;AAQA,MAAA,SAAsB,mBAAA,CACpB,IAAA,EAC+B;AAC/B,EAAA,MAAM,EAAE,SAAS,EAAA,EAAI,IAAA;AACrB,EAAA,MAAM,IAAA,mBAAM,IAAA,CAAK,GAAA,UAAO,OAAA,CAAQ,GAAA,CAAI,SAAA,IAAa,eAAA;AACjD,EAAA,MAAM,KAAA,mBACJ,IAAA,CAAK,IAAA,UAAA,CACJ,CAAC,OAAA,EAAA,GAAoB;AACpB,IAAA,IAAI;AAEF,MAAA,OAAA,CAAQ,IAAA,CAAK,OAAO,CAAA;AAAA,IACtB,EAAA,UAAQ;AAAA,IAER;AAAA,EACF,CAAA,GAAA;AAEF,EAAA,MAAM,EAAE,UAAU,EAAA,EAAI,MAAM,4DAAA,CAAO,yBAAyB,GAAA;AAE5D,EAAA,MAAM,YAAA,EAAc,CAAA,EAAA,GAClB,IAAI,SAAA,CAAU;AAAA,IACZ,MAAA,EAAQ,gBAAA;AAAA,IACR,UAAA,EAAY,EAAE,SAAS,CAAA;AAAA,IACvB,gBAAA,EAAkB,IAAA;AAAA,IAClB,GAAI,IAAA,CAAK,YAAA,EAAc,EAAE,WAAA,EAAa,IAAA,CAAK,YAAY,EAAA,EAAI,CAAC,CAAA;AAAA,IAC5D,GAAI,IAAA,CAAK,WAAA,EAAa,EAAE,UAAA,EAAY,IAAA,CAAK,WAAW,EAAA,EAAI,CAAC;AAAA,EAC3D,CAAQ,CAAA;AAKV,EAAA,GAAA,CAAI,CAAC,GAAA,EAAK;AACR,IAAA,OAAO,EAAE,MAAA,EAAQ,WAAA,CAAY,CAAA,EAAG,MAAA,EAAQ,gBAAA,EAAkB,KAAA,EAAO,oBAAoB,CAAA;AAAA,EACvF;AAKA,EAAA,IAAI,YAAA;AACJ,EAAA,IAAI,SAAA,EAAW,KAAA;AACf,EAAA,IAAI;AACF,IAAA,aAAA,EAAe,WAAA,CAAY,CAAA;AAK3B,IAAA,MAAM,YAAA,CAAa,OAAA,CAAQ,CAAA;AAC3B,IAAA,MAAM,YAAA,CAAa,OAAA,CAAQ,UAAU,CAAA;AACrC,IAAA,SAAA,EAAW,IAAA;AAAA,EACb,EAAA,WAAQ;AACN,IAAA,SAAA,EAAW,KAAA;AACX,IAAA,GAAA,CAAI,uBAAO,YAAA,2BAAc,aAAA,IAAe,UAAA,EAAY;AAClD,MAAA,IAAI;AACF,QAAA,MAAM,YAAA,CAAa,UAAA,CAAW,CAAA;AAAA,MAChC,EAAA,WAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACA,EAAA,GAAA,CAAI,QAAA,EAAU;AACZ,IAAA,OAAO,EAAE,MAAA,EAAQ,YAAA,EAAc,MAAA,EAAQ,gBAAA,EAAkB,KAAA,EAAO,oBAAoB,CAAA;AAAA,EACtF;AAGA,EAAA,IAAI,UAAA;AACJ,EAAA,IAAI,OAAA,EAAS,KAAA;AACb,EAAA,IAAI;AACF,IAAA,MAAM,EAAE,iBAAiB,EAAA,EAAI,MAAM,4DAAA,CAAO,iCAAiC,GAAA;AAC3E,IAAA,WAAA,EAAa,IAAI,gBAAA,CAAiB;AAAA,MAChC,QAAA;AAAA;AAAA;AAAA;AAAA,MAIA,OAAA,EAAS,mBAAA,CAAoB,QAAQ,EAAA,EAAI,gBAAA,EAAkB;AAAA,IAC7D,CAAQ,CAAA;AACR,IAAA,MAAM,UAAA,CAAW,OAAA,CAAQ,CAAA;AACzB,IAAA,OAAA,EAAS,IAAA;AAAA,EACX,EAAA,WAAQ;AACN,IAAA,OAAA,EAAS,KAAA;AACT,IAAA,GAAA,CAAI,uBAAO,UAAA,6BAAY,aAAA,IAAe,UAAA,EAAY;AAChD,MAAA,IAAI;AACF,QAAA,MAAM,UAAA,CAAW,UAAA,CAAW,CAAA;AAAA,MAC9B,EAAA,WAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACA,EAAA,GAAA,CAAI,MAAA,EAAQ;AACV,IAAA,IAAA,CAAK,mCAAmC,CAAA;AACxC,IAAA,OAAO,EAAE,MAAA,EAAQ,UAAA,EAAY,MAAA,EAAQ,aAAA,EAAe,KAAA,EAAO,mBAAmB,CAAA;AAAA,EAChF;AAGA,EAAA,MAAM,EAAE,eAAe,EAAA,EAAI,MAAM,4DAAA,CAAO,4BAA4B,GAAA;AACpE,EAAA,IAAA,CAAK,qCAAqC,CAAA;AAC1C,EAAA,OAAO,EAAE,MAAA,EAAQ,IAAI,cAAA,CAAe,CAAA,EAAG,MAAA,EAAQ,QAAA,EAAU,KAAA,EAAO,iBAAiB,CAAA;AACnF;ADvHA;AACA;AACE;AACA;AACA;AACF,4NAAC","file":"/home/runner/work/framework/framework/packages/services/service-datasource/dist/chunk-76HQ74MX.cjs","sourcesContent":[null,"// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Shared native-`better-sqlite3` → wasm SQLite → in-memory step-down for any\n * sqlite-via-`better-sqlite3` construction (issue #2229).\n *\n * ## Why a probe is necessary\n *\n * `better-sqlite3` loads its native `.node` addon LAZILY — not at\n * `require('better-sqlite3')`, and not even at knex construction, but at the\n * first pool-connection acquire (`new Database(file)`), i.e. the first query.\n * So an ABI mismatch (a cached prebuilt binary built for a different Node\n * version — `NODE_MODULE_VERSION` mismatch) is invisible at boot and only\n * surfaces much later as a runtime `Find operation failed` on the first read.\n *\n * This helper makes the failure observable up-front by actively probing: it\n * opens a connection and runs a cheap `SELECT 1`, which forces the native addon\n * to load. (`connect()` alone is NOT a reliable probe: for SQLite it only runs\n * `mkdir` + a `PRAGMA` whose error is swallowed internally — so we additionally\n * issue a raw `SELECT 1`, which propagates the load error.) On failure it steps\n * down:\n *\n * 1. native `better-sqlite3` — fast, real SQL\n * 2. wasm SQLite — pure-JS, real SQL + on-disk persistence, slower [dev only]\n * 3. in-memory (mingo) — neither real SQL nor persistent [dev only, last resort]\n *\n * ## Dev vs production\n *\n * The wasm + in-memory step-down is GATED to dev. In production a native load\n * failure is NOT silently swapped for a different engine: the error is re-thrown\n * so it surfaces loudly (fail-closed) instead of an operator unknowingly running\n * on wasm/mingo. This mirrors the existing `serve.ts` default-dev fallback and\n * hoists it into one place shared by every sqlite construction site.\n */\n\n/** Which engine the resolver ultimately produced. */\nexport type SqliteFallbackEngine = 'better-sqlite3' | 'sqlite-wasm' | 'memory';\n\nexport interface ResolveSqliteDriverOptions {\n /**\n * SQLite filename — `:memory:` for an ephemeral database, or an absolute /\n * relative path for a persistent file. Preserved across the wasm fallback so\n * a persistent `file:` database keeps its on-disk persistence through wasm.\n * Pass the raw filename (callers strip any `file:` / `sqlite:` scheme first).\n */\n filename: string;\n /**\n * Gates the wasm + in-memory step-down. When `true` (dev) a native ABI/load\n * failure steps down the chain with a warning. When `false` (production) the\n * native driver is returned unprobed so a failure surfaces loudly at first use\n * (fail-closed) — we never silently degrade behind the operator's back.\n * Defaults to `process.env.NODE_ENV === 'development'`.\n */\n dev?: boolean;\n /** Forwarded to the native SqlDriver (dev loosen-only self-heal, #2186). */\n autoMigrate?: 'off' | 'safe';\n /** Forwarded to the SQL drivers (external schema mode, ADR-0015). */\n schemaMode?: string;\n /**\n * Warning sink for the step-down messages. Defaults to `console.warn`.\n * `serve.ts` passes a `chalk.yellow` wrapper so the banner stays consistent.\n */\n warn?: (message: string) => void;\n}\n\nexport interface ResolvedSqliteDriver {\n /** The concrete engine driver to register (e.g. via `DriverPlugin`). */\n driver: any;\n /** Which engine actually resolved. */\n engine: SqliteFallbackEngine;\n /** Banner label, matching `serve.ts`'s existing strings. */\n label: string;\n}\n\n/**\n * Warning emitted when native `better-sqlite3` is unavailable but wasm SQLite\n * loads. Kept byte-for-byte identical to the original `serve.ts` text so the\n * dev experience is the same regardless of which construction site triggers it.\n */\nexport const NATIVE_SQLITE_WASM_FALLBACK_WARNING =\n ' ⚠ native better-sqlite3 unavailable (ABI mismatch or not built) — dev using wasm SQLite (real SQL, slower).\\n' +\n ' Rebuild better-sqlite3 for native speed, or set OS_DATABASE_DRIVER=sqlite-wasm to silence this.';\n\n/** Warning emitted when neither native nor wasm SQLite loads (dev last resort). */\nexport const NATIVE_SQLITE_MEMORY_FALLBACK_WARNING =\n ' ⚠ neither native nor wasm SQLite available — dev falling back to InMemoryDriver (mingo, not real SQL).\\n' +\n ' Rebuild better-sqlite3, or set OS_DATABASE_URL / OS_DATABASE_DRIVER for SQL fidelity.';\n\n/** `:memory:` and other `:`-prefixed pseudo-filenames are never persisted. */\nfunction isEphemeralFilename(filename: string): boolean {\n return filename === ':memory:' || filename.startsWith(':');\n}\n\n/**\n * Probe a `better-sqlite3` SQLite construction and, in dev, step down to wasm\n * SQLite (then in-memory) when the native addon cannot load.\n *\n * @see {@link ResolveSqliteDriverOptions}\n */\nexport async function resolveSqliteDriver(\n opts: ResolveSqliteDriverOptions,\n): Promise<ResolvedSqliteDriver> {\n const { filename } = opts;\n const dev = opts.dev ?? process.env.NODE_ENV === 'development';\n const warn =\n opts.warn ??\n ((message: string) => {\n try {\n // eslint-disable-next-line no-console\n console.warn(message);\n } catch {\n /* ignore */\n }\n });\n\n const { SqlDriver } = await import('@objectstack/driver-sql');\n\n const buildNative = () =>\n new SqlDriver({\n client: 'better-sqlite3',\n connection: { filename },\n useNullAsDefault: true,\n ...(opts.autoMigrate ? { autoMigrate: opts.autoMigrate } : {}),\n ...(opts.schemaMode ? { schemaMode: opts.schemaMode } : {}),\n } as any);\n\n // Production: never silently swap engines. Construct the native driver and\n // hand it back UNPROBED — exactly the historical behavior. A native load\n // failure surfaces loudly at first use (fail-closed).\n if (!dev) {\n return { driver: buildNative(), engine: 'better-sqlite3', label: 'SqlDriver(sqlite)' };\n }\n\n // ── Dev: probe-by-connect, step down on native ABI/load failure. ──────────\n\n // 1. Native better-sqlite3.\n let nativeDriver: any;\n let nativeOk = false;\n try {\n nativeDriver = buildNative();\n // connect() runs mkdir (so a SELECT on a file DB whose dir is missing does\n // not false-positive as an ABI failure) + a PRAGMA whose error it swallows;\n // the raw SELECT 1 below is what reliably forces the native addon to load\n // and PROPAGATES an ABI mismatch.\n await nativeDriver.connect();\n await nativeDriver.execute('SELECT 1');\n nativeOk = true;\n } catch {\n nativeOk = false;\n if (typeof nativeDriver?.disconnect === 'function') {\n try {\n await nativeDriver.disconnect();\n } catch {\n /* ignore */\n }\n }\n }\n if (nativeOk) {\n return { driver: nativeDriver, engine: 'better-sqlite3', label: 'SqlDriver(sqlite)' };\n }\n\n // 2. wasm SQLite — real SQL semantics + on-disk persistence, no native build.\n let wasmDriver: any;\n let wasmOk = false;\n try {\n const { SqliteWasmDriver } = await import('@objectstack/driver-sqlite-wasm');\n wasmDriver = new SqliteWasmDriver({\n filename,\n // Match the existing construction sites: ephemeral DBs flush on\n // disconnect; a persistent file flushes on every write so AI-authored\n // data survives an unclean dev-server kill.\n persist: isEphemeralFilename(filename) ? 'on-disconnect' : 'on-write',\n } as any);\n await wasmDriver.connect();\n wasmOk = true;\n } catch {\n wasmOk = false;\n if (typeof wasmDriver?.disconnect === 'function') {\n try {\n await wasmDriver.disconnect();\n } catch {\n /* ignore */\n }\n }\n }\n if (wasmOk) {\n warn(NATIVE_SQLITE_WASM_FALLBACK_WARNING);\n return { driver: wasmDriver, engine: 'sqlite-wasm', label: 'SqliteWasmDriver' };\n }\n\n // 3. In-memory (mingo) — dev-only last resort. Not real SQL, not persistent.\n const { InMemoryDriver } = await import('@objectstack/driver-memory');\n warn(NATIVE_SQLITE_MEMORY_FALLBACK_WARNING);\n return { driver: new InMemoryDriver(), engine: 'memory', label: 'InMemoryDriver' };\n}\n"]}
@@ -0,0 +1,9 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});// src/contracts/connect-policy.ts
2
+ var allowAllConnectPolicy = {
3
+ canConnect: () => ({ allow: true })
4
+ };
5
+
6
+
7
+
8
+ exports.allowAllConnectPolicy = allowAllConnectPolicy;
9
+ //# sourceMappingURL=chunk-BI2SYWLC.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/framework/framework/packages/services/service-datasource/dist/chunk-BI2SYWLC.cjs","../src/contracts/connect-policy.ts"],"names":[],"mappings":"AAAA;ACkEO,IAAM,sBAAA,EAAiD;AAAA,EAC5D,UAAA,EAAY,CAAA,EAAA,GAAA,CAAO,EAAE,KAAA,EAAO,KAAK,CAAA;AACnC,CAAA;ADhEA;AACA;AACE;AACF,sDAAC","file":"/home/runner/work/framework/framework/packages/services/service-datasource/dist/chunk-BI2SYWLC.cjs","sourcesContent":[null,"// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * DatasourceConnectPolicy — host-injectable gate consulted *before*\n * {@link DatasourceConnectionService} builds and registers a live driver\n * (ADR-0062 D1/D5, and the epic #2163 \"connect-policy seam\" note).\n *\n * The framework ships a permissive default ({@link allowAllConnectPolicy}) so a\n * self-hosted single-environment runtime connects external datasources out of\n * the box (subject to the D2 auto-connect gate, which is applied separately by\n * {@link DatasourceConnectionService.connectDeclared}). A multi-tenant host\n * (shared-container cloud) binds a stricter policy that can *fail-close* on the\n * shared runtime — e.g. checking `sys_environment.plan`, an egress allow-list,\n * and per-tenant quota — to enforce SSRF / egress isolation.\n *\n * This keeps a single connect path for code- and runtime-origin datasources\n * (D1): the host injects a policy rather than forking a second connect path.\n * No plan coupling lives in the open framework.\n */\n\n/** Why a connect is being attempted — lets a policy treat origins differently. */\nexport interface DatasourceConnectContext {\n /** Provenance of the datasource being connected. */\n origin?: 'code' | 'runtime';\n /**\n * What triggered this connect:\n * - `declared-auto` — code-defined datasource auto-connected at boot (D2 gate passed).\n * - `runtime-admin` — UI \"Add/Update Datasource\" hot pool registration.\n * - `rehydrate` — boot rehydration of a persisted runtime datasource.\n */\n trigger?: 'declared-auto' | 'runtime-admin' | 'rehydrate';\n}\n\n/** A policy verdict. `allow:false` leaves the datasource unconnected (metadata-only). */\nexport interface DatasourceConnectDecision {\n allow: boolean;\n /** Human-readable reason, surfaced in logs when a connect is denied. */\n reason?: string;\n}\n\n/** The minimal datasource shape a policy inspects (never a secret). */\nexport interface DatasourceConnectSubject {\n name: string;\n driver: string;\n schemaMode?: 'managed' | 'external' | 'validate-only';\n external?: Record<string, unknown>;\n}\n\n/** Host-provided policy gate consulted before opening a connection. */\nexport interface DatasourceConnectPolicy {\n /**\n * Decide whether `ds` may be connected. Sync or async. Throwing is treated\n * as a denial (fail-closed) by {@link DatasourceConnectionService}.\n */\n canConnect(\n ds: DatasourceConnectSubject,\n ctx?: DatasourceConnectContext,\n ): DatasourceConnectDecision | Promise<DatasourceConnectDecision>;\n}\n\n/**\n * Open-core default: allow every connect. The D2 auto-connect gate (external /\n * explicitly-routed / `autoConnect:true`) still applies on top of this for\n * code-defined datasources, so a managed, unrouted datasource is never\n * connected even under the permissive policy.\n */\nexport const allowAllConnectPolicy: DatasourceConnectPolicy = {\n canConnect: () => ({ allow: true }),\n};\n"]}
@@ -0,0 +1,82 @@
1
+ // src/sqlite-driver-fallback.ts
2
+ var NATIVE_SQLITE_WASM_FALLBACK_WARNING = " \u26A0 native better-sqlite3 unavailable (ABI mismatch or not built) \u2014 dev using wasm SQLite (real SQL, slower).\n Rebuild better-sqlite3 for native speed, or set OS_DATABASE_DRIVER=sqlite-wasm to silence this.";
3
+ var NATIVE_SQLITE_MEMORY_FALLBACK_WARNING = " \u26A0 neither native nor wasm SQLite available \u2014 dev falling back to InMemoryDriver (mingo, not real SQL).\n Rebuild better-sqlite3, or set OS_DATABASE_URL / OS_DATABASE_DRIVER for SQL fidelity.";
4
+ function isEphemeralFilename(filename) {
5
+ return filename === ":memory:" || filename.startsWith(":");
6
+ }
7
+ async function resolveSqliteDriver(opts) {
8
+ const { filename } = opts;
9
+ const dev = opts.dev ?? process.env.NODE_ENV === "development";
10
+ const warn = opts.warn ?? ((message) => {
11
+ try {
12
+ console.warn(message);
13
+ } catch {
14
+ }
15
+ });
16
+ const { SqlDriver } = await import("@objectstack/driver-sql");
17
+ const buildNative = () => new SqlDriver({
18
+ client: "better-sqlite3",
19
+ connection: { filename },
20
+ useNullAsDefault: true,
21
+ ...opts.autoMigrate ? { autoMigrate: opts.autoMigrate } : {},
22
+ ...opts.schemaMode ? { schemaMode: opts.schemaMode } : {}
23
+ });
24
+ if (!dev) {
25
+ return { driver: buildNative(), engine: "better-sqlite3", label: "SqlDriver(sqlite)" };
26
+ }
27
+ let nativeDriver;
28
+ let nativeOk = false;
29
+ try {
30
+ nativeDriver = buildNative();
31
+ await nativeDriver.connect();
32
+ await nativeDriver.execute("SELECT 1");
33
+ nativeOk = true;
34
+ } catch {
35
+ nativeOk = false;
36
+ if (typeof nativeDriver?.disconnect === "function") {
37
+ try {
38
+ await nativeDriver.disconnect();
39
+ } catch {
40
+ }
41
+ }
42
+ }
43
+ if (nativeOk) {
44
+ return { driver: nativeDriver, engine: "better-sqlite3", label: "SqlDriver(sqlite)" };
45
+ }
46
+ let wasmDriver;
47
+ let wasmOk = false;
48
+ try {
49
+ const { SqliteWasmDriver } = await import("@objectstack/driver-sqlite-wasm");
50
+ wasmDriver = new SqliteWasmDriver({
51
+ filename,
52
+ // Match the existing construction sites: ephemeral DBs flush on
53
+ // disconnect; a persistent file flushes on every write so AI-authored
54
+ // data survives an unclean dev-server kill.
55
+ persist: isEphemeralFilename(filename) ? "on-disconnect" : "on-write"
56
+ });
57
+ await wasmDriver.connect();
58
+ wasmOk = true;
59
+ } catch {
60
+ wasmOk = false;
61
+ if (typeof wasmDriver?.disconnect === "function") {
62
+ try {
63
+ await wasmDriver.disconnect();
64
+ } catch {
65
+ }
66
+ }
67
+ }
68
+ if (wasmOk) {
69
+ warn(NATIVE_SQLITE_WASM_FALLBACK_WARNING);
70
+ return { driver: wasmDriver, engine: "sqlite-wasm", label: "SqliteWasmDriver" };
71
+ }
72
+ const { InMemoryDriver } = await import("@objectstack/driver-memory");
73
+ warn(NATIVE_SQLITE_MEMORY_FALLBACK_WARNING);
74
+ return { driver: new InMemoryDriver(), engine: "memory", label: "InMemoryDriver" };
75
+ }
76
+
77
+ export {
78
+ NATIVE_SQLITE_WASM_FALLBACK_WARNING,
79
+ NATIVE_SQLITE_MEMORY_FALLBACK_WARNING,
80
+ resolveSqliteDriver
81
+ };
82
+ //# sourceMappingURL=chunk-JRBGOCRJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/sqlite-driver-fallback.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Shared native-`better-sqlite3` → wasm SQLite → in-memory step-down for any\n * sqlite-via-`better-sqlite3` construction (issue #2229).\n *\n * ## Why a probe is necessary\n *\n * `better-sqlite3` loads its native `.node` addon LAZILY — not at\n * `require('better-sqlite3')`, and not even at knex construction, but at the\n * first pool-connection acquire (`new Database(file)`), i.e. the first query.\n * So an ABI mismatch (a cached prebuilt binary built for a different Node\n * version — `NODE_MODULE_VERSION` mismatch) is invisible at boot and only\n * surfaces much later as a runtime `Find operation failed` on the first read.\n *\n * This helper makes the failure observable up-front by actively probing: it\n * opens a connection and runs a cheap `SELECT 1`, which forces the native addon\n * to load. (`connect()` alone is NOT a reliable probe: for SQLite it only runs\n * `mkdir` + a `PRAGMA` whose error is swallowed internally — so we additionally\n * issue a raw `SELECT 1`, which propagates the load error.) On failure it steps\n * down:\n *\n * 1. native `better-sqlite3` — fast, real SQL\n * 2. wasm SQLite — pure-JS, real SQL + on-disk persistence, slower [dev only]\n * 3. in-memory (mingo) — neither real SQL nor persistent [dev only, last resort]\n *\n * ## Dev vs production\n *\n * The wasm + in-memory step-down is GATED to dev. In production a native load\n * failure is NOT silently swapped for a different engine: the error is re-thrown\n * so it surfaces loudly (fail-closed) instead of an operator unknowingly running\n * on wasm/mingo. This mirrors the existing `serve.ts` default-dev fallback and\n * hoists it into one place shared by every sqlite construction site.\n */\n\n/** Which engine the resolver ultimately produced. */\nexport type SqliteFallbackEngine = 'better-sqlite3' | 'sqlite-wasm' | 'memory';\n\nexport interface ResolveSqliteDriverOptions {\n /**\n * SQLite filename — `:memory:` for an ephemeral database, or an absolute /\n * relative path for a persistent file. Preserved across the wasm fallback so\n * a persistent `file:` database keeps its on-disk persistence through wasm.\n * Pass the raw filename (callers strip any `file:` / `sqlite:` scheme first).\n */\n filename: string;\n /**\n * Gates the wasm + in-memory step-down. When `true` (dev) a native ABI/load\n * failure steps down the chain with a warning. When `false` (production) the\n * native driver is returned unprobed so a failure surfaces loudly at first use\n * (fail-closed) — we never silently degrade behind the operator's back.\n * Defaults to `process.env.NODE_ENV === 'development'`.\n */\n dev?: boolean;\n /** Forwarded to the native SqlDriver (dev loosen-only self-heal, #2186). */\n autoMigrate?: 'off' | 'safe';\n /** Forwarded to the SQL drivers (external schema mode, ADR-0015). */\n schemaMode?: string;\n /**\n * Warning sink for the step-down messages. Defaults to `console.warn`.\n * `serve.ts` passes a `chalk.yellow` wrapper so the banner stays consistent.\n */\n warn?: (message: string) => void;\n}\n\nexport interface ResolvedSqliteDriver {\n /** The concrete engine driver to register (e.g. via `DriverPlugin`). */\n driver: any;\n /** Which engine actually resolved. */\n engine: SqliteFallbackEngine;\n /** Banner label, matching `serve.ts`'s existing strings. */\n label: string;\n}\n\n/**\n * Warning emitted when native `better-sqlite3` is unavailable but wasm SQLite\n * loads. Kept byte-for-byte identical to the original `serve.ts` text so the\n * dev experience is the same regardless of which construction site triggers it.\n */\nexport const NATIVE_SQLITE_WASM_FALLBACK_WARNING =\n ' ⚠ native better-sqlite3 unavailable (ABI mismatch or not built) — dev using wasm SQLite (real SQL, slower).\\n' +\n ' Rebuild better-sqlite3 for native speed, or set OS_DATABASE_DRIVER=sqlite-wasm to silence this.';\n\n/** Warning emitted when neither native nor wasm SQLite loads (dev last resort). */\nexport const NATIVE_SQLITE_MEMORY_FALLBACK_WARNING =\n ' ⚠ neither native nor wasm SQLite available — dev falling back to InMemoryDriver (mingo, not real SQL).\\n' +\n ' Rebuild better-sqlite3, or set OS_DATABASE_URL / OS_DATABASE_DRIVER for SQL fidelity.';\n\n/** `:memory:` and other `:`-prefixed pseudo-filenames are never persisted. */\nfunction isEphemeralFilename(filename: string): boolean {\n return filename === ':memory:' || filename.startsWith(':');\n}\n\n/**\n * Probe a `better-sqlite3` SQLite construction and, in dev, step down to wasm\n * SQLite (then in-memory) when the native addon cannot load.\n *\n * @see {@link ResolveSqliteDriverOptions}\n */\nexport async function resolveSqliteDriver(\n opts: ResolveSqliteDriverOptions,\n): Promise<ResolvedSqliteDriver> {\n const { filename } = opts;\n const dev = opts.dev ?? process.env.NODE_ENV === 'development';\n const warn =\n opts.warn ??\n ((message: string) => {\n try {\n // eslint-disable-next-line no-console\n console.warn(message);\n } catch {\n /* ignore */\n }\n });\n\n const { SqlDriver } = await import('@objectstack/driver-sql');\n\n const buildNative = () =>\n new SqlDriver({\n client: 'better-sqlite3',\n connection: { filename },\n useNullAsDefault: true,\n ...(opts.autoMigrate ? { autoMigrate: opts.autoMigrate } : {}),\n ...(opts.schemaMode ? { schemaMode: opts.schemaMode } : {}),\n } as any);\n\n // Production: never silently swap engines. Construct the native driver and\n // hand it back UNPROBED — exactly the historical behavior. A native load\n // failure surfaces loudly at first use (fail-closed).\n if (!dev) {\n return { driver: buildNative(), engine: 'better-sqlite3', label: 'SqlDriver(sqlite)' };\n }\n\n // ── Dev: probe-by-connect, step down on native ABI/load failure. ──────────\n\n // 1. Native better-sqlite3.\n let nativeDriver: any;\n let nativeOk = false;\n try {\n nativeDriver = buildNative();\n // connect() runs mkdir (so a SELECT on a file DB whose dir is missing does\n // not false-positive as an ABI failure) + a PRAGMA whose error it swallows;\n // the raw SELECT 1 below is what reliably forces the native addon to load\n // and PROPAGATES an ABI mismatch.\n await nativeDriver.connect();\n await nativeDriver.execute('SELECT 1');\n nativeOk = true;\n } catch {\n nativeOk = false;\n if (typeof nativeDriver?.disconnect === 'function') {\n try {\n await nativeDriver.disconnect();\n } catch {\n /* ignore */\n }\n }\n }\n if (nativeOk) {\n return { driver: nativeDriver, engine: 'better-sqlite3', label: 'SqlDriver(sqlite)' };\n }\n\n // 2. wasm SQLite — real SQL semantics + on-disk persistence, no native build.\n let wasmDriver: any;\n let wasmOk = false;\n try {\n const { SqliteWasmDriver } = await import('@objectstack/driver-sqlite-wasm');\n wasmDriver = new SqliteWasmDriver({\n filename,\n // Match the existing construction sites: ephemeral DBs flush on\n // disconnect; a persistent file flushes on every write so AI-authored\n // data survives an unclean dev-server kill.\n persist: isEphemeralFilename(filename) ? 'on-disconnect' : 'on-write',\n } as any);\n await wasmDriver.connect();\n wasmOk = true;\n } catch {\n wasmOk = false;\n if (typeof wasmDriver?.disconnect === 'function') {\n try {\n await wasmDriver.disconnect();\n } catch {\n /* ignore */\n }\n }\n }\n if (wasmOk) {\n warn(NATIVE_SQLITE_WASM_FALLBACK_WARNING);\n return { driver: wasmDriver, engine: 'sqlite-wasm', label: 'SqliteWasmDriver' };\n }\n\n // 3. In-memory (mingo) — dev-only last resort. Not real SQL, not persistent.\n const { InMemoryDriver } = await import('@objectstack/driver-memory');\n warn(NATIVE_SQLITE_MEMORY_FALLBACK_WARNING);\n return { driver: new InMemoryDriver(), engine: 'memory', label: 'InMemoryDriver' };\n}\n"],"mappings":";AA+EO,IAAM,sCACX;AAIK,IAAM,wCACX;AAIF,SAAS,oBAAoB,UAA2B;AACtD,SAAO,aAAa,cAAc,SAAS,WAAW,GAAG;AAC3D;AAQA,eAAsB,oBACpB,MAC+B;AAC/B,QAAM,EAAE,SAAS,IAAI;AACrB,QAAM,MAAM,KAAK,OAAO,QAAQ,IAAI,aAAa;AACjD,QAAM,OACJ,KAAK,SACJ,CAAC,YAAoB;AACpB,QAAI;AAEF,cAAQ,KAAK,OAAO;AAAA,IACtB,QAAQ;AAAA,IAER;AAAA,EACF;AAEF,QAAM,EAAE,UAAU,IAAI,MAAM,OAAO,yBAAyB;AAE5D,QAAM,cAAc,MAClB,IAAI,UAAU;AAAA,IACZ,QAAQ;AAAA,IACR,YAAY,EAAE,SAAS;AAAA,IACvB,kBAAkB;AAAA,IAClB,GAAI,KAAK,cAAc,EAAE,aAAa,KAAK,YAAY,IAAI,CAAC;AAAA,IAC5D,GAAI,KAAK,aAAa,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;AAAA,EAC3D,CAAQ;AAKV,MAAI,CAAC,KAAK;AACR,WAAO,EAAE,QAAQ,YAAY,GAAG,QAAQ,kBAAkB,OAAO,oBAAoB;AAAA,EACvF;AAKA,MAAI;AACJ,MAAI,WAAW;AACf,MAAI;AACF,mBAAe,YAAY;AAK3B,UAAM,aAAa,QAAQ;AAC3B,UAAM,aAAa,QAAQ,UAAU;AACrC,eAAW;AAAA,EACb,QAAQ;AACN,eAAW;AACX,QAAI,OAAO,cAAc,eAAe,YAAY;AAClD,UAAI;AACF,cAAM,aAAa,WAAW;AAAA,MAChC,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACA,MAAI,UAAU;AACZ,WAAO,EAAE,QAAQ,cAAc,QAAQ,kBAAkB,OAAO,oBAAoB;AAAA,EACtF;AAGA,MAAI;AACJ,MAAI,SAAS;AACb,MAAI;AACF,UAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,iCAAiC;AAC3E,iBAAa,IAAI,iBAAiB;AAAA,MAChC;AAAA;AAAA;AAAA;AAAA,MAIA,SAAS,oBAAoB,QAAQ,IAAI,kBAAkB;AAAA,IAC7D,CAAQ;AACR,UAAM,WAAW,QAAQ;AACzB,aAAS;AAAA,EACX,QAAQ;AACN,aAAS;AACT,QAAI,OAAO,YAAY,eAAe,YAAY;AAChD,UAAI;AACF,cAAM,WAAW,WAAW;AAAA,MAC9B,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACA,MAAI,QAAQ;AACV,SAAK,mCAAmC;AACxC,WAAO,EAAE,QAAQ,YAAY,QAAQ,eAAe,OAAO,mBAAmB;AAAA,EAChF;AAGA,QAAM,EAAE,eAAe,IAAI,MAAM,OAAO,4BAA4B;AACpE,OAAK,qCAAqC;AAC1C,SAAO,EAAE,QAAQ,IAAI,eAAe,GAAG,QAAQ,UAAU,OAAO,iBAAiB;AACnF;","names":[]}
@@ -0,0 +1,9 @@
1
+ // src/contracts/connect-policy.ts
2
+ var allowAllConnectPolicy = {
3
+ canConnect: () => ({ allow: true })
4
+ };
5
+
6
+ export {
7
+ allowAllConnectPolicy
8
+ };
9
+ //# sourceMappingURL=chunk-XLS4RP7B.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/contracts/connect-policy.ts"],"sourcesContent":["// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * DatasourceConnectPolicy — host-injectable gate consulted *before*\n * {@link DatasourceConnectionService} builds and registers a live driver\n * (ADR-0062 D1/D5, and the epic #2163 \"connect-policy seam\" note).\n *\n * The framework ships a permissive default ({@link allowAllConnectPolicy}) so a\n * self-hosted single-environment runtime connects external datasources out of\n * the box (subject to the D2 auto-connect gate, which is applied separately by\n * {@link DatasourceConnectionService.connectDeclared}). A multi-tenant host\n * (shared-container cloud) binds a stricter policy that can *fail-close* on the\n * shared runtime — e.g. checking `sys_environment.plan`, an egress allow-list,\n * and per-tenant quota — to enforce SSRF / egress isolation.\n *\n * This keeps a single connect path for code- and runtime-origin datasources\n * (D1): the host injects a policy rather than forking a second connect path.\n * No plan coupling lives in the open framework.\n */\n\n/** Why a connect is being attempted — lets a policy treat origins differently. */\nexport interface DatasourceConnectContext {\n /** Provenance of the datasource being connected. */\n origin?: 'code' | 'runtime';\n /**\n * What triggered this connect:\n * - `declared-auto` — code-defined datasource auto-connected at boot (D2 gate passed).\n * - `runtime-admin` — UI \"Add/Update Datasource\" hot pool registration.\n * - `rehydrate` — boot rehydration of a persisted runtime datasource.\n */\n trigger?: 'declared-auto' | 'runtime-admin' | 'rehydrate';\n}\n\n/** A policy verdict. `allow:false` leaves the datasource unconnected (metadata-only). */\nexport interface DatasourceConnectDecision {\n allow: boolean;\n /** Human-readable reason, surfaced in logs when a connect is denied. */\n reason?: string;\n}\n\n/** The minimal datasource shape a policy inspects (never a secret). */\nexport interface DatasourceConnectSubject {\n name: string;\n driver: string;\n schemaMode?: 'managed' | 'external' | 'validate-only';\n external?: Record<string, unknown>;\n}\n\n/** Host-provided policy gate consulted before opening a connection. */\nexport interface DatasourceConnectPolicy {\n /**\n * Decide whether `ds` may be connected. Sync or async. Throwing is treated\n * as a denial (fail-closed) by {@link DatasourceConnectionService}.\n */\n canConnect(\n ds: DatasourceConnectSubject,\n ctx?: DatasourceConnectContext,\n ): DatasourceConnectDecision | Promise<DatasourceConnectDecision>;\n}\n\n/**\n * Open-core default: allow every connect. The D2 auto-connect gate (external /\n * explicitly-routed / `autoConnect:true`) still applies on top of this for\n * code-defined datasources, so a managed, unrouted datasource is never\n * connected even under the permissive policy.\n */\nexport const allowAllConnectPolicy: DatasourceConnectPolicy = {\n canConnect: () => ({ allow: true }),\n};\n"],"mappings":";AAkEO,IAAM,wBAAiD;AAAA,EAC5D,YAAY,OAAO,EAAE,OAAO,KAAK;AACnC;","names":[]}
@@ -1 +1,7 @@
1
- "use strict";//# sourceMappingURL=index.cjs.map
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+ var _chunkBI2SYWLCcjs = require('../chunk-BI2SYWLC.cjs');
4
+
5
+
6
+ exports.allowAllConnectPolicy = _chunkBI2SYWLCcjs.allowAllConnectPolicy;
7
+ //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/framework/framework/packages/services/service-datasource/dist/contracts/index.cjs"],"names":[],"mappings":"AAAA","file":"/home/runner/work/framework/framework/packages/services/service-datasource/dist/contracts/index.cjs"}
1
+ {"version":3,"sources":["/home/runner/work/framework/framework/packages/services/service-datasource/dist/contracts/index.cjs"],"names":[],"mappings":"AAAA;AACE;AACF,yDAA8B;AAC9B;AACE;AACF,wEAAC","file":"/home/runner/work/framework/framework/packages/services/service-datasource/dist/contracts/index.cjs"}
@@ -175,4 +175,62 @@ interface IDatasourceDriverFactory {
175
175
  create(spec: DatasourceConnectionSpec): Promise<DatasourceDriverHandle> | DatasourceDriverHandle;
176
176
  }
177
177
 
178
- export type { DatasourceConnectionSpec, DatasourceDraft, DatasourceDriverHandle, DatasourceOrigin, DatasourceSummary, IDatasourceAdminService, IDatasourceDriverFactory, SecretInput, TestConnectionResult };
178
+ /**
179
+ * DatasourceConnectPolicy — host-injectable gate consulted *before*
180
+ * {@link DatasourceConnectionService} builds and registers a live driver
181
+ * (ADR-0062 D1/D5, and the epic #2163 "connect-policy seam" note).
182
+ *
183
+ * The framework ships a permissive default ({@link allowAllConnectPolicy}) so a
184
+ * self-hosted single-environment runtime connects external datasources out of
185
+ * the box (subject to the D2 auto-connect gate, which is applied separately by
186
+ * {@link DatasourceConnectionService.connectDeclared}). A multi-tenant host
187
+ * (shared-container cloud) binds a stricter policy that can *fail-close* on the
188
+ * shared runtime — e.g. checking `sys_environment.plan`, an egress allow-list,
189
+ * and per-tenant quota — to enforce SSRF / egress isolation.
190
+ *
191
+ * This keeps a single connect path for code- and runtime-origin datasources
192
+ * (D1): the host injects a policy rather than forking a second connect path.
193
+ * No plan coupling lives in the open framework.
194
+ */
195
+ /** Why a connect is being attempted — lets a policy treat origins differently. */
196
+ interface DatasourceConnectContext {
197
+ /** Provenance of the datasource being connected. */
198
+ origin?: 'code' | 'runtime';
199
+ /**
200
+ * What triggered this connect:
201
+ * - `declared-auto` — code-defined datasource auto-connected at boot (D2 gate passed).
202
+ * - `runtime-admin` — UI "Add/Update Datasource" hot pool registration.
203
+ * - `rehydrate` — boot rehydration of a persisted runtime datasource.
204
+ */
205
+ trigger?: 'declared-auto' | 'runtime-admin' | 'rehydrate';
206
+ }
207
+ /** A policy verdict. `allow:false` leaves the datasource unconnected (metadata-only). */
208
+ interface DatasourceConnectDecision {
209
+ allow: boolean;
210
+ /** Human-readable reason, surfaced in logs when a connect is denied. */
211
+ reason?: string;
212
+ }
213
+ /** The minimal datasource shape a policy inspects (never a secret). */
214
+ interface DatasourceConnectSubject {
215
+ name: string;
216
+ driver: string;
217
+ schemaMode?: 'managed' | 'external' | 'validate-only';
218
+ external?: Record<string, unknown>;
219
+ }
220
+ /** Host-provided policy gate consulted before opening a connection. */
221
+ interface DatasourceConnectPolicy {
222
+ /**
223
+ * Decide whether `ds` may be connected. Sync or async. Throwing is treated
224
+ * as a denial (fail-closed) by {@link DatasourceConnectionService}.
225
+ */
226
+ canConnect(ds: DatasourceConnectSubject, ctx?: DatasourceConnectContext): DatasourceConnectDecision | Promise<DatasourceConnectDecision>;
227
+ }
228
+ /**
229
+ * Open-core default: allow every connect. The D2 auto-connect gate (external /
230
+ * explicitly-routed / `autoConnect:true`) still applies on top of this for
231
+ * code-defined datasources, so a managed, unrouted datasource is never
232
+ * connected even under the permissive policy.
233
+ */
234
+ declare const allowAllConnectPolicy: DatasourceConnectPolicy;
235
+
236
+ export { type DatasourceConnectContext, type DatasourceConnectDecision, type DatasourceConnectPolicy, type DatasourceConnectSubject, type DatasourceConnectionSpec, type DatasourceDraft, type DatasourceDriverHandle, type DatasourceOrigin, type DatasourceSummary, type IDatasourceAdminService, type IDatasourceDriverFactory, type SecretInput, type TestConnectionResult, allowAllConnectPolicy };
@@ -175,4 +175,62 @@ interface IDatasourceDriverFactory {
175
175
  create(spec: DatasourceConnectionSpec): Promise<DatasourceDriverHandle> | DatasourceDriverHandle;
176
176
  }
177
177
 
178
- export type { DatasourceConnectionSpec, DatasourceDraft, DatasourceDriverHandle, DatasourceOrigin, DatasourceSummary, IDatasourceAdminService, IDatasourceDriverFactory, SecretInput, TestConnectionResult };
178
+ /**
179
+ * DatasourceConnectPolicy — host-injectable gate consulted *before*
180
+ * {@link DatasourceConnectionService} builds and registers a live driver
181
+ * (ADR-0062 D1/D5, and the epic #2163 "connect-policy seam" note).
182
+ *
183
+ * The framework ships a permissive default ({@link allowAllConnectPolicy}) so a
184
+ * self-hosted single-environment runtime connects external datasources out of
185
+ * the box (subject to the D2 auto-connect gate, which is applied separately by
186
+ * {@link DatasourceConnectionService.connectDeclared}). A multi-tenant host
187
+ * (shared-container cloud) binds a stricter policy that can *fail-close* on the
188
+ * shared runtime — e.g. checking `sys_environment.plan`, an egress allow-list,
189
+ * and per-tenant quota — to enforce SSRF / egress isolation.
190
+ *
191
+ * This keeps a single connect path for code- and runtime-origin datasources
192
+ * (D1): the host injects a policy rather than forking a second connect path.
193
+ * No plan coupling lives in the open framework.
194
+ */
195
+ /** Why a connect is being attempted — lets a policy treat origins differently. */
196
+ interface DatasourceConnectContext {
197
+ /** Provenance of the datasource being connected. */
198
+ origin?: 'code' | 'runtime';
199
+ /**
200
+ * What triggered this connect:
201
+ * - `declared-auto` — code-defined datasource auto-connected at boot (D2 gate passed).
202
+ * - `runtime-admin` — UI "Add/Update Datasource" hot pool registration.
203
+ * - `rehydrate` — boot rehydration of a persisted runtime datasource.
204
+ */
205
+ trigger?: 'declared-auto' | 'runtime-admin' | 'rehydrate';
206
+ }
207
+ /** A policy verdict. `allow:false` leaves the datasource unconnected (metadata-only). */
208
+ interface DatasourceConnectDecision {
209
+ allow: boolean;
210
+ /** Human-readable reason, surfaced in logs when a connect is denied. */
211
+ reason?: string;
212
+ }
213
+ /** The minimal datasource shape a policy inspects (never a secret). */
214
+ interface DatasourceConnectSubject {
215
+ name: string;
216
+ driver: string;
217
+ schemaMode?: 'managed' | 'external' | 'validate-only';
218
+ external?: Record<string, unknown>;
219
+ }
220
+ /** Host-provided policy gate consulted before opening a connection. */
221
+ interface DatasourceConnectPolicy {
222
+ /**
223
+ * Decide whether `ds` may be connected. Sync or async. Throwing is treated
224
+ * as a denial (fail-closed) by {@link DatasourceConnectionService}.
225
+ */
226
+ canConnect(ds: DatasourceConnectSubject, ctx?: DatasourceConnectContext): DatasourceConnectDecision | Promise<DatasourceConnectDecision>;
227
+ }
228
+ /**
229
+ * Open-core default: allow every connect. The D2 auto-connect gate (external /
230
+ * explicitly-routed / `autoConnect:true`) still applies on top of this for
231
+ * code-defined datasources, so a managed, unrouted datasource is never
232
+ * connected even under the permissive policy.
233
+ */
234
+ declare const allowAllConnectPolicy: DatasourceConnectPolicy;
235
+
236
+ export { type DatasourceConnectContext, type DatasourceConnectDecision, type DatasourceConnectPolicy, type DatasourceConnectSubject, type DatasourceConnectionSpec, type DatasourceDraft, type DatasourceDriverHandle, type DatasourceOrigin, type DatasourceSummary, type IDatasourceAdminService, type IDatasourceDriverFactory, type SecretInput, type TestConnectionResult, allowAllConnectPolicy };
@@ -1 +1,7 @@
1
+ import {
2
+ allowAllConnectPolicy
3
+ } from "../chunk-XLS4RP7B.js";
4
+ export {
5
+ allowAllConnectPolicy
6
+ };
1
7
  //# sourceMappingURL=index.js.map