@objectstack/types 16.0.0 → 17.0.0-rc.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.
- package/dist/index.d.mts +140 -1
- package/dist/index.d.ts +140 -1
- package/dist/index.js +56 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +53 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,56 @@
|
|
|
1
|
+
import { TenancyPosture } from '@objectstack/spec/security';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Degraded-boot reporting, shared by every subsystem that can be told to boot
|
|
5
|
+
* without a datasource it needs.
|
|
6
|
+
*
|
|
7
|
+
* Two of them exist today and they opt in through the *same* operator flag
|
|
8
|
+
* (`OS_ALLOW_DRIVER_CONNECT_FAILURE`, see {@link resolveAllowDriverConnectFailure}):
|
|
9
|
+
*
|
|
10
|
+
* - `ObjectQLEngine.init()` — a boot-registered driver whose `connect()`
|
|
11
|
+
* rejected (framework#3741).
|
|
12
|
+
* - `DatasourceConnectionService` — a declared datasource that objects bind to
|
|
13
|
+
* explicitly, or an `external` one with `validation.onMismatch:'fail'`,
|
|
14
|
+
* that could not be connected (framework#3758).
|
|
15
|
+
*
|
|
16
|
+
* They live in different packages but owe the operator the same thing: the
|
|
17
|
+
* degraded state must be impossible to miss.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Emit the degraded-boot banner on a channel the host cannot accidentally
|
|
21
|
+
* silence.
|
|
22
|
+
*
|
|
23
|
+
* `OS_ALLOW_DRIVER_CONNECT_FAILURE` only justifies itself if the state it opts
|
|
24
|
+
* into is impossible to miss — and a logger-only banner is missable: `os serve`
|
|
25
|
+
* swallows ALL of stdout while the kernel boots (its "boot-quiet" capture), and
|
|
26
|
+
* `Logger` routes `warn` to stdout, so the one message that matters would be
|
|
27
|
+
* invisible in exactly the situation it exists for. Writing to stderr as well
|
|
28
|
+
* is the same belt-and-braces the kernel already uses for plugin startup
|
|
29
|
+
* failures.
|
|
30
|
+
*
|
|
31
|
+
* Best-effort and never throws: falls back to `console.error`, then to silence
|
|
32
|
+
* on runtimes that have neither (the logger still carries the structured
|
|
33
|
+
* record either way).
|
|
34
|
+
*/
|
|
35
|
+
declare function emitDegradedBootBanner(message: string): void;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Environment-variable helpers shared across `@objectstack/*` packages.
|
|
39
|
+
*
|
|
40
|
+
* The framework standardises on `OS_*` prefixed env vars (see AGENTS.md
|
|
41
|
+
* "Environment Variables" section). Some historical names predate this
|
|
42
|
+
* convention — `AUTH_SECRET`, `ROOT_DOMAIN`, `OBJECTSTACK_*`, …
|
|
43
|
+
*
|
|
44
|
+
* To migrate without breaking user `.env` files mid-release, call
|
|
45
|
+
* {@link readEnvWithDeprecation} at every legacy read site:
|
|
46
|
+
*
|
|
47
|
+
* const v = readEnvWithDeprecation('OS_AUTH_SECRET', 'AUTH_SECRET');
|
|
48
|
+
*
|
|
49
|
+
* If only the legacy name is set, the value is still returned but a
|
|
50
|
+
* one-shot `console.warn` fires (per-process per-variable) telling
|
|
51
|
+
* operators to rename it.
|
|
52
|
+
*/
|
|
53
|
+
|
|
1
54
|
/**
|
|
2
55
|
* Read an env var, preferring the canonical `OS_*` name and falling
|
|
3
56
|
* back to one or more legacy aliases.
|
|
@@ -45,6 +98,28 @@ declare function readEnvWithDeprecation(preferred: string, legacy: string | read
|
|
|
45
98
|
* result must be stable for the process lifetime.
|
|
46
99
|
*/
|
|
47
100
|
declare function resolveMultiOrgEnabled(): boolean;
|
|
101
|
+
/**
|
|
102
|
+
* [ADR-0105 D1] Resolve the deployment's REQUESTED tenancy posture —
|
|
103
|
+
* `single` | `group` | `isolated`.
|
|
104
|
+
*
|
|
105
|
+
* `OS_TENANCY_POSTURE` is the canonical knob and generalizes the boolean
|
|
106
|
+
* `OS_MULTI_ORG_ENABLED` it supersedes:
|
|
107
|
+
*
|
|
108
|
+
* - set → that posture (the legacy spelling `multi` normalizes to `isolated`)
|
|
109
|
+
* - unset → derived from `OS_MULTI_ORG_ENABLED`: `true` ⇒ `isolated`, else `single`
|
|
110
|
+
*
|
|
111
|
+
* so every existing deployment keeps its current posture with no config change.
|
|
112
|
+
*
|
|
113
|
+
* An unrecognized value THROWS rather than falling back. A typo'd posture that
|
|
114
|
+
* quietly resolved to `single` would silently remove the organization wall —
|
|
115
|
+
* the deployment-layer form of the "declared but unenforced" defect ADR-0049
|
|
116
|
+
* forbids, and the same reasoning behind ADR-0093 D5's refusal to boot into
|
|
117
|
+
* undeclared degradation.
|
|
118
|
+
*
|
|
119
|
+
* This resolves what the operator ASKED FOR. Whether the posture is actually
|
|
120
|
+
* enforced is the `tenancy` service's answer (`isolationActive` / `degraded`).
|
|
121
|
+
*/
|
|
122
|
+
declare function resolveTenancyPosture(): TenancyPosture;
|
|
48
123
|
/**
|
|
49
124
|
* Escape hatch for the degraded-tenancy boot guard (ADR-0093 D5).
|
|
50
125
|
*
|
|
@@ -57,6 +132,26 @@ declare function resolveMultiOrgEnabled(): boolean;
|
|
|
57
132
|
* an unset flag means "fail fast".
|
|
58
133
|
*/
|
|
59
134
|
declare function resolveAllowDegradedTenancy(): boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Escape hatch for the driver-connect boot guard (framework#3741).
|
|
137
|
+
*
|
|
138
|
+
* `ObjectQLEngine.init()` connects every boot-registered driver and, by
|
|
139
|
+
* default, refuses to boot when any of them fails — a server whose database is
|
|
140
|
+
* unreachable must not report itself started and then 500 every request with an
|
|
141
|
+
* error that reads nothing like "the database is down". Failing there is also
|
|
142
|
+
* what gives a driver the ability to REFUSE STARTUP at all: any fatal startup
|
|
143
|
+
* check a driver wants to run (licence, server version, incompatible
|
|
144
|
+
* configuration, missing capability) can simply throw from `connect()`.
|
|
145
|
+
*
|
|
146
|
+
* Setting this to a truthy value (`true`/`1`/`on`/`yes`, case-insensitive)
|
|
147
|
+
* boots anyway, in an explicitly degraded state that is logged loudly at
|
|
148
|
+
* startup. Every query routed to a failed driver fails until the datasource
|
|
149
|
+
* becomes reachable — the underlying clients do re-establish connections on
|
|
150
|
+
* their own (framework#3759) — but the boot-time schema sync those drivers
|
|
151
|
+
* missed is never re-run, so their tables may simply not exist afterwards.
|
|
152
|
+
* Defaults OFF — an unset flag means "fail fast".
|
|
153
|
+
*/
|
|
154
|
+
declare function resolveAllowDriverConnectFailure(): boolean;
|
|
60
155
|
/**
|
|
61
156
|
* SINGLE decision point for "is the MCP HTTP surface (`/api/v1/mcp`) on?".
|
|
62
157
|
*
|
|
@@ -184,6 +279,50 @@ declare function resolveSandboxTimeoutMs(kind: 'hook' | 'action' | 'wallCeiling'
|
|
|
184
279
|
*/
|
|
185
280
|
declare function _resetEnvDeprecationWarnings(): void;
|
|
186
281
|
|
|
282
|
+
/**
|
|
283
|
+
* Shared "does this error message leak server internals?" heuristic (#3867).
|
|
284
|
+
*
|
|
285
|
+
* ObjectStack has more than one HTTP boundary. `@objectstack/rest` guards the
|
|
286
|
+
* REST data routes inside `mapDataError`; the dispatcher-plugin routes
|
|
287
|
+
* (`/analytics`, `/packages`, `/i18n`, `/storage`, `/automation`, …) exit
|
|
288
|
+
* through `errorResponseBase`. Before #3867 only the first of those sanitised
|
|
289
|
+
* anything, so a driver error raised under `/analytics/query` reached the
|
|
290
|
+
* client verbatim — a real SQL statement in the response body:
|
|
291
|
+
*
|
|
292
|
+
* ```
|
|
293
|
+
* {"success":false,"error":{"message":"SELECT FROM \"sqlite_sequence\" - near \"FROM\": syntax error","code":500}}
|
|
294
|
+
* ```
|
|
295
|
+
*
|
|
296
|
+
* "Do not ship driver internals to clients" is a property of the HTTP
|
|
297
|
+
* boundary, not of one router, so the predicate lives here — the package both
|
|
298
|
+
* `@objectstack/rest` and `@objectstack/runtime` already depend on — and each
|
|
299
|
+
* boundary applies it in its own envelope. One heuristic, one place to widen
|
|
300
|
+
* when a new dialect's phrasing shows up.
|
|
301
|
+
*
|
|
302
|
+
* Deliberately a *heuristic over the message*, not a driver taxonomy: these
|
|
303
|
+
* errors arrive as plain `Error`s from a half-dozen dialects with no shared
|
|
304
|
+
* shape. It is applied only where the outcome is already a 5xx, so a false
|
|
305
|
+
* positive costs a caller nothing but detail on a response that was a server
|
|
306
|
+
* fault anyway — while the full text still reaches server logs and the
|
|
307
|
+
* error reporter.
|
|
308
|
+
*/
|
|
309
|
+
/** Generic replacement text for a message that trips {@link looksLikeInternalErrorLeak}. */
|
|
310
|
+
declare const INTERNAL_ERROR_MESSAGE = "Internal server error";
|
|
311
|
+
/**
|
|
312
|
+
* Whether `message` looks like a raw SQL statement or driver/engine dump that
|
|
313
|
+
* must not be returned to an API client.
|
|
314
|
+
*
|
|
315
|
+
* Matches: dialect error codes (`SQLSTATE`, `sqlite_*`), bare statements
|
|
316
|
+
* (a message that *starts* as `SELECT`/`INSERT INTO`/`UPDATE`/`DELETE FROM` —
|
|
317
|
+
* drivers prefix the offending SQL to their message), and constraint-violation
|
|
318
|
+
* dumps, which name physical tables and columns.
|
|
319
|
+
*
|
|
320
|
+
* Does NOT match ordinary business or validation messages, which is why the
|
|
321
|
+
* statement forms are anchored with `startsWith`: a legitimate message may
|
|
322
|
+
* *mention* "update" without being one.
|
|
323
|
+
*/
|
|
324
|
+
declare function looksLikeInternalErrorLeak(message: string | undefined | null): boolean;
|
|
325
|
+
|
|
187
326
|
/**
|
|
188
327
|
* True when a dynamic `import()` / `require.resolve()` failed because the
|
|
189
328
|
* module is simply NOT INSTALLED — as opposed to the module being present but
|
|
@@ -214,4 +353,4 @@ interface RuntimePlugin {
|
|
|
214
353
|
onStart?: (ctx: RuntimeContext) => void | Promise<void>;
|
|
215
354
|
}
|
|
216
355
|
|
|
217
|
-
export { type IKernel, type RuntimeContext, type RuntimePlugin, _resetEnvDeprecationWarnings, isMcpServerEnabled, isModuleNotFoundError, readEnvWithDeprecation, resolveAllowDegradedTenancy, resolveMcpStdioAutoStart, resolveMultiOrgEnabled, resolveOrgLimit, resolveSandboxTimeoutMs, resolveSearchPinyinEnabled };
|
|
356
|
+
export { type IKernel, INTERNAL_ERROR_MESSAGE, type RuntimeContext, type RuntimePlugin, _resetEnvDeprecationWarnings, emitDegradedBootBanner, isMcpServerEnabled, isModuleNotFoundError, looksLikeInternalErrorLeak, readEnvWithDeprecation, resolveAllowDegradedTenancy, resolveAllowDriverConnectFailure, resolveMcpStdioAutoStart, resolveMultiOrgEnabled, resolveOrgLimit, resolveSandboxTimeoutMs, resolveSearchPinyinEnabled, resolveTenancyPosture };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,56 @@
|
|
|
1
|
+
import { TenancyPosture } from '@objectstack/spec/security';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Degraded-boot reporting, shared by every subsystem that can be told to boot
|
|
5
|
+
* without a datasource it needs.
|
|
6
|
+
*
|
|
7
|
+
* Two of them exist today and they opt in through the *same* operator flag
|
|
8
|
+
* (`OS_ALLOW_DRIVER_CONNECT_FAILURE`, see {@link resolveAllowDriverConnectFailure}):
|
|
9
|
+
*
|
|
10
|
+
* - `ObjectQLEngine.init()` — a boot-registered driver whose `connect()`
|
|
11
|
+
* rejected (framework#3741).
|
|
12
|
+
* - `DatasourceConnectionService` — a declared datasource that objects bind to
|
|
13
|
+
* explicitly, or an `external` one with `validation.onMismatch:'fail'`,
|
|
14
|
+
* that could not be connected (framework#3758).
|
|
15
|
+
*
|
|
16
|
+
* They live in different packages but owe the operator the same thing: the
|
|
17
|
+
* degraded state must be impossible to miss.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Emit the degraded-boot banner on a channel the host cannot accidentally
|
|
21
|
+
* silence.
|
|
22
|
+
*
|
|
23
|
+
* `OS_ALLOW_DRIVER_CONNECT_FAILURE` only justifies itself if the state it opts
|
|
24
|
+
* into is impossible to miss — and a logger-only banner is missable: `os serve`
|
|
25
|
+
* swallows ALL of stdout while the kernel boots (its "boot-quiet" capture), and
|
|
26
|
+
* `Logger` routes `warn` to stdout, so the one message that matters would be
|
|
27
|
+
* invisible in exactly the situation it exists for. Writing to stderr as well
|
|
28
|
+
* is the same belt-and-braces the kernel already uses for plugin startup
|
|
29
|
+
* failures.
|
|
30
|
+
*
|
|
31
|
+
* Best-effort and never throws: falls back to `console.error`, then to silence
|
|
32
|
+
* on runtimes that have neither (the logger still carries the structured
|
|
33
|
+
* record either way).
|
|
34
|
+
*/
|
|
35
|
+
declare function emitDegradedBootBanner(message: string): void;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Environment-variable helpers shared across `@objectstack/*` packages.
|
|
39
|
+
*
|
|
40
|
+
* The framework standardises on `OS_*` prefixed env vars (see AGENTS.md
|
|
41
|
+
* "Environment Variables" section). Some historical names predate this
|
|
42
|
+
* convention — `AUTH_SECRET`, `ROOT_DOMAIN`, `OBJECTSTACK_*`, …
|
|
43
|
+
*
|
|
44
|
+
* To migrate without breaking user `.env` files mid-release, call
|
|
45
|
+
* {@link readEnvWithDeprecation} at every legacy read site:
|
|
46
|
+
*
|
|
47
|
+
* const v = readEnvWithDeprecation('OS_AUTH_SECRET', 'AUTH_SECRET');
|
|
48
|
+
*
|
|
49
|
+
* If only the legacy name is set, the value is still returned but a
|
|
50
|
+
* one-shot `console.warn` fires (per-process per-variable) telling
|
|
51
|
+
* operators to rename it.
|
|
52
|
+
*/
|
|
53
|
+
|
|
1
54
|
/**
|
|
2
55
|
* Read an env var, preferring the canonical `OS_*` name and falling
|
|
3
56
|
* back to one or more legacy aliases.
|
|
@@ -45,6 +98,28 @@ declare function readEnvWithDeprecation(preferred: string, legacy: string | read
|
|
|
45
98
|
* result must be stable for the process lifetime.
|
|
46
99
|
*/
|
|
47
100
|
declare function resolveMultiOrgEnabled(): boolean;
|
|
101
|
+
/**
|
|
102
|
+
* [ADR-0105 D1] Resolve the deployment's REQUESTED tenancy posture —
|
|
103
|
+
* `single` | `group` | `isolated`.
|
|
104
|
+
*
|
|
105
|
+
* `OS_TENANCY_POSTURE` is the canonical knob and generalizes the boolean
|
|
106
|
+
* `OS_MULTI_ORG_ENABLED` it supersedes:
|
|
107
|
+
*
|
|
108
|
+
* - set → that posture (the legacy spelling `multi` normalizes to `isolated`)
|
|
109
|
+
* - unset → derived from `OS_MULTI_ORG_ENABLED`: `true` ⇒ `isolated`, else `single`
|
|
110
|
+
*
|
|
111
|
+
* so every existing deployment keeps its current posture with no config change.
|
|
112
|
+
*
|
|
113
|
+
* An unrecognized value THROWS rather than falling back. A typo'd posture that
|
|
114
|
+
* quietly resolved to `single` would silently remove the organization wall —
|
|
115
|
+
* the deployment-layer form of the "declared but unenforced" defect ADR-0049
|
|
116
|
+
* forbids, and the same reasoning behind ADR-0093 D5's refusal to boot into
|
|
117
|
+
* undeclared degradation.
|
|
118
|
+
*
|
|
119
|
+
* This resolves what the operator ASKED FOR. Whether the posture is actually
|
|
120
|
+
* enforced is the `tenancy` service's answer (`isolationActive` / `degraded`).
|
|
121
|
+
*/
|
|
122
|
+
declare function resolveTenancyPosture(): TenancyPosture;
|
|
48
123
|
/**
|
|
49
124
|
* Escape hatch for the degraded-tenancy boot guard (ADR-0093 D5).
|
|
50
125
|
*
|
|
@@ -57,6 +132,26 @@ declare function resolveMultiOrgEnabled(): boolean;
|
|
|
57
132
|
* an unset flag means "fail fast".
|
|
58
133
|
*/
|
|
59
134
|
declare function resolveAllowDegradedTenancy(): boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Escape hatch for the driver-connect boot guard (framework#3741).
|
|
137
|
+
*
|
|
138
|
+
* `ObjectQLEngine.init()` connects every boot-registered driver and, by
|
|
139
|
+
* default, refuses to boot when any of them fails — a server whose database is
|
|
140
|
+
* unreachable must not report itself started and then 500 every request with an
|
|
141
|
+
* error that reads nothing like "the database is down". Failing there is also
|
|
142
|
+
* what gives a driver the ability to REFUSE STARTUP at all: any fatal startup
|
|
143
|
+
* check a driver wants to run (licence, server version, incompatible
|
|
144
|
+
* configuration, missing capability) can simply throw from `connect()`.
|
|
145
|
+
*
|
|
146
|
+
* Setting this to a truthy value (`true`/`1`/`on`/`yes`, case-insensitive)
|
|
147
|
+
* boots anyway, in an explicitly degraded state that is logged loudly at
|
|
148
|
+
* startup. Every query routed to a failed driver fails until the datasource
|
|
149
|
+
* becomes reachable — the underlying clients do re-establish connections on
|
|
150
|
+
* their own (framework#3759) — but the boot-time schema sync those drivers
|
|
151
|
+
* missed is never re-run, so their tables may simply not exist afterwards.
|
|
152
|
+
* Defaults OFF — an unset flag means "fail fast".
|
|
153
|
+
*/
|
|
154
|
+
declare function resolveAllowDriverConnectFailure(): boolean;
|
|
60
155
|
/**
|
|
61
156
|
* SINGLE decision point for "is the MCP HTTP surface (`/api/v1/mcp`) on?".
|
|
62
157
|
*
|
|
@@ -184,6 +279,50 @@ declare function resolveSandboxTimeoutMs(kind: 'hook' | 'action' | 'wallCeiling'
|
|
|
184
279
|
*/
|
|
185
280
|
declare function _resetEnvDeprecationWarnings(): void;
|
|
186
281
|
|
|
282
|
+
/**
|
|
283
|
+
* Shared "does this error message leak server internals?" heuristic (#3867).
|
|
284
|
+
*
|
|
285
|
+
* ObjectStack has more than one HTTP boundary. `@objectstack/rest` guards the
|
|
286
|
+
* REST data routes inside `mapDataError`; the dispatcher-plugin routes
|
|
287
|
+
* (`/analytics`, `/packages`, `/i18n`, `/storage`, `/automation`, …) exit
|
|
288
|
+
* through `errorResponseBase`. Before #3867 only the first of those sanitised
|
|
289
|
+
* anything, so a driver error raised under `/analytics/query` reached the
|
|
290
|
+
* client verbatim — a real SQL statement in the response body:
|
|
291
|
+
*
|
|
292
|
+
* ```
|
|
293
|
+
* {"success":false,"error":{"message":"SELECT FROM \"sqlite_sequence\" - near \"FROM\": syntax error","code":500}}
|
|
294
|
+
* ```
|
|
295
|
+
*
|
|
296
|
+
* "Do not ship driver internals to clients" is a property of the HTTP
|
|
297
|
+
* boundary, not of one router, so the predicate lives here — the package both
|
|
298
|
+
* `@objectstack/rest` and `@objectstack/runtime` already depend on — and each
|
|
299
|
+
* boundary applies it in its own envelope. One heuristic, one place to widen
|
|
300
|
+
* when a new dialect's phrasing shows up.
|
|
301
|
+
*
|
|
302
|
+
* Deliberately a *heuristic over the message*, not a driver taxonomy: these
|
|
303
|
+
* errors arrive as plain `Error`s from a half-dozen dialects with no shared
|
|
304
|
+
* shape. It is applied only where the outcome is already a 5xx, so a false
|
|
305
|
+
* positive costs a caller nothing but detail on a response that was a server
|
|
306
|
+
* fault anyway — while the full text still reaches server logs and the
|
|
307
|
+
* error reporter.
|
|
308
|
+
*/
|
|
309
|
+
/** Generic replacement text for a message that trips {@link looksLikeInternalErrorLeak}. */
|
|
310
|
+
declare const INTERNAL_ERROR_MESSAGE = "Internal server error";
|
|
311
|
+
/**
|
|
312
|
+
* Whether `message` looks like a raw SQL statement or driver/engine dump that
|
|
313
|
+
* must not be returned to an API client.
|
|
314
|
+
*
|
|
315
|
+
* Matches: dialect error codes (`SQLSTATE`, `sqlite_*`), bare statements
|
|
316
|
+
* (a message that *starts* as `SELECT`/`INSERT INTO`/`UPDATE`/`DELETE FROM` —
|
|
317
|
+
* drivers prefix the offending SQL to their message), and constraint-violation
|
|
318
|
+
* dumps, which name physical tables and columns.
|
|
319
|
+
*
|
|
320
|
+
* Does NOT match ordinary business or validation messages, which is why the
|
|
321
|
+
* statement forms are anchored with `startsWith`: a legitimate message may
|
|
322
|
+
* *mention* "update" without being one.
|
|
323
|
+
*/
|
|
324
|
+
declare function looksLikeInternalErrorLeak(message: string | undefined | null): boolean;
|
|
325
|
+
|
|
187
326
|
/**
|
|
188
327
|
* True when a dynamic `import()` / `require.resolve()` failed because the
|
|
189
328
|
* module is simply NOT INSTALLED — as opposed to the module being present but
|
|
@@ -214,4 +353,4 @@ interface RuntimePlugin {
|
|
|
214
353
|
onStart?: (ctx: RuntimeContext) => void | Promise<void>;
|
|
215
354
|
}
|
|
216
355
|
|
|
217
|
-
export { type IKernel, type RuntimeContext, type RuntimePlugin, _resetEnvDeprecationWarnings, isMcpServerEnabled, isModuleNotFoundError, readEnvWithDeprecation, resolveAllowDegradedTenancy, resolveMcpStdioAutoStart, resolveMultiOrgEnabled, resolveOrgLimit, resolveSandboxTimeoutMs, resolveSearchPinyinEnabled };
|
|
356
|
+
export { type IKernel, INTERNAL_ERROR_MESSAGE, type RuntimeContext, type RuntimePlugin, _resetEnvDeprecationWarnings, emitDegradedBootBanner, isMcpServerEnabled, isModuleNotFoundError, looksLikeInternalErrorLeak, readEnvWithDeprecation, resolveAllowDegradedTenancy, resolveAllowDriverConnectFailure, resolveMcpStdioAutoStart, resolveMultiOrgEnabled, resolveOrgLimit, resolveSandboxTimeoutMs, resolveSearchPinyinEnabled, resolveTenancyPosture };
|
package/dist/index.js
CHANGED
|
@@ -20,20 +20,43 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
INTERNAL_ERROR_MESSAGE: () => INTERNAL_ERROR_MESSAGE,
|
|
23
24
|
_resetEnvDeprecationWarnings: () => _resetEnvDeprecationWarnings,
|
|
25
|
+
emitDegradedBootBanner: () => emitDegradedBootBanner,
|
|
24
26
|
isMcpServerEnabled: () => isMcpServerEnabled,
|
|
25
27
|
isModuleNotFoundError: () => isModuleNotFoundError,
|
|
28
|
+
looksLikeInternalErrorLeak: () => looksLikeInternalErrorLeak,
|
|
26
29
|
readEnvWithDeprecation: () => readEnvWithDeprecation,
|
|
27
30
|
resolveAllowDegradedTenancy: () => resolveAllowDegradedTenancy,
|
|
31
|
+
resolveAllowDriverConnectFailure: () => resolveAllowDriverConnectFailure,
|
|
28
32
|
resolveMcpStdioAutoStart: () => resolveMcpStdioAutoStart,
|
|
29
33
|
resolveMultiOrgEnabled: () => resolveMultiOrgEnabled,
|
|
30
34
|
resolveOrgLimit: () => resolveOrgLimit,
|
|
31
35
|
resolveSandboxTimeoutMs: () => resolveSandboxTimeoutMs,
|
|
32
|
-
resolveSearchPinyinEnabled: () => resolveSearchPinyinEnabled
|
|
36
|
+
resolveSearchPinyinEnabled: () => resolveSearchPinyinEnabled,
|
|
37
|
+
resolveTenancyPosture: () => resolveTenancyPosture
|
|
33
38
|
});
|
|
34
39
|
module.exports = __toCommonJS(index_exports);
|
|
35
40
|
|
|
41
|
+
// src/degraded-boot.ts
|
|
42
|
+
function emitDegradedBootBanner(message) {
|
|
43
|
+
const proc = globalThis.process;
|
|
44
|
+
try {
|
|
45
|
+
if (typeof proc?.stderr?.write === "function") {
|
|
46
|
+
proc.stderr.write(`${message}
|
|
47
|
+
`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
globalThis.console?.error?.(message);
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
36
58
|
// src/env.ts
|
|
59
|
+
var import_security = require("@objectstack/spec/security");
|
|
37
60
|
var _warnedKeys = /* @__PURE__ */ new Set();
|
|
38
61
|
function readEnvWithDeprecation(preferred, legacy, options) {
|
|
39
62
|
const env = globalThis.process?.env;
|
|
@@ -64,11 +87,29 @@ function resolveMultiOrgEnabled() {
|
|
|
64
87
|
const raw = readEnvWithDeprecation("OS_MULTI_ORG_ENABLED", []);
|
|
65
88
|
return String(raw ?? "false").toLowerCase() !== "false";
|
|
66
89
|
}
|
|
90
|
+
function resolveTenancyPosture() {
|
|
91
|
+
const raw = globalThis.process?.env?.OS_TENANCY_POSTURE;
|
|
92
|
+
if (raw != null && String(raw).trim() !== "") {
|
|
93
|
+
const posture = (0, import_security.normalizeTenancyPosture)(raw);
|
|
94
|
+
if (!posture) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`Invalid OS_TENANCY_POSTURE=${JSON.stringify(String(raw))}. Expected one of: ${import_security.TENANCY_POSTURES.join(", ")} (or the legacy alias 'multi' = 'isolated'). Refusing to boot rather than silently falling back to a posture with no organization wall.`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return posture;
|
|
100
|
+
}
|
|
101
|
+
return resolveMultiOrgEnabled() ? "isolated" : "single";
|
|
102
|
+
}
|
|
67
103
|
function resolveAllowDegradedTenancy() {
|
|
68
104
|
const raw = readEnvWithDeprecation("OS_ALLOW_DEGRADED_TENANCY", [], { silent: true });
|
|
69
105
|
if (raw == null) return false;
|
|
70
106
|
return ["1", "true", "on", "yes"].includes(String(raw).trim().toLowerCase());
|
|
71
107
|
}
|
|
108
|
+
function resolveAllowDriverConnectFailure() {
|
|
109
|
+
const raw = readEnvWithDeprecation("OS_ALLOW_DRIVER_CONNECT_FAILURE", [], { silent: true });
|
|
110
|
+
if (raw == null) return false;
|
|
111
|
+
return ["1", "true", "on", "yes"].includes(String(raw).trim().toLowerCase());
|
|
112
|
+
}
|
|
72
113
|
function isMcpServerEnabled() {
|
|
73
114
|
const raw = readEnvWithDeprecation("OS_MCP_SERVER_ENABLED", "MCP_SERVER_ENABLED", {
|
|
74
115
|
silent: true
|
|
@@ -111,6 +152,14 @@ function _resetEnvDeprecationWarnings() {
|
|
|
111
152
|
_warnedKeys.clear();
|
|
112
153
|
}
|
|
113
154
|
|
|
155
|
+
// src/error-leak.ts
|
|
156
|
+
var INTERNAL_ERROR_MESSAGE = "Internal server error";
|
|
157
|
+
function looksLikeInternalErrorLeak(message) {
|
|
158
|
+
if (!message) return false;
|
|
159
|
+
const lower = String(message).toLowerCase();
|
|
160
|
+
return lower.includes("sqlite_") || lower.includes("sqlstate") || lower.startsWith("insert into ") || lower.startsWith("update ") || lower.startsWith("select ") || lower.startsWith("delete from ") || lower.includes("constraint failed") || lower.includes("unique constraint") || lower.includes("foreign key");
|
|
161
|
+
}
|
|
162
|
+
|
|
114
163
|
// src/module-not-found.ts
|
|
115
164
|
function isModuleNotFoundError(err) {
|
|
116
165
|
const code = err?.code;
|
|
@@ -120,15 +169,20 @@ function isModuleNotFoundError(err) {
|
|
|
120
169
|
}
|
|
121
170
|
// Annotate the CommonJS export names for ESM import in node:
|
|
122
171
|
0 && (module.exports = {
|
|
172
|
+
INTERNAL_ERROR_MESSAGE,
|
|
123
173
|
_resetEnvDeprecationWarnings,
|
|
174
|
+
emitDegradedBootBanner,
|
|
124
175
|
isMcpServerEnabled,
|
|
125
176
|
isModuleNotFoundError,
|
|
177
|
+
looksLikeInternalErrorLeak,
|
|
126
178
|
readEnvWithDeprecation,
|
|
127
179
|
resolveAllowDegradedTenancy,
|
|
180
|
+
resolveAllowDriverConnectFailure,
|
|
128
181
|
resolveMcpStdioAutoStart,
|
|
129
182
|
resolveMultiOrgEnabled,
|
|
130
183
|
resolveOrgLimit,
|
|
131
184
|
resolveSandboxTimeoutMs,
|
|
132
|
-
resolveSearchPinyinEnabled
|
|
185
|
+
resolveSearchPinyinEnabled,
|
|
186
|
+
resolveTenancyPosture
|
|
133
187
|
});
|
|
134
188
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/env.ts","../src/module-not-found.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nexport * from './env.js';\nexport * from './module-not-found.js';\n\n// Placeholder for Kernel interface to avoid circular dependency\n// The actual Kernel implementation will satisfy this interface.\nexport interface IKernel {\n // We can add specific methods here that plugins are allowed to call\n // forcing a stricter contract than exposing the whole class.\n ql?: any; // ObjectQL instance (optional to support initialization phase)\n start(): Promise<void>;\n // ... expose other needed public methods\n [key: string]: any; \n}\n\nexport interface RuntimeContext {\n engine: IKernel;\n}\n\nexport interface RuntimePlugin {\n name: string;\n install?: (ctx: RuntimeContext) => void | Promise<void>;\n onStart?: (ctx: RuntimeContext) => void | Promise<void>;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Environment-variable helpers shared across `@objectstack/*` packages.\n *\n * The framework standardises on `OS_*` prefixed env vars (see AGENTS.md\n * \"Environment Variables\" section). Some historical names predate this\n * convention — `AUTH_SECRET`, `ROOT_DOMAIN`, `OBJECTSTACK_*`, …\n *\n * To migrate without breaking user `.env` files mid-release, call\n * {@link readEnvWithDeprecation} at every legacy read site:\n *\n * const v = readEnvWithDeprecation('OS_AUTH_SECRET', 'AUTH_SECRET');\n *\n * If only the legacy name is set, the value is still returned but a\n * one-shot `console.warn` fires (per-process per-variable) telling\n * operators to rename it.\n */\n\nconst _warnedKeys = new Set<string>();\n\n/**\n * Read an env var, preferring the canonical `OS_*` name and falling\n * back to one or more legacy aliases.\n *\n * When only a legacy alias is set, emits a one-shot deprecation warning.\n * The warning is process-wide deduplicated: identical (preferred, legacy)\n * pairs will only warn once even if read from multiple call sites.\n *\n * Legacy aliases are checked in order; the first one with a defined\n * value wins (and triggers the warning for that specific alias).\n *\n * Safe to call from environments where `process` is unavailable (returns\n * `undefined`); the warning is suppressed when running outside Node-like\n * runtimes that lack `console.warn`.\n *\n * @param preferred Canonical OS_*-prefixed env var name.\n * @param legacy Older name (or array of older names) to fall back on.\n * @param options Optional behaviour flags. Set `silent: true` for aliases\n * that remain accepted conventions rather than true legacy\n * names — e.g. `PORT`, which PaaS platforms (Render, Railway,\n * Heroku, Fly, …) inject automatically. Warning on those\n * would nag operators about env they never set.\n * @returns The resolved value, or `undefined` if neither is set.\n */\nexport function readEnvWithDeprecation(\n preferred: string,\n legacy: string | readonly string[],\n options?: { silent?: boolean },\n): string | undefined {\n const env = (globalThis as { process?: { env?: Record<string, string | undefined> } })\n .process?.env;\n if (!env) return undefined;\n\n const preferredValue = env[preferred];\n if (preferredValue !== undefined) return preferredValue;\n\n const legacyList = typeof legacy === 'string' ? [legacy] : legacy;\n for (const legacyName of legacyList) {\n const legacyValue = env[legacyName];\n if (legacyValue !== undefined) {\n const dedupeKey = `${preferred}|${legacyName}`;\n if (!options?.silent && !_warnedKeys.has(dedupeKey)) {\n _warnedKeys.add(dedupeKey);\n const consoleRef = (globalThis as { console?: { warn?: (msg: string) => void } }).console;\n try {\n consoleRef?.warn?.(\n `[ObjectStack] Env var \\`${legacyName}\\` is deprecated; rename it to \\`${preferred}\\`. ` +\n `The legacy name still works for now but will be removed in a future major release.`,\n );\n } catch {\n /* `console.warn` unavailable (exotic runtime) — ignore */\n }\n }\n return legacyValue;\n }\n }\n\n return undefined;\n}\n\n/**\n * Resolve whether the deployment runs in multi-org (a.k.a. multi-tenant) mode.\n *\n * Single source of truth for the `OS_MULTI_ORG_ENABLED` flag. Resolution: the\n * canonical `OS_MULTI_ORG_ENABLED`; else `false`. Any value other than a\n * case-insensitive `'false'` enables it. (The legacy `OS_MULTI_TENANT` alias was\n * removed in 11.0.)\n *\n * Every site that needs to know \"is this multi-org?\" — the SQL driver's\n * tenant-audit gate, the auth manager's `/auth/config` feature flag and\n * org-create guard, the CLI / dev / runtime org-scoping plugin wiring — MUST\n * call this instead of re-reading the env, so the driver, the security layer,\n * and the UI can never disagree about the mode. Previously each site inlined\n * its own `String(... ?? 'false').toLowerCase() !== 'false'` (and the SQL\n * driver read `process.env` directly, skipping the deprecation warning).\n *\n * Reads `process.env` live on each call; memoise at the call site if the\n * result must be stable for the process lifetime.\n */\nexport function resolveMultiOrgEnabled(): boolean {\n const raw = readEnvWithDeprecation('OS_MULTI_ORG_ENABLED', []);\n return String(raw ?? 'false').toLowerCase() !== 'false';\n}\n\n/**\n * Escape hatch for the degraded-tenancy boot guard (ADR-0093 D5).\n *\n * When `OS_MULTI_ORG_ENABLED=true` but the enterprise `@objectstack/organizations`\n * package cannot provide tenant isolation, the platform refuses to boot — a\n * deployment that asked for tenant isolation must not serve traffic pretending\n * to have it (ADR-0049 at the deployment layer). Setting this to a truthy value\n * (`true`/`1`/`on`/`yes`, case-insensitive) boots anyway in an explicitly\n * *degraded* state that is branded everywhere an operator looks. Defaults OFF —\n * an unset flag means \"fail fast\".\n */\nexport function resolveAllowDegradedTenancy(): boolean {\n const raw = readEnvWithDeprecation('OS_ALLOW_DEGRADED_TENANCY', [], { silent: true });\n if (raw == null) return false;\n return ['1', 'true', 'on', 'yes'].includes(String(raw).trim().toLowerCase());\n}\n\n/**\n * SINGLE decision point for \"is the MCP HTTP surface (`/api/v1/mcp`) on?\".\n *\n * MCP is a core platform capability and defaults ON: an unset\n * `OS_MCP_SERVER_ENABLED` means the surface is served. Operators opt OUT with\n * an explicit falsy value (`false`/`0`/`off`/`no`, case-insensitive); any\n * other value — including the historical `true` — keeps it on.\n *\n * Every consumer of the flag — the runtime dispatcher's `/mcp` route gate,\n * the CLI's MCP plugin auto-load, the REST `/discovery` advertisement, and\n * the auth service's OAuth/DCR follow-defaults — MUST call this instead of\n * re-reading the env, so the served route, the advertised route, and the\n * authorization track can never disagree.\n *\n * Note the asymmetry with the MCP plugin's *stdio* auto-start\n * ({@link resolveMcpStdioAutoStart}), which stays opt-in and is gated by a\n * SEPARATE switch: attaching a long-lived stdio transport to every process is\n * a side effect no default should impose, while the HTTP surface is served\n * statelessly per-request.\n */\nexport function isMcpServerEnabled(): boolean {\n const raw = readEnvWithDeprecation('OS_MCP_SERVER_ENABLED', 'MCP_SERVER_ENABLED', {\n silent: true,\n });\n if (raw == null) return true;\n return !['0', 'false', 'off', 'no'].includes(raw.trim().toLowerCase());\n}\n\n/**\n * SINGLE decision point for \"should the MCP plugin auto-start a long-lived\n * (stdio) transport?\" — distinct from {@link isMcpServerEnabled}, which governs\n * the stateless HTTP surface.\n *\n * The stdio transport is a different, stricter posture: the plugin bridges the\n * RAW metadata service + data engine onto the long-lived server with NO\n * per-request principal (unscoped — see the `mcp-stdio-authority` conformance\n * row), so it is safe only as a single-operator LOCAL tool and MUST stay\n * opt-in. It defaults OFF.\n *\n * Canonical switch: `OS_MCP_STDIO_ENABLED` (truthy). The plugin also starts it\n * when constructed with `{ autoStart: true }` (that path is checked by the\n * caller, not here).\n *\n * DEPRECATED alias: `OS_MCP_SERVER_ENABLED=true` historically ALSO started\n * stdio — overloading the very var that gates the HTTP surface, so an operator\n * setting it to \"make sure MCP is on\" silently attached an unscoped transport.\n * That trigger still works (with a one-time warning from the caller) for one\n * release; prefer the dedicated var. Note `OS_MCP_SERVER_ENABLED=false` only\n * ever gated the HTTP surface and never started stdio, so it is unaffected.\n *\n * @returns `enabled` — whether stdio auto-start is requested by the env; and\n * `viaDeprecatedAlias` — whether it came through the legacy\n * `OS_MCP_SERVER_ENABLED=true` trigger (so the caller can warn once).\n */\nexport function resolveMcpStdioAutoStart(): { enabled: boolean; viaDeprecatedAlias: boolean } {\n const stdio = readEnvWithDeprecation('OS_MCP_STDIO_ENABLED', [], { silent: true });\n if (stdio != null && ['1', 'true', 'on', 'yes'].includes(stdio.trim().toLowerCase())) {\n return { enabled: true, viaDeprecatedAlias: false };\n }\n // Legacy trigger: only the literal `true` ever started stdio (preserved\n // exactly). `OS_MCP_SERVER_ENABLED=false`/other values never did.\n const legacy = readEnvWithDeprecation('OS_MCP_SERVER_ENABLED', 'MCP_SERVER_ENABLED', { silent: true });\n if (legacy != null && legacy.trim().toLowerCase() === 'true') {\n return { enabled: true, viaDeprecatedAlias: true };\n }\n return { enabled: false, viaDeprecatedAlias: false };\n}\n\n/**\n * Maximum number of organizations a single user may CREATE, from `OS_ORG_LIMIT`.\n * The auth plugin forwards this as better-auth's `organizationLimit` in function\n * form, counting only the caller's `role=owner` memberships — so it caps\n * self-created orgs (each of which can auto-provision a free environment on the\n * cloud control plane) without penalising a user invited into many orgs.\n *\n * Only meaningful when multi-org is enabled ({@link resolveMultiOrgEnabled}).\n * Returns `undefined` when unset or non-positive → no limit (better-auth treats\n * an absent `organizationLimit` as unlimited), preserving self-host behaviour.\n * Deployments that let users self-create orgs SHOULD set a generous cap.\n */\nexport function resolveOrgLimit(): number | undefined {\n const raw = readEnvWithDeprecation('OS_ORG_LIMIT', [], { silent: true });\n if (raw == null || String(raw).trim() === '') return undefined;\n const n = Number.parseInt(String(raw), 10);\n return Number.isFinite(n) && n > 0 ? n : undefined;\n}\n\n/**\n * SINGLE decision point for \"is pinyin search recall on?\" (#2486).\n *\n * Pinyin search is a deployment/locale-level capability, not field metadata:\n * Chinese deployments want it, pure-Japanese/English deployments don't. The\n * flag gates the whole feature end-to-end — the SchemaRegistry's compile-time\n * `__search` companion-column seam AND the `plugin-pinyin-search` populate\n * hooks — so there is no half-state where a column exists but nobody fills it\n * (ADR-0049: no declared-but-unenforced capability).\n *\n * Resolution:\n * 1. An explicit `OS_SEARCH_PINYIN_ENABLED` always wins — truthy\n * (`1`/`true`/`on`/`yes`) enables, anything else disables.\n * 2. When unset, the default derives from the deployment's configured\n * locales (`opts.locales`, e.g. the stack's `i18n.defaultLocale` +\n * `supportedLocales`): any `zh-*` locale turns it on.\n * 3. No env var and no `zh-*` locale → off. OSS / non-Chinese deployments\n * never load `pinyin-pro` and pay zero compute cost.\n *\n * Hosts that know the stack's i18n config (the CLI `serve` boot path) resolve\n * once with locales and stamp the decision back into the env, so downstream\n * consumers constructed without config access (per-engine SchemaRegistry)\n * read the same answer via the no-arg form.\n */\nexport function resolveSearchPinyinEnabled(opts?: { locales?: readonly string[] }): boolean {\n const raw = readEnvWithDeprecation('OS_SEARCH_PINYIN_ENABLED', [], { silent: true });\n if (raw != null && String(raw).trim() !== '') {\n return ['1', 'true', 'on', 'yes'].includes(String(raw).trim().toLowerCase());\n }\n return (opts?.locales ?? []).some((l) => /^zh([-_]|$)/i.test(String(l ?? '').trim()));\n}\n\n/**\n * SINGLE decision point for a sandbox script-runner DEFAULT (ms), resolved from\n * the environment (framework#3259 / ADR-0102).\n *\n * The QuickJS sandbox meters each hook/action invocation against a per-invocation\n * budget. Two dimensions are env-tunable:\n * - the **CPU-time budget** for hooks / actions — how much *VM-active* time a\n * body may burn (built-in 250ms hooks / 5000ms actions); and\n * - the **wall-clock ceiling** — the backstop bounding a body parked forever on\n * a host call that never settles (built-in 30_000ms).\n *\n * The built-in defaults suit a warm, idle host; a heavily loaded or slow host\n * (an oversubscribed CI runner, constrained production hardware) may need a\n * higher floor. This lets an operator raise it once, deployment-wide, instead of\n * re-tuning every call site.\n *\n * Canonical vars (OS_{DOMAIN}_{NAME}, DOMAIN=SANDBOX):\n * - hook → `OS_SANDBOX_HOOK_TIMEOUT_MS`\n * - action → `OS_SANDBOX_ACTION_TIMEOUT_MS`\n * - wallCeiling → `OS_SANDBOX_WALL_CEILING_MS`\n *\n * Only a positive integer is honored; unset / empty / non-numeric / non-positive\n * falls back to `fallback`, so behaviour is byte-for-byte unchanged when the var\n * is absent. This is a FALLBACK default ONLY: an explicit constructor option\n * still wins over it, and (for the CPU budget) a body's own declared `timeoutMs`\n * still wins over the resolved default per the runner's resolution rule.\n */\nexport function resolveSandboxTimeoutMs(\n kind: 'hook' | 'action' | 'wallCeiling',\n fallback: number,\n): number {\n const name =\n kind === 'hook'\n ? 'OS_SANDBOX_HOOK_TIMEOUT_MS'\n : kind === 'action'\n ? 'OS_SANDBOX_ACTION_TIMEOUT_MS'\n : 'OS_SANDBOX_WALL_CEILING_MS';\n const raw = readEnvWithDeprecation(name, [], { silent: true });\n if (raw == null || String(raw).trim() === '') return fallback;\n const n = Number.parseInt(String(raw).trim(), 10);\n return Number.isFinite(n) && n > 0 ? n : fallback;\n}\n\n/**\n * Internal: clear the dedupe set. Test-only; exposed so suite-wide\n * deprecation warnings don't bleed between tests.\n *\n * @internal\n */\nexport function _resetEnvDeprecationWarnings(): void {\n _warnedKeys.clear();\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * True when a dynamic `import()` / `require.resolve()` failed because the\n * module is simply NOT INSTALLED — as opposed to the module being present but\n * throwing while it loads (a real crash). Checking `err.code` FIRST matters:\n * ESM reports a missing package as `err.code === 'ERR_MODULE_NOT_FOUND'` with\n * the human message `Cannot find package '...'`; matching only the older\n * `Cannot find module` string mis-classifies that as a crash (framework#1595).\n *\n * Single shared owner for this classification (framework#3265): the CLI's\n * optional-plugin guards and `requires` capability resolver delegate here, and\n * cloud's `objectos-runtime` capability loader is expected to adopt it at its\n * next framework pin bump — so the parallel loaders cannot drift apart and\n * re-introduce the #1595 false-alarm class.\n */\nexport function isModuleNotFoundError(err: unknown): boolean {\n const code = (err as { code?: string } | null | undefined)?.code;\n if (code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND') return true;\n const msg = err instanceof Error ? err.message : String(err);\n return msg.includes('Cannot find module') || msg.includes('Cannot find package');\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACmBA,IAAM,cAAc,oBAAI,IAAY;AA0B7B,SAAS,uBACd,WACA,QACA,SACoB;AACpB,QAAM,MAAO,WACV,SAAS;AACZ,MAAI,CAAC,IAAK,QAAO;AAEjB,QAAM,iBAAiB,IAAI,SAAS;AACpC,MAAI,mBAAmB,OAAW,QAAO;AAEzC,QAAM,aAAa,OAAO,WAAW,WAAW,CAAC,MAAM,IAAI;AAC3D,aAAW,cAAc,YAAY;AACnC,UAAM,cAAc,IAAI,UAAU;AAClC,QAAI,gBAAgB,QAAW;AAC7B,YAAM,YAAY,GAAG,SAAS,IAAI,UAAU;AAC5C,UAAI,CAAC,SAAS,UAAU,CAAC,YAAY,IAAI,SAAS,GAAG;AACnD,oBAAY,IAAI,SAAS;AACzB,cAAM,aAAc,WAA8D;AAClF,YAAI;AACF,sBAAY;AAAA,YACV,2BAA2B,UAAU,oCAAoC,SAAS;AAAA,UAEpF;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAqBO,SAAS,yBAAkC;AAChD,QAAM,MAAM,uBAAuB,wBAAwB,CAAC,CAAC;AAC7D,SAAO,OAAO,OAAO,OAAO,EAAE,YAAY,MAAM;AAClD;AAaO,SAAS,8BAAuC;AACrD,QAAM,MAAM,uBAAuB,6BAA6B,CAAC,GAAG,EAAE,QAAQ,KAAK,CAAC;AACpF,MAAI,OAAO,KAAM,QAAO;AACxB,SAAO,CAAC,KAAK,QAAQ,MAAM,KAAK,EAAE,SAAS,OAAO,GAAG,EAAE,KAAK,EAAE,YAAY,CAAC;AAC7E;AAsBO,SAAS,qBAA8B;AAC5C,QAAM,MAAM,uBAAuB,yBAAyB,sBAAsB;AAAA,IAChF,QAAQ;AAAA,EACV,CAAC;AACD,MAAI,OAAO,KAAM,QAAO;AACxB,SAAO,CAAC,CAAC,KAAK,SAAS,OAAO,IAAI,EAAE,SAAS,IAAI,KAAK,EAAE,YAAY,CAAC;AACvE;AA4BO,SAAS,2BAA8E;AAC5F,QAAM,QAAQ,uBAAuB,wBAAwB,CAAC,GAAG,EAAE,QAAQ,KAAK,CAAC;AACjF,MAAI,SAAS,QAAQ,CAAC,KAAK,QAAQ,MAAM,KAAK,EAAE,SAAS,MAAM,KAAK,EAAE,YAAY,CAAC,GAAG;AACpF,WAAO,EAAE,SAAS,MAAM,oBAAoB,MAAM;AAAA,EACpD;AAGA,QAAM,SAAS,uBAAuB,yBAAyB,sBAAsB,EAAE,QAAQ,KAAK,CAAC;AACrG,MAAI,UAAU,QAAQ,OAAO,KAAK,EAAE,YAAY,MAAM,QAAQ;AAC5D,WAAO,EAAE,SAAS,MAAM,oBAAoB,KAAK;AAAA,EACnD;AACA,SAAO,EAAE,SAAS,OAAO,oBAAoB,MAAM;AACrD;AAcO,SAAS,kBAAsC;AACpD,QAAM,MAAM,uBAAuB,gBAAgB,CAAC,GAAG,EAAE,QAAQ,KAAK,CAAC;AACvE,MAAI,OAAO,QAAQ,OAAO,GAAG,EAAE,KAAK,MAAM,GAAI,QAAO;AACrD,QAAM,IAAI,OAAO,SAAS,OAAO,GAAG,GAAG,EAAE;AACzC,SAAO,OAAO,SAAS,CAAC,KAAK,IAAI,IAAI,IAAI;AAC3C;AA0BO,SAAS,2BAA2B,MAAiD;AAC1F,QAAM,MAAM,uBAAuB,4BAA4B,CAAC,GAAG,EAAE,QAAQ,KAAK,CAAC;AACnF,MAAI,OAAO,QAAQ,OAAO,GAAG,EAAE,KAAK,MAAM,IAAI;AAC5C,WAAO,CAAC,KAAK,QAAQ,MAAM,KAAK,EAAE,SAAS,OAAO,GAAG,EAAE,KAAK,EAAE,YAAY,CAAC;AAAA,EAC7E;AACA,UAAQ,MAAM,WAAW,CAAC,GAAG,KAAK,CAAC,MAAM,eAAe,KAAK,OAAO,KAAK,EAAE,EAAE,KAAK,CAAC,CAAC;AACtF;AA6BO,SAAS,wBACd,MACA,UACQ;AACR,QAAM,OACJ,SAAS,SACL,+BACA,SAAS,WACP,iCACA;AACR,QAAM,MAAM,uBAAuB,MAAM,CAAC,GAAG,EAAE,QAAQ,KAAK,CAAC;AAC7D,MAAI,OAAO,QAAQ,OAAO,GAAG,EAAE,KAAK,MAAM,GAAI,QAAO;AACrD,QAAM,IAAI,OAAO,SAAS,OAAO,GAAG,EAAE,KAAK,GAAG,EAAE;AAChD,SAAO,OAAO,SAAS,CAAC,KAAK,IAAI,IAAI,IAAI;AAC3C;AAQO,SAAS,+BAAqC;AACnD,cAAY,MAAM;AACpB;;;ACpRO,SAAS,sBAAsB,KAAuB;AAC3D,QAAM,OAAQ,KAA8C;AAC5D,MAAI,SAAS,0BAA0B,SAAS,mBAAoB,QAAO;AAC3E,QAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,SAAO,IAAI,SAAS,oBAAoB,KAAK,IAAI,SAAS,qBAAqB;AACjF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/degraded-boot.ts","../src/env.ts","../src/error-leak.ts","../src/module-not-found.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nexport * from './degraded-boot.js';\nexport * from './env.js';\nexport * from './error-leak.js';\nexport * from './module-not-found.js';\n\n// Placeholder for Kernel interface to avoid circular dependency\n// The actual Kernel implementation will satisfy this interface.\nexport interface IKernel {\n // We can add specific methods here that plugins are allowed to call\n // forcing a stricter contract than exposing the whole class.\n ql?: any; // ObjectQL instance (optional to support initialization phase)\n start(): Promise<void>;\n // ... expose other needed public methods\n [key: string]: any; \n}\n\nexport interface RuntimeContext {\n engine: IKernel;\n}\n\nexport interface RuntimePlugin {\n name: string;\n install?: (ctx: RuntimeContext) => void | Promise<void>;\n onStart?: (ctx: RuntimeContext) => void | Promise<void>;\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Degraded-boot reporting, shared by every subsystem that can be told to boot\n * without a datasource it needs.\n *\n * Two of them exist today and they opt in through the *same* operator flag\n * (`OS_ALLOW_DRIVER_CONNECT_FAILURE`, see {@link resolveAllowDriverConnectFailure}):\n *\n * - `ObjectQLEngine.init()` — a boot-registered driver whose `connect()`\n * rejected (framework#3741).\n * - `DatasourceConnectionService` — a declared datasource that objects bind to\n * explicitly, or an `external` one with `validation.onMismatch:'fail'`,\n * that could not be connected (framework#3758).\n *\n * They live in different packages but owe the operator the same thing: the\n * degraded state must be impossible to miss.\n */\n\n/**\n * Emit the degraded-boot banner on a channel the host cannot accidentally\n * silence.\n *\n * `OS_ALLOW_DRIVER_CONNECT_FAILURE` only justifies itself if the state it opts\n * into is impossible to miss — and a logger-only banner is missable: `os serve`\n * swallows ALL of stdout while the kernel boots (its \"boot-quiet\" capture), and\n * `Logger` routes `warn` to stdout, so the one message that matters would be\n * invisible in exactly the situation it exists for. Writing to stderr as well\n * is the same belt-and-braces the kernel already uses for plugin startup\n * failures.\n *\n * Best-effort and never throws: falls back to `console.error`, then to silence\n * on runtimes that have neither (the logger still carries the structured\n * record either way).\n */\nexport function emitDegradedBootBanner(message: string): void {\n const proc = (globalThis as {\n process?: { stderr?: { write?: (chunk: string) => unknown } };\n }).process;\n try {\n if (typeof proc?.stderr?.write === 'function') {\n proc.stderr.write(`${message}\\n`);\n return;\n }\n } catch {\n /* stderr unavailable / closed — fall through to console */\n }\n try {\n (globalThis as { console?: { error?: (msg: string) => void } }).console?.error?.(message);\n } catch {\n /* no output channel at all — the logger record is the remaining trace */\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Environment-variable helpers shared across `@objectstack/*` packages.\n *\n * The framework standardises on `OS_*` prefixed env vars (see AGENTS.md\n * \"Environment Variables\" section). Some historical names predate this\n * convention — `AUTH_SECRET`, `ROOT_DOMAIN`, `OBJECTSTACK_*`, …\n *\n * To migrate without breaking user `.env` files mid-release, call\n * {@link readEnvWithDeprecation} at every legacy read site:\n *\n * const v = readEnvWithDeprecation('OS_AUTH_SECRET', 'AUTH_SECRET');\n *\n * If only the legacy name is set, the value is still returned but a\n * one-shot `console.warn` fires (per-process per-variable) telling\n * operators to rename it.\n */\n\nimport {\n normalizeTenancyPosture,\n TENANCY_POSTURES,\n type TenancyPosture,\n} from '@objectstack/spec/security';\n\nconst _warnedKeys = new Set<string>();\n\n/**\n * Read an env var, preferring the canonical `OS_*` name and falling\n * back to one or more legacy aliases.\n *\n * When only a legacy alias is set, emits a one-shot deprecation warning.\n * The warning is process-wide deduplicated: identical (preferred, legacy)\n * pairs will only warn once even if read from multiple call sites.\n *\n * Legacy aliases are checked in order; the first one with a defined\n * value wins (and triggers the warning for that specific alias).\n *\n * Safe to call from environments where `process` is unavailable (returns\n * `undefined`); the warning is suppressed when running outside Node-like\n * runtimes that lack `console.warn`.\n *\n * @param preferred Canonical OS_*-prefixed env var name.\n * @param legacy Older name (or array of older names) to fall back on.\n * @param options Optional behaviour flags. Set `silent: true` for aliases\n * that remain accepted conventions rather than true legacy\n * names — e.g. `PORT`, which PaaS platforms (Render, Railway,\n * Heroku, Fly, …) inject automatically. Warning on those\n * would nag operators about env they never set.\n * @returns The resolved value, or `undefined` if neither is set.\n */\nexport function readEnvWithDeprecation(\n preferred: string,\n legacy: string | readonly string[],\n options?: { silent?: boolean },\n): string | undefined {\n const env = (globalThis as { process?: { env?: Record<string, string | undefined> } })\n .process?.env;\n if (!env) return undefined;\n\n const preferredValue = env[preferred];\n if (preferredValue !== undefined) return preferredValue;\n\n const legacyList = typeof legacy === 'string' ? [legacy] : legacy;\n for (const legacyName of legacyList) {\n const legacyValue = env[legacyName];\n if (legacyValue !== undefined) {\n const dedupeKey = `${preferred}|${legacyName}`;\n if (!options?.silent && !_warnedKeys.has(dedupeKey)) {\n _warnedKeys.add(dedupeKey);\n const consoleRef = (globalThis as { console?: { warn?: (msg: string) => void } }).console;\n try {\n consoleRef?.warn?.(\n `[ObjectStack] Env var \\`${legacyName}\\` is deprecated; rename it to \\`${preferred}\\`. ` +\n `The legacy name still works for now but will be removed in a future major release.`,\n );\n } catch {\n /* `console.warn` unavailable (exotic runtime) — ignore */\n }\n }\n return legacyValue;\n }\n }\n\n return undefined;\n}\n\n/**\n * Resolve whether the deployment runs in multi-org (a.k.a. multi-tenant) mode.\n *\n * Single source of truth for the `OS_MULTI_ORG_ENABLED` flag. Resolution: the\n * canonical `OS_MULTI_ORG_ENABLED`; else `false`. Any value other than a\n * case-insensitive `'false'` enables it. (The legacy `OS_MULTI_TENANT` alias was\n * removed in 11.0.)\n *\n * Every site that needs to know \"is this multi-org?\" — the SQL driver's\n * tenant-audit gate, the auth manager's `/auth/config` feature flag and\n * org-create guard, the CLI / dev / runtime org-scoping plugin wiring — MUST\n * call this instead of re-reading the env, so the driver, the security layer,\n * and the UI can never disagree about the mode. Previously each site inlined\n * its own `String(... ?? 'false').toLowerCase() !== 'false'` (and the SQL\n * driver read `process.env` directly, skipping the deprecation warning).\n *\n * Reads `process.env` live on each call; memoise at the call site if the\n * result must be stable for the process lifetime.\n */\nexport function resolveMultiOrgEnabled(): boolean {\n const raw = readEnvWithDeprecation('OS_MULTI_ORG_ENABLED', []);\n return String(raw ?? 'false').toLowerCase() !== 'false';\n}\n\n/**\n * [ADR-0105 D1] Resolve the deployment's REQUESTED tenancy posture —\n * `single` | `group` | `isolated`.\n *\n * `OS_TENANCY_POSTURE` is the canonical knob and generalizes the boolean\n * `OS_MULTI_ORG_ENABLED` it supersedes:\n *\n * - set → that posture (the legacy spelling `multi` normalizes to `isolated`)\n * - unset → derived from `OS_MULTI_ORG_ENABLED`: `true` ⇒ `isolated`, else `single`\n *\n * so every existing deployment keeps its current posture with no config change.\n *\n * An unrecognized value THROWS rather than falling back. A typo'd posture that\n * quietly resolved to `single` would silently remove the organization wall —\n * the deployment-layer form of the \"declared but unenforced\" defect ADR-0049\n * forbids, and the same reasoning behind ADR-0093 D5's refusal to boot into\n * undeclared degradation.\n *\n * This resolves what the operator ASKED FOR. Whether the posture is actually\n * enforced is the `tenancy` service's answer (`isolationActive` / `degraded`).\n */\nexport function resolveTenancyPosture(): TenancyPosture {\n // Read through `globalThis` like `readEnvWithDeprecation` does — this package\n // targets non-Node runtimes too, where a bare `process` reference throws.\n const raw = (globalThis as { process?: { env?: Record<string, string | undefined> } })\n .process?.env?.OS_TENANCY_POSTURE;\n if (raw != null && String(raw).trim() !== '') {\n const posture = normalizeTenancyPosture(raw);\n if (!posture) {\n throw new Error(\n `Invalid OS_TENANCY_POSTURE=${JSON.stringify(String(raw))}. ` +\n `Expected one of: ${TENANCY_POSTURES.join(', ')} (or the legacy alias 'multi' = 'isolated'). ` +\n 'Refusing to boot rather than silently falling back to a posture with no organization wall.',\n );\n }\n return posture;\n }\n return resolveMultiOrgEnabled() ? 'isolated' : 'single';\n}\n\n/**\n * Escape hatch for the degraded-tenancy boot guard (ADR-0093 D5).\n *\n * When `OS_MULTI_ORG_ENABLED=true` but the enterprise `@objectstack/organizations`\n * package cannot provide tenant isolation, the platform refuses to boot — a\n * deployment that asked for tenant isolation must not serve traffic pretending\n * to have it (ADR-0049 at the deployment layer). Setting this to a truthy value\n * (`true`/`1`/`on`/`yes`, case-insensitive) boots anyway in an explicitly\n * *degraded* state that is branded everywhere an operator looks. Defaults OFF —\n * an unset flag means \"fail fast\".\n */\nexport function resolveAllowDegradedTenancy(): boolean {\n const raw = readEnvWithDeprecation('OS_ALLOW_DEGRADED_TENANCY', [], { silent: true });\n if (raw == null) return false;\n return ['1', 'true', 'on', 'yes'].includes(String(raw).trim().toLowerCase());\n}\n\n/**\n * Escape hatch for the driver-connect boot guard (framework#3741).\n *\n * `ObjectQLEngine.init()` connects every boot-registered driver and, by\n * default, refuses to boot when any of them fails — a server whose database is\n * unreachable must not report itself started and then 500 every request with an\n * error that reads nothing like \"the database is down\". Failing there is also\n * what gives a driver the ability to REFUSE STARTUP at all: any fatal startup\n * check a driver wants to run (licence, server version, incompatible\n * configuration, missing capability) can simply throw from `connect()`.\n *\n * Setting this to a truthy value (`true`/`1`/`on`/`yes`, case-insensitive)\n * boots anyway, in an explicitly degraded state that is logged loudly at\n * startup. Every query routed to a failed driver fails until the datasource\n * becomes reachable — the underlying clients do re-establish connections on\n * their own (framework#3759) — but the boot-time schema sync those drivers\n * missed is never re-run, so their tables may simply not exist afterwards.\n * Defaults OFF — an unset flag means \"fail fast\".\n */\nexport function resolveAllowDriverConnectFailure(): boolean {\n const raw = readEnvWithDeprecation('OS_ALLOW_DRIVER_CONNECT_FAILURE', [], { silent: true });\n if (raw == null) return false;\n return ['1', 'true', 'on', 'yes'].includes(String(raw).trim().toLowerCase());\n}\n\n/**\n * SINGLE decision point for \"is the MCP HTTP surface (`/api/v1/mcp`) on?\".\n *\n * MCP is a core platform capability and defaults ON: an unset\n * `OS_MCP_SERVER_ENABLED` means the surface is served. Operators opt OUT with\n * an explicit falsy value (`false`/`0`/`off`/`no`, case-insensitive); any\n * other value — including the historical `true` — keeps it on.\n *\n * Every consumer of the flag — the runtime dispatcher's `/mcp` route gate,\n * the CLI's MCP plugin auto-load, the REST `/discovery` advertisement, and\n * the auth service's OAuth/DCR follow-defaults — MUST call this instead of\n * re-reading the env, so the served route, the advertised route, and the\n * authorization track can never disagree.\n *\n * Note the asymmetry with the MCP plugin's *stdio* auto-start\n * ({@link resolveMcpStdioAutoStart}), which stays opt-in and is gated by a\n * SEPARATE switch: attaching a long-lived stdio transport to every process is\n * a side effect no default should impose, while the HTTP surface is served\n * statelessly per-request.\n */\nexport function isMcpServerEnabled(): boolean {\n const raw = readEnvWithDeprecation('OS_MCP_SERVER_ENABLED', 'MCP_SERVER_ENABLED', {\n silent: true,\n });\n if (raw == null) return true;\n return !['0', 'false', 'off', 'no'].includes(raw.trim().toLowerCase());\n}\n\n/**\n * SINGLE decision point for \"should the MCP plugin auto-start a long-lived\n * (stdio) transport?\" — distinct from {@link isMcpServerEnabled}, which governs\n * the stateless HTTP surface.\n *\n * The stdio transport is a different, stricter posture: the plugin bridges the\n * RAW metadata service + data engine onto the long-lived server with NO\n * per-request principal (unscoped — see the `mcp-stdio-authority` conformance\n * row), so it is safe only as a single-operator LOCAL tool and MUST stay\n * opt-in. It defaults OFF.\n *\n * Canonical switch: `OS_MCP_STDIO_ENABLED` (truthy). The plugin also starts it\n * when constructed with `{ autoStart: true }` (that path is checked by the\n * caller, not here).\n *\n * DEPRECATED alias: `OS_MCP_SERVER_ENABLED=true` historically ALSO started\n * stdio — overloading the very var that gates the HTTP surface, so an operator\n * setting it to \"make sure MCP is on\" silently attached an unscoped transport.\n * That trigger still works (with a one-time warning from the caller) for one\n * release; prefer the dedicated var. Note `OS_MCP_SERVER_ENABLED=false` only\n * ever gated the HTTP surface and never started stdio, so it is unaffected.\n *\n * @returns `enabled` — whether stdio auto-start is requested by the env; and\n * `viaDeprecatedAlias` — whether it came through the legacy\n * `OS_MCP_SERVER_ENABLED=true` trigger (so the caller can warn once).\n */\nexport function resolveMcpStdioAutoStart(): { enabled: boolean; viaDeprecatedAlias: boolean } {\n const stdio = readEnvWithDeprecation('OS_MCP_STDIO_ENABLED', [], { silent: true });\n if (stdio != null && ['1', 'true', 'on', 'yes'].includes(stdio.trim().toLowerCase())) {\n return { enabled: true, viaDeprecatedAlias: false };\n }\n // Legacy trigger: only the literal `true` ever started stdio (preserved\n // exactly). `OS_MCP_SERVER_ENABLED=false`/other values never did.\n const legacy = readEnvWithDeprecation('OS_MCP_SERVER_ENABLED', 'MCP_SERVER_ENABLED', { silent: true });\n if (legacy != null && legacy.trim().toLowerCase() === 'true') {\n return { enabled: true, viaDeprecatedAlias: true };\n }\n return { enabled: false, viaDeprecatedAlias: false };\n}\n\n/**\n * Maximum number of organizations a single user may CREATE, from `OS_ORG_LIMIT`.\n * The auth plugin forwards this as better-auth's `organizationLimit` in function\n * form, counting only the caller's `role=owner` memberships — so it caps\n * self-created orgs (each of which can auto-provision a free environment on the\n * cloud control plane) without penalising a user invited into many orgs.\n *\n * Only meaningful when multi-org is enabled ({@link resolveMultiOrgEnabled}).\n * Returns `undefined` when unset or non-positive → no limit (better-auth treats\n * an absent `organizationLimit` as unlimited), preserving self-host behaviour.\n * Deployments that let users self-create orgs SHOULD set a generous cap.\n */\nexport function resolveOrgLimit(): number | undefined {\n const raw = readEnvWithDeprecation('OS_ORG_LIMIT', [], { silent: true });\n if (raw == null || String(raw).trim() === '') return undefined;\n const n = Number.parseInt(String(raw), 10);\n return Number.isFinite(n) && n > 0 ? n : undefined;\n}\n\n/**\n * SINGLE decision point for \"is pinyin search recall on?\" (#2486).\n *\n * Pinyin search is a deployment/locale-level capability, not field metadata:\n * Chinese deployments want it, pure-Japanese/English deployments don't. The\n * flag gates the whole feature end-to-end — the SchemaRegistry's compile-time\n * `__search` companion-column seam AND the `plugin-pinyin-search` populate\n * hooks — so there is no half-state where a column exists but nobody fills it\n * (ADR-0049: no declared-but-unenforced capability).\n *\n * Resolution:\n * 1. An explicit `OS_SEARCH_PINYIN_ENABLED` always wins — truthy\n * (`1`/`true`/`on`/`yes`) enables, anything else disables.\n * 2. When unset, the default derives from the deployment's configured\n * locales (`opts.locales`, e.g. the stack's `i18n.defaultLocale` +\n * `supportedLocales`): any `zh-*` locale turns it on.\n * 3. No env var and no `zh-*` locale → off. OSS / non-Chinese deployments\n * never load `pinyin-pro` and pay zero compute cost.\n *\n * Hosts that know the stack's i18n config (the CLI `serve` boot path) resolve\n * once with locales and stamp the decision back into the env, so downstream\n * consumers constructed without config access (per-engine SchemaRegistry)\n * read the same answer via the no-arg form.\n */\nexport function resolveSearchPinyinEnabled(opts?: { locales?: readonly string[] }): boolean {\n const raw = readEnvWithDeprecation('OS_SEARCH_PINYIN_ENABLED', [], { silent: true });\n if (raw != null && String(raw).trim() !== '') {\n return ['1', 'true', 'on', 'yes'].includes(String(raw).trim().toLowerCase());\n }\n return (opts?.locales ?? []).some((l) => /^zh([-_]|$)/i.test(String(l ?? '').trim()));\n}\n\n/**\n * SINGLE decision point for a sandbox script-runner DEFAULT (ms), resolved from\n * the environment (framework#3259 / ADR-0102).\n *\n * The QuickJS sandbox meters each hook/action invocation against a per-invocation\n * budget. Two dimensions are env-tunable:\n * - the **CPU-time budget** for hooks / actions — how much *VM-active* time a\n * body may burn (built-in 250ms hooks / 5000ms actions); and\n * - the **wall-clock ceiling** — the backstop bounding a body parked forever on\n * a host call that never settles (built-in 30_000ms).\n *\n * The built-in defaults suit a warm, idle host; a heavily loaded or slow host\n * (an oversubscribed CI runner, constrained production hardware) may need a\n * higher floor. This lets an operator raise it once, deployment-wide, instead of\n * re-tuning every call site.\n *\n * Canonical vars (OS_{DOMAIN}_{NAME}, DOMAIN=SANDBOX):\n * - hook → `OS_SANDBOX_HOOK_TIMEOUT_MS`\n * - action → `OS_SANDBOX_ACTION_TIMEOUT_MS`\n * - wallCeiling → `OS_SANDBOX_WALL_CEILING_MS`\n *\n * Only a positive integer is honored; unset / empty / non-numeric / non-positive\n * falls back to `fallback`, so behaviour is byte-for-byte unchanged when the var\n * is absent. This is a FALLBACK default ONLY: an explicit constructor option\n * still wins over it, and (for the CPU budget) a body's own declared `timeoutMs`\n * still wins over the resolved default per the runner's resolution rule.\n */\nexport function resolveSandboxTimeoutMs(\n kind: 'hook' | 'action' | 'wallCeiling',\n fallback: number,\n): number {\n const name =\n kind === 'hook'\n ? 'OS_SANDBOX_HOOK_TIMEOUT_MS'\n : kind === 'action'\n ? 'OS_SANDBOX_ACTION_TIMEOUT_MS'\n : 'OS_SANDBOX_WALL_CEILING_MS';\n const raw = readEnvWithDeprecation(name, [], { silent: true });\n if (raw == null || String(raw).trim() === '') return fallback;\n const n = Number.parseInt(String(raw).trim(), 10);\n return Number.isFinite(n) && n > 0 ? n : fallback;\n}\n\n/**\n * Internal: clear the dedupe set. Test-only; exposed so suite-wide\n * deprecation warnings don't bleed between tests.\n *\n * @internal\n */\nexport function _resetEnvDeprecationWarnings(): void {\n _warnedKeys.clear();\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Shared \"does this error message leak server internals?\" heuristic (#3867).\n *\n * ObjectStack has more than one HTTP boundary. `@objectstack/rest` guards the\n * REST data routes inside `mapDataError`; the dispatcher-plugin routes\n * (`/analytics`, `/packages`, `/i18n`, `/storage`, `/automation`, …) exit\n * through `errorResponseBase`. Before #3867 only the first of those sanitised\n * anything, so a driver error raised under `/analytics/query` reached the\n * client verbatim — a real SQL statement in the response body:\n *\n * ```\n * {\"success\":false,\"error\":{\"message\":\"SELECT FROM \\\"sqlite_sequence\\\" - near \\\"FROM\\\": syntax error\",\"code\":500}}\n * ```\n *\n * \"Do not ship driver internals to clients\" is a property of the HTTP\n * boundary, not of one router, so the predicate lives here — the package both\n * `@objectstack/rest` and `@objectstack/runtime` already depend on — and each\n * boundary applies it in its own envelope. One heuristic, one place to widen\n * when a new dialect's phrasing shows up.\n *\n * Deliberately a *heuristic over the message*, not a driver taxonomy: these\n * errors arrive as plain `Error`s from a half-dozen dialects with no shared\n * shape. It is applied only where the outcome is already a 5xx, so a false\n * positive costs a caller nothing but detail on a response that was a server\n * fault anyway — while the full text still reaches server logs and the\n * error reporter.\n */\n\n/** Generic replacement text for a message that trips {@link looksLikeInternalErrorLeak}. */\nexport const INTERNAL_ERROR_MESSAGE = 'Internal server error';\n\n/**\n * Whether `message` looks like a raw SQL statement or driver/engine dump that\n * must not be returned to an API client.\n *\n * Matches: dialect error codes (`SQLSTATE`, `sqlite_*`), bare statements\n * (a message that *starts* as `SELECT`/`INSERT INTO`/`UPDATE`/`DELETE FROM` —\n * drivers prefix the offending SQL to their message), and constraint-violation\n * dumps, which name physical tables and columns.\n *\n * Does NOT match ordinary business or validation messages, which is why the\n * statement forms are anchored with `startsWith`: a legitimate message may\n * *mention* \"update\" without being one.\n */\nexport function looksLikeInternalErrorLeak(message: string | undefined | null): boolean {\n if (!message) return false;\n const lower = String(message).toLowerCase();\n return (\n lower.includes('sqlite_') ||\n lower.includes('sqlstate') ||\n lower.startsWith('insert into ') ||\n lower.startsWith('update ') ||\n lower.startsWith('select ') ||\n lower.startsWith('delete from ') ||\n lower.includes('constraint failed') ||\n lower.includes('unique constraint') ||\n lower.includes('foreign key')\n );\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * True when a dynamic `import()` / `require.resolve()` failed because the\n * module is simply NOT INSTALLED — as opposed to the module being present but\n * throwing while it loads (a real crash). Checking `err.code` FIRST matters:\n * ESM reports a missing package as `err.code === 'ERR_MODULE_NOT_FOUND'` with\n * the human message `Cannot find package '...'`; matching only the older\n * `Cannot find module` string mis-classifies that as a crash (framework#1595).\n *\n * Single shared owner for this classification (framework#3265): the CLI's\n * optional-plugin guards and `requires` capability resolver delegate here, and\n * cloud's `objectos-runtime` capability loader is expected to adopt it at its\n * next framework pin bump — so the parallel loaders cannot drift apart and\n * re-introduce the #1595 false-alarm class.\n */\nexport function isModuleNotFoundError(err: unknown): boolean {\n const code = (err as { code?: string } | null | undefined)?.code;\n if (code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND') return true;\n const msg = err instanceof Error ? err.message : String(err);\n return msg.includes('Cannot find module') || msg.includes('Cannot find package');\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,25 @@
|
|
|
1
|
+
// src/degraded-boot.ts
|
|
2
|
+
function emitDegradedBootBanner(message) {
|
|
3
|
+
const proc = globalThis.process;
|
|
4
|
+
try {
|
|
5
|
+
if (typeof proc?.stderr?.write === "function") {
|
|
6
|
+
proc.stderr.write(`${message}
|
|
7
|
+
`);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
} catch {
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
globalThis.console?.error?.(message);
|
|
14
|
+
} catch {
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
1
18
|
// src/env.ts
|
|
19
|
+
import {
|
|
20
|
+
normalizeTenancyPosture,
|
|
21
|
+
TENANCY_POSTURES
|
|
22
|
+
} from "@objectstack/spec/security";
|
|
2
23
|
var _warnedKeys = /* @__PURE__ */ new Set();
|
|
3
24
|
function readEnvWithDeprecation(preferred, legacy, options) {
|
|
4
25
|
const env = globalThis.process?.env;
|
|
@@ -29,11 +50,29 @@ function resolveMultiOrgEnabled() {
|
|
|
29
50
|
const raw = readEnvWithDeprecation("OS_MULTI_ORG_ENABLED", []);
|
|
30
51
|
return String(raw ?? "false").toLowerCase() !== "false";
|
|
31
52
|
}
|
|
53
|
+
function resolveTenancyPosture() {
|
|
54
|
+
const raw = globalThis.process?.env?.OS_TENANCY_POSTURE;
|
|
55
|
+
if (raw != null && String(raw).trim() !== "") {
|
|
56
|
+
const posture = normalizeTenancyPosture(raw);
|
|
57
|
+
if (!posture) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Invalid OS_TENANCY_POSTURE=${JSON.stringify(String(raw))}. Expected one of: ${TENANCY_POSTURES.join(", ")} (or the legacy alias 'multi' = 'isolated'). Refusing to boot rather than silently falling back to a posture with no organization wall.`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return posture;
|
|
63
|
+
}
|
|
64
|
+
return resolveMultiOrgEnabled() ? "isolated" : "single";
|
|
65
|
+
}
|
|
32
66
|
function resolveAllowDegradedTenancy() {
|
|
33
67
|
const raw = readEnvWithDeprecation("OS_ALLOW_DEGRADED_TENANCY", [], { silent: true });
|
|
34
68
|
if (raw == null) return false;
|
|
35
69
|
return ["1", "true", "on", "yes"].includes(String(raw).trim().toLowerCase());
|
|
36
70
|
}
|
|
71
|
+
function resolveAllowDriverConnectFailure() {
|
|
72
|
+
const raw = readEnvWithDeprecation("OS_ALLOW_DRIVER_CONNECT_FAILURE", [], { silent: true });
|
|
73
|
+
if (raw == null) return false;
|
|
74
|
+
return ["1", "true", "on", "yes"].includes(String(raw).trim().toLowerCase());
|
|
75
|
+
}
|
|
37
76
|
function isMcpServerEnabled() {
|
|
38
77
|
const raw = readEnvWithDeprecation("OS_MCP_SERVER_ENABLED", "MCP_SERVER_ENABLED", {
|
|
39
78
|
silent: true
|
|
@@ -76,6 +115,14 @@ function _resetEnvDeprecationWarnings() {
|
|
|
76
115
|
_warnedKeys.clear();
|
|
77
116
|
}
|
|
78
117
|
|
|
118
|
+
// src/error-leak.ts
|
|
119
|
+
var INTERNAL_ERROR_MESSAGE = "Internal server error";
|
|
120
|
+
function looksLikeInternalErrorLeak(message) {
|
|
121
|
+
if (!message) return false;
|
|
122
|
+
const lower = String(message).toLowerCase();
|
|
123
|
+
return lower.includes("sqlite_") || lower.includes("sqlstate") || lower.startsWith("insert into ") || lower.startsWith("update ") || lower.startsWith("select ") || lower.startsWith("delete from ") || lower.includes("constraint failed") || lower.includes("unique constraint") || lower.includes("foreign key");
|
|
124
|
+
}
|
|
125
|
+
|
|
79
126
|
// src/module-not-found.ts
|
|
80
127
|
function isModuleNotFoundError(err) {
|
|
81
128
|
const code = err?.code;
|
|
@@ -84,15 +131,20 @@ function isModuleNotFoundError(err) {
|
|
|
84
131
|
return msg.includes("Cannot find module") || msg.includes("Cannot find package");
|
|
85
132
|
}
|
|
86
133
|
export {
|
|
134
|
+
INTERNAL_ERROR_MESSAGE,
|
|
87
135
|
_resetEnvDeprecationWarnings,
|
|
136
|
+
emitDegradedBootBanner,
|
|
88
137
|
isMcpServerEnabled,
|
|
89
138
|
isModuleNotFoundError,
|
|
139
|
+
looksLikeInternalErrorLeak,
|
|
90
140
|
readEnvWithDeprecation,
|
|
91
141
|
resolveAllowDegradedTenancy,
|
|
142
|
+
resolveAllowDriverConnectFailure,
|
|
92
143
|
resolveMcpStdioAutoStart,
|
|
93
144
|
resolveMultiOrgEnabled,
|
|
94
145
|
resolveOrgLimit,
|
|
95
146
|
resolveSandboxTimeoutMs,
|
|
96
|
-
resolveSearchPinyinEnabled
|
|
147
|
+
resolveSearchPinyinEnabled,
|
|
148
|
+
resolveTenancyPosture
|
|
97
149
|
};
|
|
98
150
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/env.ts","../src/module-not-found.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Environment-variable helpers shared across `@objectstack/*` packages.\n *\n * The framework standardises on `OS_*` prefixed env vars (see AGENTS.md\n * \"Environment Variables\" section). Some historical names predate this\n * convention — `AUTH_SECRET`, `ROOT_DOMAIN`, `OBJECTSTACK_*`, …\n *\n * To migrate without breaking user `.env` files mid-release, call\n * {@link readEnvWithDeprecation} at every legacy read site:\n *\n * const v = readEnvWithDeprecation('OS_AUTH_SECRET', 'AUTH_SECRET');\n *\n * If only the legacy name is set, the value is still returned but a\n * one-shot `console.warn` fires (per-process per-variable) telling\n * operators to rename it.\n */\n\nconst _warnedKeys = new Set<string>();\n\n/**\n * Read an env var, preferring the canonical `OS_*` name and falling\n * back to one or more legacy aliases.\n *\n * When only a legacy alias is set, emits a one-shot deprecation warning.\n * The warning is process-wide deduplicated: identical (preferred, legacy)\n * pairs will only warn once even if read from multiple call sites.\n *\n * Legacy aliases are checked in order; the first one with a defined\n * value wins (and triggers the warning for that specific alias).\n *\n * Safe to call from environments where `process` is unavailable (returns\n * `undefined`); the warning is suppressed when running outside Node-like\n * runtimes that lack `console.warn`.\n *\n * @param preferred Canonical OS_*-prefixed env var name.\n * @param legacy Older name (or array of older names) to fall back on.\n * @param options Optional behaviour flags. Set `silent: true` for aliases\n * that remain accepted conventions rather than true legacy\n * names — e.g. `PORT`, which PaaS platforms (Render, Railway,\n * Heroku, Fly, …) inject automatically. Warning on those\n * would nag operators about env they never set.\n * @returns The resolved value, or `undefined` if neither is set.\n */\nexport function readEnvWithDeprecation(\n preferred: string,\n legacy: string | readonly string[],\n options?: { silent?: boolean },\n): string | undefined {\n const env = (globalThis as { process?: { env?: Record<string, string | undefined> } })\n .process?.env;\n if (!env) return undefined;\n\n const preferredValue = env[preferred];\n if (preferredValue !== undefined) return preferredValue;\n\n const legacyList = typeof legacy === 'string' ? [legacy] : legacy;\n for (const legacyName of legacyList) {\n const legacyValue = env[legacyName];\n if (legacyValue !== undefined) {\n const dedupeKey = `${preferred}|${legacyName}`;\n if (!options?.silent && !_warnedKeys.has(dedupeKey)) {\n _warnedKeys.add(dedupeKey);\n const consoleRef = (globalThis as { console?: { warn?: (msg: string) => void } }).console;\n try {\n consoleRef?.warn?.(\n `[ObjectStack] Env var \\`${legacyName}\\` is deprecated; rename it to \\`${preferred}\\`. ` +\n `The legacy name still works for now but will be removed in a future major release.`,\n );\n } catch {\n /* `console.warn` unavailable (exotic runtime) — ignore */\n }\n }\n return legacyValue;\n }\n }\n\n return undefined;\n}\n\n/**\n * Resolve whether the deployment runs in multi-org (a.k.a. multi-tenant) mode.\n *\n * Single source of truth for the `OS_MULTI_ORG_ENABLED` flag. Resolution: the\n * canonical `OS_MULTI_ORG_ENABLED`; else `false`. Any value other than a\n * case-insensitive `'false'` enables it. (The legacy `OS_MULTI_TENANT` alias was\n * removed in 11.0.)\n *\n * Every site that needs to know \"is this multi-org?\" — the SQL driver's\n * tenant-audit gate, the auth manager's `/auth/config` feature flag and\n * org-create guard, the CLI / dev / runtime org-scoping plugin wiring — MUST\n * call this instead of re-reading the env, so the driver, the security layer,\n * and the UI can never disagree about the mode. Previously each site inlined\n * its own `String(... ?? 'false').toLowerCase() !== 'false'` (and the SQL\n * driver read `process.env` directly, skipping the deprecation warning).\n *\n * Reads `process.env` live on each call; memoise at the call site if the\n * result must be stable for the process lifetime.\n */\nexport function resolveMultiOrgEnabled(): boolean {\n const raw = readEnvWithDeprecation('OS_MULTI_ORG_ENABLED', []);\n return String(raw ?? 'false').toLowerCase() !== 'false';\n}\n\n/**\n * Escape hatch for the degraded-tenancy boot guard (ADR-0093 D5).\n *\n * When `OS_MULTI_ORG_ENABLED=true` but the enterprise `@objectstack/organizations`\n * package cannot provide tenant isolation, the platform refuses to boot — a\n * deployment that asked for tenant isolation must not serve traffic pretending\n * to have it (ADR-0049 at the deployment layer). Setting this to a truthy value\n * (`true`/`1`/`on`/`yes`, case-insensitive) boots anyway in an explicitly\n * *degraded* state that is branded everywhere an operator looks. Defaults OFF —\n * an unset flag means \"fail fast\".\n */\nexport function resolveAllowDegradedTenancy(): boolean {\n const raw = readEnvWithDeprecation('OS_ALLOW_DEGRADED_TENANCY', [], { silent: true });\n if (raw == null) return false;\n return ['1', 'true', 'on', 'yes'].includes(String(raw).trim().toLowerCase());\n}\n\n/**\n * SINGLE decision point for \"is the MCP HTTP surface (`/api/v1/mcp`) on?\".\n *\n * MCP is a core platform capability and defaults ON: an unset\n * `OS_MCP_SERVER_ENABLED` means the surface is served. Operators opt OUT with\n * an explicit falsy value (`false`/`0`/`off`/`no`, case-insensitive); any\n * other value — including the historical `true` — keeps it on.\n *\n * Every consumer of the flag — the runtime dispatcher's `/mcp` route gate,\n * the CLI's MCP plugin auto-load, the REST `/discovery` advertisement, and\n * the auth service's OAuth/DCR follow-defaults — MUST call this instead of\n * re-reading the env, so the served route, the advertised route, and the\n * authorization track can never disagree.\n *\n * Note the asymmetry with the MCP plugin's *stdio* auto-start\n * ({@link resolveMcpStdioAutoStart}), which stays opt-in and is gated by a\n * SEPARATE switch: attaching a long-lived stdio transport to every process is\n * a side effect no default should impose, while the HTTP surface is served\n * statelessly per-request.\n */\nexport function isMcpServerEnabled(): boolean {\n const raw = readEnvWithDeprecation('OS_MCP_SERVER_ENABLED', 'MCP_SERVER_ENABLED', {\n silent: true,\n });\n if (raw == null) return true;\n return !['0', 'false', 'off', 'no'].includes(raw.trim().toLowerCase());\n}\n\n/**\n * SINGLE decision point for \"should the MCP plugin auto-start a long-lived\n * (stdio) transport?\" — distinct from {@link isMcpServerEnabled}, which governs\n * the stateless HTTP surface.\n *\n * The stdio transport is a different, stricter posture: the plugin bridges the\n * RAW metadata service + data engine onto the long-lived server with NO\n * per-request principal (unscoped — see the `mcp-stdio-authority` conformance\n * row), so it is safe only as a single-operator LOCAL tool and MUST stay\n * opt-in. It defaults OFF.\n *\n * Canonical switch: `OS_MCP_STDIO_ENABLED` (truthy). The plugin also starts it\n * when constructed with `{ autoStart: true }` (that path is checked by the\n * caller, not here).\n *\n * DEPRECATED alias: `OS_MCP_SERVER_ENABLED=true` historically ALSO started\n * stdio — overloading the very var that gates the HTTP surface, so an operator\n * setting it to \"make sure MCP is on\" silently attached an unscoped transport.\n * That trigger still works (with a one-time warning from the caller) for one\n * release; prefer the dedicated var. Note `OS_MCP_SERVER_ENABLED=false` only\n * ever gated the HTTP surface and never started stdio, so it is unaffected.\n *\n * @returns `enabled` — whether stdio auto-start is requested by the env; and\n * `viaDeprecatedAlias` — whether it came through the legacy\n * `OS_MCP_SERVER_ENABLED=true` trigger (so the caller can warn once).\n */\nexport function resolveMcpStdioAutoStart(): { enabled: boolean; viaDeprecatedAlias: boolean } {\n const stdio = readEnvWithDeprecation('OS_MCP_STDIO_ENABLED', [], { silent: true });\n if (stdio != null && ['1', 'true', 'on', 'yes'].includes(stdio.trim().toLowerCase())) {\n return { enabled: true, viaDeprecatedAlias: false };\n }\n // Legacy trigger: only the literal `true` ever started stdio (preserved\n // exactly). `OS_MCP_SERVER_ENABLED=false`/other values never did.\n const legacy = readEnvWithDeprecation('OS_MCP_SERVER_ENABLED', 'MCP_SERVER_ENABLED', { silent: true });\n if (legacy != null && legacy.trim().toLowerCase() === 'true') {\n return { enabled: true, viaDeprecatedAlias: true };\n }\n return { enabled: false, viaDeprecatedAlias: false };\n}\n\n/**\n * Maximum number of organizations a single user may CREATE, from `OS_ORG_LIMIT`.\n * The auth plugin forwards this as better-auth's `organizationLimit` in function\n * form, counting only the caller's `role=owner` memberships — so it caps\n * self-created orgs (each of which can auto-provision a free environment on the\n * cloud control plane) without penalising a user invited into many orgs.\n *\n * Only meaningful when multi-org is enabled ({@link resolveMultiOrgEnabled}).\n * Returns `undefined` when unset or non-positive → no limit (better-auth treats\n * an absent `organizationLimit` as unlimited), preserving self-host behaviour.\n * Deployments that let users self-create orgs SHOULD set a generous cap.\n */\nexport function resolveOrgLimit(): number | undefined {\n const raw = readEnvWithDeprecation('OS_ORG_LIMIT', [], { silent: true });\n if (raw == null || String(raw).trim() === '') return undefined;\n const n = Number.parseInt(String(raw), 10);\n return Number.isFinite(n) && n > 0 ? n : undefined;\n}\n\n/**\n * SINGLE decision point for \"is pinyin search recall on?\" (#2486).\n *\n * Pinyin search is a deployment/locale-level capability, not field metadata:\n * Chinese deployments want it, pure-Japanese/English deployments don't. The\n * flag gates the whole feature end-to-end — the SchemaRegistry's compile-time\n * `__search` companion-column seam AND the `plugin-pinyin-search` populate\n * hooks — so there is no half-state where a column exists but nobody fills it\n * (ADR-0049: no declared-but-unenforced capability).\n *\n * Resolution:\n * 1. An explicit `OS_SEARCH_PINYIN_ENABLED` always wins — truthy\n * (`1`/`true`/`on`/`yes`) enables, anything else disables.\n * 2. When unset, the default derives from the deployment's configured\n * locales (`opts.locales`, e.g. the stack's `i18n.defaultLocale` +\n * `supportedLocales`): any `zh-*` locale turns it on.\n * 3. No env var and no `zh-*` locale → off. OSS / non-Chinese deployments\n * never load `pinyin-pro` and pay zero compute cost.\n *\n * Hosts that know the stack's i18n config (the CLI `serve` boot path) resolve\n * once with locales and stamp the decision back into the env, so downstream\n * consumers constructed without config access (per-engine SchemaRegistry)\n * read the same answer via the no-arg form.\n */\nexport function resolveSearchPinyinEnabled(opts?: { locales?: readonly string[] }): boolean {\n const raw = readEnvWithDeprecation('OS_SEARCH_PINYIN_ENABLED', [], { silent: true });\n if (raw != null && String(raw).trim() !== '') {\n return ['1', 'true', 'on', 'yes'].includes(String(raw).trim().toLowerCase());\n }\n return (opts?.locales ?? []).some((l) => /^zh([-_]|$)/i.test(String(l ?? '').trim()));\n}\n\n/**\n * SINGLE decision point for a sandbox script-runner DEFAULT (ms), resolved from\n * the environment (framework#3259 / ADR-0102).\n *\n * The QuickJS sandbox meters each hook/action invocation against a per-invocation\n * budget. Two dimensions are env-tunable:\n * - the **CPU-time budget** for hooks / actions — how much *VM-active* time a\n * body may burn (built-in 250ms hooks / 5000ms actions); and\n * - the **wall-clock ceiling** — the backstop bounding a body parked forever on\n * a host call that never settles (built-in 30_000ms).\n *\n * The built-in defaults suit a warm, idle host; a heavily loaded or slow host\n * (an oversubscribed CI runner, constrained production hardware) may need a\n * higher floor. This lets an operator raise it once, deployment-wide, instead of\n * re-tuning every call site.\n *\n * Canonical vars (OS_{DOMAIN}_{NAME}, DOMAIN=SANDBOX):\n * - hook → `OS_SANDBOX_HOOK_TIMEOUT_MS`\n * - action → `OS_SANDBOX_ACTION_TIMEOUT_MS`\n * - wallCeiling → `OS_SANDBOX_WALL_CEILING_MS`\n *\n * Only a positive integer is honored; unset / empty / non-numeric / non-positive\n * falls back to `fallback`, so behaviour is byte-for-byte unchanged when the var\n * is absent. This is a FALLBACK default ONLY: an explicit constructor option\n * still wins over it, and (for the CPU budget) a body's own declared `timeoutMs`\n * still wins over the resolved default per the runner's resolution rule.\n */\nexport function resolveSandboxTimeoutMs(\n kind: 'hook' | 'action' | 'wallCeiling',\n fallback: number,\n): number {\n const name =\n kind === 'hook'\n ? 'OS_SANDBOX_HOOK_TIMEOUT_MS'\n : kind === 'action'\n ? 'OS_SANDBOX_ACTION_TIMEOUT_MS'\n : 'OS_SANDBOX_WALL_CEILING_MS';\n const raw = readEnvWithDeprecation(name, [], { silent: true });\n if (raw == null || String(raw).trim() === '') return fallback;\n const n = Number.parseInt(String(raw).trim(), 10);\n return Number.isFinite(n) && n > 0 ? n : fallback;\n}\n\n/**\n * Internal: clear the dedupe set. Test-only; exposed so suite-wide\n * deprecation warnings don't bleed between tests.\n *\n * @internal\n */\nexport function _resetEnvDeprecationWarnings(): void {\n _warnedKeys.clear();\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * True when a dynamic `import()` / `require.resolve()` failed because the\n * module is simply NOT INSTALLED — as opposed to the module being present but\n * throwing while it loads (a real crash). Checking `err.code` FIRST matters:\n * ESM reports a missing package as `err.code === 'ERR_MODULE_NOT_FOUND'` with\n * the human message `Cannot find package '...'`; matching only the older\n * `Cannot find module` string mis-classifies that as a crash (framework#1595).\n *\n * Single shared owner for this classification (framework#3265): the CLI's\n * optional-plugin guards and `requires` capability resolver delegate here, and\n * cloud's `objectos-runtime` capability loader is expected to adopt it at its\n * next framework pin bump — so the parallel loaders cannot drift apart and\n * re-introduce the #1595 false-alarm class.\n */\nexport function isModuleNotFoundError(err: unknown): boolean {\n const code = (err as { code?: string } | null | undefined)?.code;\n if (code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND') return true;\n const msg = err instanceof Error ? err.message : String(err);\n return msg.includes('Cannot find module') || msg.includes('Cannot find package');\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/degraded-boot.ts","../src/env.ts","../src/error-leak.ts","../src/module-not-found.ts"],"sourcesContent":["// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Degraded-boot reporting, shared by every subsystem that can be told to boot\n * without a datasource it needs.\n *\n * Two of them exist today and they opt in through the *same* operator flag\n * (`OS_ALLOW_DRIVER_CONNECT_FAILURE`, see {@link resolveAllowDriverConnectFailure}):\n *\n * - `ObjectQLEngine.init()` — a boot-registered driver whose `connect()`\n * rejected (framework#3741).\n * - `DatasourceConnectionService` — a declared datasource that objects bind to\n * explicitly, or an `external` one with `validation.onMismatch:'fail'`,\n * that could not be connected (framework#3758).\n *\n * They live in different packages but owe the operator the same thing: the\n * degraded state must be impossible to miss.\n */\n\n/**\n * Emit the degraded-boot banner on a channel the host cannot accidentally\n * silence.\n *\n * `OS_ALLOW_DRIVER_CONNECT_FAILURE` only justifies itself if the state it opts\n * into is impossible to miss — and a logger-only banner is missable: `os serve`\n * swallows ALL of stdout while the kernel boots (its \"boot-quiet\" capture), and\n * `Logger` routes `warn` to stdout, so the one message that matters would be\n * invisible in exactly the situation it exists for. Writing to stderr as well\n * is the same belt-and-braces the kernel already uses for plugin startup\n * failures.\n *\n * Best-effort and never throws: falls back to `console.error`, then to silence\n * on runtimes that have neither (the logger still carries the structured\n * record either way).\n */\nexport function emitDegradedBootBanner(message: string): void {\n const proc = (globalThis as {\n process?: { stderr?: { write?: (chunk: string) => unknown } };\n }).process;\n try {\n if (typeof proc?.stderr?.write === 'function') {\n proc.stderr.write(`${message}\\n`);\n return;\n }\n } catch {\n /* stderr unavailable / closed — fall through to console */\n }\n try {\n (globalThis as { console?: { error?: (msg: string) => void } }).console?.error?.(message);\n } catch {\n /* no output channel at all — the logger record is the remaining trace */\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Environment-variable helpers shared across `@objectstack/*` packages.\n *\n * The framework standardises on `OS_*` prefixed env vars (see AGENTS.md\n * \"Environment Variables\" section). Some historical names predate this\n * convention — `AUTH_SECRET`, `ROOT_DOMAIN`, `OBJECTSTACK_*`, …\n *\n * To migrate without breaking user `.env` files mid-release, call\n * {@link readEnvWithDeprecation} at every legacy read site:\n *\n * const v = readEnvWithDeprecation('OS_AUTH_SECRET', 'AUTH_SECRET');\n *\n * If only the legacy name is set, the value is still returned but a\n * one-shot `console.warn` fires (per-process per-variable) telling\n * operators to rename it.\n */\n\nimport {\n normalizeTenancyPosture,\n TENANCY_POSTURES,\n type TenancyPosture,\n} from '@objectstack/spec/security';\n\nconst _warnedKeys = new Set<string>();\n\n/**\n * Read an env var, preferring the canonical `OS_*` name and falling\n * back to one or more legacy aliases.\n *\n * When only a legacy alias is set, emits a one-shot deprecation warning.\n * The warning is process-wide deduplicated: identical (preferred, legacy)\n * pairs will only warn once even if read from multiple call sites.\n *\n * Legacy aliases are checked in order; the first one with a defined\n * value wins (and triggers the warning for that specific alias).\n *\n * Safe to call from environments where `process` is unavailable (returns\n * `undefined`); the warning is suppressed when running outside Node-like\n * runtimes that lack `console.warn`.\n *\n * @param preferred Canonical OS_*-prefixed env var name.\n * @param legacy Older name (or array of older names) to fall back on.\n * @param options Optional behaviour flags. Set `silent: true` for aliases\n * that remain accepted conventions rather than true legacy\n * names — e.g. `PORT`, which PaaS platforms (Render, Railway,\n * Heroku, Fly, …) inject automatically. Warning on those\n * would nag operators about env they never set.\n * @returns The resolved value, or `undefined` if neither is set.\n */\nexport function readEnvWithDeprecation(\n preferred: string,\n legacy: string | readonly string[],\n options?: { silent?: boolean },\n): string | undefined {\n const env = (globalThis as { process?: { env?: Record<string, string | undefined> } })\n .process?.env;\n if (!env) return undefined;\n\n const preferredValue = env[preferred];\n if (preferredValue !== undefined) return preferredValue;\n\n const legacyList = typeof legacy === 'string' ? [legacy] : legacy;\n for (const legacyName of legacyList) {\n const legacyValue = env[legacyName];\n if (legacyValue !== undefined) {\n const dedupeKey = `${preferred}|${legacyName}`;\n if (!options?.silent && !_warnedKeys.has(dedupeKey)) {\n _warnedKeys.add(dedupeKey);\n const consoleRef = (globalThis as { console?: { warn?: (msg: string) => void } }).console;\n try {\n consoleRef?.warn?.(\n `[ObjectStack] Env var \\`${legacyName}\\` is deprecated; rename it to \\`${preferred}\\`. ` +\n `The legacy name still works for now but will be removed in a future major release.`,\n );\n } catch {\n /* `console.warn` unavailable (exotic runtime) — ignore */\n }\n }\n return legacyValue;\n }\n }\n\n return undefined;\n}\n\n/**\n * Resolve whether the deployment runs in multi-org (a.k.a. multi-tenant) mode.\n *\n * Single source of truth for the `OS_MULTI_ORG_ENABLED` flag. Resolution: the\n * canonical `OS_MULTI_ORG_ENABLED`; else `false`. Any value other than a\n * case-insensitive `'false'` enables it. (The legacy `OS_MULTI_TENANT` alias was\n * removed in 11.0.)\n *\n * Every site that needs to know \"is this multi-org?\" — the SQL driver's\n * tenant-audit gate, the auth manager's `/auth/config` feature flag and\n * org-create guard, the CLI / dev / runtime org-scoping plugin wiring — MUST\n * call this instead of re-reading the env, so the driver, the security layer,\n * and the UI can never disagree about the mode. Previously each site inlined\n * its own `String(... ?? 'false').toLowerCase() !== 'false'` (and the SQL\n * driver read `process.env` directly, skipping the deprecation warning).\n *\n * Reads `process.env` live on each call; memoise at the call site if the\n * result must be stable for the process lifetime.\n */\nexport function resolveMultiOrgEnabled(): boolean {\n const raw = readEnvWithDeprecation('OS_MULTI_ORG_ENABLED', []);\n return String(raw ?? 'false').toLowerCase() !== 'false';\n}\n\n/**\n * [ADR-0105 D1] Resolve the deployment's REQUESTED tenancy posture —\n * `single` | `group` | `isolated`.\n *\n * `OS_TENANCY_POSTURE` is the canonical knob and generalizes the boolean\n * `OS_MULTI_ORG_ENABLED` it supersedes:\n *\n * - set → that posture (the legacy spelling `multi` normalizes to `isolated`)\n * - unset → derived from `OS_MULTI_ORG_ENABLED`: `true` ⇒ `isolated`, else `single`\n *\n * so every existing deployment keeps its current posture with no config change.\n *\n * An unrecognized value THROWS rather than falling back. A typo'd posture that\n * quietly resolved to `single` would silently remove the organization wall —\n * the deployment-layer form of the \"declared but unenforced\" defect ADR-0049\n * forbids, and the same reasoning behind ADR-0093 D5's refusal to boot into\n * undeclared degradation.\n *\n * This resolves what the operator ASKED FOR. Whether the posture is actually\n * enforced is the `tenancy` service's answer (`isolationActive` / `degraded`).\n */\nexport function resolveTenancyPosture(): TenancyPosture {\n // Read through `globalThis` like `readEnvWithDeprecation` does — this package\n // targets non-Node runtimes too, where a bare `process` reference throws.\n const raw = (globalThis as { process?: { env?: Record<string, string | undefined> } })\n .process?.env?.OS_TENANCY_POSTURE;\n if (raw != null && String(raw).trim() !== '') {\n const posture = normalizeTenancyPosture(raw);\n if (!posture) {\n throw new Error(\n `Invalid OS_TENANCY_POSTURE=${JSON.stringify(String(raw))}. ` +\n `Expected one of: ${TENANCY_POSTURES.join(', ')} (or the legacy alias 'multi' = 'isolated'). ` +\n 'Refusing to boot rather than silently falling back to a posture with no organization wall.',\n );\n }\n return posture;\n }\n return resolveMultiOrgEnabled() ? 'isolated' : 'single';\n}\n\n/**\n * Escape hatch for the degraded-tenancy boot guard (ADR-0093 D5).\n *\n * When `OS_MULTI_ORG_ENABLED=true` but the enterprise `@objectstack/organizations`\n * package cannot provide tenant isolation, the platform refuses to boot — a\n * deployment that asked for tenant isolation must not serve traffic pretending\n * to have it (ADR-0049 at the deployment layer). Setting this to a truthy value\n * (`true`/`1`/`on`/`yes`, case-insensitive) boots anyway in an explicitly\n * *degraded* state that is branded everywhere an operator looks. Defaults OFF —\n * an unset flag means \"fail fast\".\n */\nexport function resolveAllowDegradedTenancy(): boolean {\n const raw = readEnvWithDeprecation('OS_ALLOW_DEGRADED_TENANCY', [], { silent: true });\n if (raw == null) return false;\n return ['1', 'true', 'on', 'yes'].includes(String(raw).trim().toLowerCase());\n}\n\n/**\n * Escape hatch for the driver-connect boot guard (framework#3741).\n *\n * `ObjectQLEngine.init()` connects every boot-registered driver and, by\n * default, refuses to boot when any of them fails — a server whose database is\n * unreachable must not report itself started and then 500 every request with an\n * error that reads nothing like \"the database is down\". Failing there is also\n * what gives a driver the ability to REFUSE STARTUP at all: any fatal startup\n * check a driver wants to run (licence, server version, incompatible\n * configuration, missing capability) can simply throw from `connect()`.\n *\n * Setting this to a truthy value (`true`/`1`/`on`/`yes`, case-insensitive)\n * boots anyway, in an explicitly degraded state that is logged loudly at\n * startup. Every query routed to a failed driver fails until the datasource\n * becomes reachable — the underlying clients do re-establish connections on\n * their own (framework#3759) — but the boot-time schema sync those drivers\n * missed is never re-run, so their tables may simply not exist afterwards.\n * Defaults OFF — an unset flag means \"fail fast\".\n */\nexport function resolveAllowDriverConnectFailure(): boolean {\n const raw = readEnvWithDeprecation('OS_ALLOW_DRIVER_CONNECT_FAILURE', [], { silent: true });\n if (raw == null) return false;\n return ['1', 'true', 'on', 'yes'].includes(String(raw).trim().toLowerCase());\n}\n\n/**\n * SINGLE decision point for \"is the MCP HTTP surface (`/api/v1/mcp`) on?\".\n *\n * MCP is a core platform capability and defaults ON: an unset\n * `OS_MCP_SERVER_ENABLED` means the surface is served. Operators opt OUT with\n * an explicit falsy value (`false`/`0`/`off`/`no`, case-insensitive); any\n * other value — including the historical `true` — keeps it on.\n *\n * Every consumer of the flag — the runtime dispatcher's `/mcp` route gate,\n * the CLI's MCP plugin auto-load, the REST `/discovery` advertisement, and\n * the auth service's OAuth/DCR follow-defaults — MUST call this instead of\n * re-reading the env, so the served route, the advertised route, and the\n * authorization track can never disagree.\n *\n * Note the asymmetry with the MCP plugin's *stdio* auto-start\n * ({@link resolveMcpStdioAutoStart}), which stays opt-in and is gated by a\n * SEPARATE switch: attaching a long-lived stdio transport to every process is\n * a side effect no default should impose, while the HTTP surface is served\n * statelessly per-request.\n */\nexport function isMcpServerEnabled(): boolean {\n const raw = readEnvWithDeprecation('OS_MCP_SERVER_ENABLED', 'MCP_SERVER_ENABLED', {\n silent: true,\n });\n if (raw == null) return true;\n return !['0', 'false', 'off', 'no'].includes(raw.trim().toLowerCase());\n}\n\n/**\n * SINGLE decision point for \"should the MCP plugin auto-start a long-lived\n * (stdio) transport?\" — distinct from {@link isMcpServerEnabled}, which governs\n * the stateless HTTP surface.\n *\n * The stdio transport is a different, stricter posture: the plugin bridges the\n * RAW metadata service + data engine onto the long-lived server with NO\n * per-request principal (unscoped — see the `mcp-stdio-authority` conformance\n * row), so it is safe only as a single-operator LOCAL tool and MUST stay\n * opt-in. It defaults OFF.\n *\n * Canonical switch: `OS_MCP_STDIO_ENABLED` (truthy). The plugin also starts it\n * when constructed with `{ autoStart: true }` (that path is checked by the\n * caller, not here).\n *\n * DEPRECATED alias: `OS_MCP_SERVER_ENABLED=true` historically ALSO started\n * stdio — overloading the very var that gates the HTTP surface, so an operator\n * setting it to \"make sure MCP is on\" silently attached an unscoped transport.\n * That trigger still works (with a one-time warning from the caller) for one\n * release; prefer the dedicated var. Note `OS_MCP_SERVER_ENABLED=false` only\n * ever gated the HTTP surface and never started stdio, so it is unaffected.\n *\n * @returns `enabled` — whether stdio auto-start is requested by the env; and\n * `viaDeprecatedAlias` — whether it came through the legacy\n * `OS_MCP_SERVER_ENABLED=true` trigger (so the caller can warn once).\n */\nexport function resolveMcpStdioAutoStart(): { enabled: boolean; viaDeprecatedAlias: boolean } {\n const stdio = readEnvWithDeprecation('OS_MCP_STDIO_ENABLED', [], { silent: true });\n if (stdio != null && ['1', 'true', 'on', 'yes'].includes(stdio.trim().toLowerCase())) {\n return { enabled: true, viaDeprecatedAlias: false };\n }\n // Legacy trigger: only the literal `true` ever started stdio (preserved\n // exactly). `OS_MCP_SERVER_ENABLED=false`/other values never did.\n const legacy = readEnvWithDeprecation('OS_MCP_SERVER_ENABLED', 'MCP_SERVER_ENABLED', { silent: true });\n if (legacy != null && legacy.trim().toLowerCase() === 'true') {\n return { enabled: true, viaDeprecatedAlias: true };\n }\n return { enabled: false, viaDeprecatedAlias: false };\n}\n\n/**\n * Maximum number of organizations a single user may CREATE, from `OS_ORG_LIMIT`.\n * The auth plugin forwards this as better-auth's `organizationLimit` in function\n * form, counting only the caller's `role=owner` memberships — so it caps\n * self-created orgs (each of which can auto-provision a free environment on the\n * cloud control plane) without penalising a user invited into many orgs.\n *\n * Only meaningful when multi-org is enabled ({@link resolveMultiOrgEnabled}).\n * Returns `undefined` when unset or non-positive → no limit (better-auth treats\n * an absent `organizationLimit` as unlimited), preserving self-host behaviour.\n * Deployments that let users self-create orgs SHOULD set a generous cap.\n */\nexport function resolveOrgLimit(): number | undefined {\n const raw = readEnvWithDeprecation('OS_ORG_LIMIT', [], { silent: true });\n if (raw == null || String(raw).trim() === '') return undefined;\n const n = Number.parseInt(String(raw), 10);\n return Number.isFinite(n) && n > 0 ? n : undefined;\n}\n\n/**\n * SINGLE decision point for \"is pinyin search recall on?\" (#2486).\n *\n * Pinyin search is a deployment/locale-level capability, not field metadata:\n * Chinese deployments want it, pure-Japanese/English deployments don't. The\n * flag gates the whole feature end-to-end — the SchemaRegistry's compile-time\n * `__search` companion-column seam AND the `plugin-pinyin-search` populate\n * hooks — so there is no half-state where a column exists but nobody fills it\n * (ADR-0049: no declared-but-unenforced capability).\n *\n * Resolution:\n * 1. An explicit `OS_SEARCH_PINYIN_ENABLED` always wins — truthy\n * (`1`/`true`/`on`/`yes`) enables, anything else disables.\n * 2. When unset, the default derives from the deployment's configured\n * locales (`opts.locales`, e.g. the stack's `i18n.defaultLocale` +\n * `supportedLocales`): any `zh-*` locale turns it on.\n * 3. No env var and no `zh-*` locale → off. OSS / non-Chinese deployments\n * never load `pinyin-pro` and pay zero compute cost.\n *\n * Hosts that know the stack's i18n config (the CLI `serve` boot path) resolve\n * once with locales and stamp the decision back into the env, so downstream\n * consumers constructed without config access (per-engine SchemaRegistry)\n * read the same answer via the no-arg form.\n */\nexport function resolveSearchPinyinEnabled(opts?: { locales?: readonly string[] }): boolean {\n const raw = readEnvWithDeprecation('OS_SEARCH_PINYIN_ENABLED', [], { silent: true });\n if (raw != null && String(raw).trim() !== '') {\n return ['1', 'true', 'on', 'yes'].includes(String(raw).trim().toLowerCase());\n }\n return (opts?.locales ?? []).some((l) => /^zh([-_]|$)/i.test(String(l ?? '').trim()));\n}\n\n/**\n * SINGLE decision point for a sandbox script-runner DEFAULT (ms), resolved from\n * the environment (framework#3259 / ADR-0102).\n *\n * The QuickJS sandbox meters each hook/action invocation against a per-invocation\n * budget. Two dimensions are env-tunable:\n * - the **CPU-time budget** for hooks / actions — how much *VM-active* time a\n * body may burn (built-in 250ms hooks / 5000ms actions); and\n * - the **wall-clock ceiling** — the backstop bounding a body parked forever on\n * a host call that never settles (built-in 30_000ms).\n *\n * The built-in defaults suit a warm, idle host; a heavily loaded or slow host\n * (an oversubscribed CI runner, constrained production hardware) may need a\n * higher floor. This lets an operator raise it once, deployment-wide, instead of\n * re-tuning every call site.\n *\n * Canonical vars (OS_{DOMAIN}_{NAME}, DOMAIN=SANDBOX):\n * - hook → `OS_SANDBOX_HOOK_TIMEOUT_MS`\n * - action → `OS_SANDBOX_ACTION_TIMEOUT_MS`\n * - wallCeiling → `OS_SANDBOX_WALL_CEILING_MS`\n *\n * Only a positive integer is honored; unset / empty / non-numeric / non-positive\n * falls back to `fallback`, so behaviour is byte-for-byte unchanged when the var\n * is absent. This is a FALLBACK default ONLY: an explicit constructor option\n * still wins over it, and (for the CPU budget) a body's own declared `timeoutMs`\n * still wins over the resolved default per the runner's resolution rule.\n */\nexport function resolveSandboxTimeoutMs(\n kind: 'hook' | 'action' | 'wallCeiling',\n fallback: number,\n): number {\n const name =\n kind === 'hook'\n ? 'OS_SANDBOX_HOOK_TIMEOUT_MS'\n : kind === 'action'\n ? 'OS_SANDBOX_ACTION_TIMEOUT_MS'\n : 'OS_SANDBOX_WALL_CEILING_MS';\n const raw = readEnvWithDeprecation(name, [], { silent: true });\n if (raw == null || String(raw).trim() === '') return fallback;\n const n = Number.parseInt(String(raw).trim(), 10);\n return Number.isFinite(n) && n > 0 ? n : fallback;\n}\n\n/**\n * Internal: clear the dedupe set. Test-only; exposed so suite-wide\n * deprecation warnings don't bleed between tests.\n *\n * @internal\n */\nexport function _resetEnvDeprecationWarnings(): void {\n _warnedKeys.clear();\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Shared \"does this error message leak server internals?\" heuristic (#3867).\n *\n * ObjectStack has more than one HTTP boundary. `@objectstack/rest` guards the\n * REST data routes inside `mapDataError`; the dispatcher-plugin routes\n * (`/analytics`, `/packages`, `/i18n`, `/storage`, `/automation`, …) exit\n * through `errorResponseBase`. Before #3867 only the first of those sanitised\n * anything, so a driver error raised under `/analytics/query` reached the\n * client verbatim — a real SQL statement in the response body:\n *\n * ```\n * {\"success\":false,\"error\":{\"message\":\"SELECT FROM \\\"sqlite_sequence\\\" - near \\\"FROM\\\": syntax error\",\"code\":500}}\n * ```\n *\n * \"Do not ship driver internals to clients\" is a property of the HTTP\n * boundary, not of one router, so the predicate lives here — the package both\n * `@objectstack/rest` and `@objectstack/runtime` already depend on — and each\n * boundary applies it in its own envelope. One heuristic, one place to widen\n * when a new dialect's phrasing shows up.\n *\n * Deliberately a *heuristic over the message*, not a driver taxonomy: these\n * errors arrive as plain `Error`s from a half-dozen dialects with no shared\n * shape. It is applied only where the outcome is already a 5xx, so a false\n * positive costs a caller nothing but detail on a response that was a server\n * fault anyway — while the full text still reaches server logs and the\n * error reporter.\n */\n\n/** Generic replacement text for a message that trips {@link looksLikeInternalErrorLeak}. */\nexport const INTERNAL_ERROR_MESSAGE = 'Internal server error';\n\n/**\n * Whether `message` looks like a raw SQL statement or driver/engine dump that\n * must not be returned to an API client.\n *\n * Matches: dialect error codes (`SQLSTATE`, `sqlite_*`), bare statements\n * (a message that *starts* as `SELECT`/`INSERT INTO`/`UPDATE`/`DELETE FROM` —\n * drivers prefix the offending SQL to their message), and constraint-violation\n * dumps, which name physical tables and columns.\n *\n * Does NOT match ordinary business or validation messages, which is why the\n * statement forms are anchored with `startsWith`: a legitimate message may\n * *mention* \"update\" without being one.\n */\nexport function looksLikeInternalErrorLeak(message: string | undefined | null): boolean {\n if (!message) return false;\n const lower = String(message).toLowerCase();\n return (\n lower.includes('sqlite_') ||\n lower.includes('sqlstate') ||\n lower.startsWith('insert into ') ||\n lower.startsWith('update ') ||\n lower.startsWith('select ') ||\n lower.startsWith('delete from ') ||\n lower.includes('constraint failed') ||\n lower.includes('unique constraint') ||\n lower.includes('foreign key')\n );\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * True when a dynamic `import()` / `require.resolve()` failed because the\n * module is simply NOT INSTALLED — as opposed to the module being present but\n * throwing while it loads (a real crash). Checking `err.code` FIRST matters:\n * ESM reports a missing package as `err.code === 'ERR_MODULE_NOT_FOUND'` with\n * the human message `Cannot find package '...'`; matching only the older\n * `Cannot find module` string mis-classifies that as a crash (framework#1595).\n *\n * Single shared owner for this classification (framework#3265): the CLI's\n * optional-plugin guards and `requires` capability resolver delegate here, and\n * cloud's `objectos-runtime` capability loader is expected to adopt it at its\n * next framework pin bump — so the parallel loaders cannot drift apart and\n * re-introduce the #1595 false-alarm class.\n */\nexport function isModuleNotFoundError(err: unknown): boolean {\n const code = (err as { code?: string } | null | undefined)?.code;\n if (code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND') return true;\n const msg = err instanceof Error ? err.message : String(err);\n return msg.includes('Cannot find module') || msg.includes('Cannot find package');\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/types",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "17.0.0-rc.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Shared interfaces describing the ObjectStack Runtime environment",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@objectstack/spec": "
|
|
16
|
+
"@objectstack/spec": "17.0.0-rc.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"typescript": "^6.0.3",
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
"author": "ObjectStack",
|
|
29
29
|
"repository": {
|
|
30
30
|
"type": "git",
|
|
31
|
-
"url": "https://github.com/objectstack-ai/
|
|
31
|
+
"url": "https://github.com/objectstack-ai/objectstack.git",
|
|
32
32
|
"directory": "packages/types"
|
|
33
33
|
},
|
|
34
34
|
"homepage": "https://objectstack.ai/docs",
|
|
35
|
-
"bugs": "https://github.com/objectstack-ai/
|
|
35
|
+
"bugs": "https://github.com/objectstack-ai/objectstack/issues",
|
|
36
36
|
"publishConfig": {
|
|
37
37
|
"access": "public"
|
|
38
38
|
},
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"README.md"
|
|
42
42
|
],
|
|
43
43
|
"engines": {
|
|
44
|
-
"node": ">=
|
|
44
|
+
"node": ">=22.0.0"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
47
|
"build": "tsup --config ../../tsup.config.ts",
|