@objectstack/types 17.0.0-rc.0 → 17.0.0-rc.2
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/CHANGELOG.md +2813 -0
- package/dist/index.d.mts +288 -12
- package/dist/index.d.ts +288 -12
- package/dist/index.js +102 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +95 -1
- package/dist/index.mjs.map +1 -1
- package/dist/node.d.mts +28 -0
- package/dist/node.d.ts +28 -0
- package/dist/node.js +52 -0
- package/dist/node.js.map +1 -0
- package/dist/node.mjs +26 -0
- package/dist/node.mjs.map +1 -0
- package/package.json +11 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TenancyPosture } from '@objectstack/spec/security';
|
|
2
|
+
import { ErrorCode, ApiError } from '@objectstack/spec/api';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Degraded-boot reporting, shared by every subsystem that can be told to boot
|
|
@@ -21,12 +22,21 @@ import { TenancyPosture } from '@objectstack/spec/security';
|
|
|
21
22
|
* silence.
|
|
22
23
|
*
|
|
23
24
|
* `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
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
25
|
+
* into is impossible to miss — and a logger-only banner is missable, because
|
|
26
|
+
* the logger answers to a level the operator sets. `Logger.write()` returns
|
|
27
|
+
* before emitting anything when the record is below `config.level`, so at
|
|
28
|
+
* `--log-level error`, `fatal`, or `silent` this `warn` never reaches ANY
|
|
29
|
+
* stream. A production host running at `error` is exactly the deployment this
|
|
30
|
+
* flag exists for, and is exactly where the banner would vanish. Writing to
|
|
31
|
+
* stderr as well is the same belt-and-braces the kernel already uses for
|
|
32
|
+
* plugin startup failures.
|
|
33
|
+
*
|
|
34
|
+
* A second reason used to be load-bearing and no longer is: `os serve` blanked
|
|
35
|
+
* ALL of stdout while the kernel booted, and `Logger` routes `warn` to stdout,
|
|
36
|
+
* so a boot-phase banner was swallowed at every level. That was framework#4012
|
|
37
|
+
* and is fixed — the boot window buffers and replays `warn`-and-above instead
|
|
38
|
+
* of discarding it. Do not re-derive this helper's necessity from the
|
|
39
|
+
* boot-quiet capture; the level filter is what keeps it alive.
|
|
30
40
|
*
|
|
31
41
|
* Best-effort and never throws: falls back to `console.error`, then to silence
|
|
32
42
|
* on runtimes that have neither (the logger still carries the structured
|
|
@@ -152,6 +162,23 @@ declare function resolveAllowDegradedTenancy(): boolean;
|
|
|
152
162
|
* Defaults OFF — an unset flag means "fail fast".
|
|
153
163
|
*/
|
|
154
164
|
declare function resolveAllowDriverConnectFailure(): boolean;
|
|
165
|
+
/**
|
|
166
|
+
* Escape hatch for plugin-dev's production boot guard (ADR-0115 D6, #3900).
|
|
167
|
+
*
|
|
168
|
+
* `DevPlugin.init()` refuses to run under `NODE_ENV=production`: the stack it
|
|
169
|
+
* assembles is built around an auth secret published inside the npm package and
|
|
170
|
+
* an in-memory driver with persistence off, neither of which a production
|
|
171
|
+
* deployment should acquire by accident. Setting this to a truthy value
|
|
172
|
+
* (`true`/`1`/`on`/`yes`, case-insensitive) boots anyway, in an explicitly
|
|
173
|
+
* degraded state that is branded in the boot log and on the ready banner.
|
|
174
|
+
* Defaults OFF — an unset flag means "fail fast".
|
|
175
|
+
*
|
|
176
|
+
* Lives here rather than as a bare `process.env[…] === '1'` inside plugin-dev so
|
|
177
|
+
* that the whole `OS_ALLOW_*` family answers to one truthy vocabulary: the
|
|
178
|
+
* strict `=== '1'` it replaced fails CLOSED on `OS_ALLOW_DEV_PLUGIN=true`, which
|
|
179
|
+
* is safe but reads to an operator as the flag being broken.
|
|
180
|
+
*/
|
|
181
|
+
declare function resolveAllowDevPlugin(): boolean;
|
|
155
182
|
/**
|
|
156
183
|
* SINGLE decision point for "is the MCP HTTP surface (`/api/v1/mcp`) on?".
|
|
157
184
|
*
|
|
@@ -235,14 +262,46 @@ declare function resolveOrgLimit(): number | undefined;
|
|
|
235
262
|
* 3. No env var and no `zh-*` locale → off. OSS / non-Chinese deployments
|
|
236
263
|
* never load `pinyin-pro` and pay zero compute cost.
|
|
237
264
|
*
|
|
238
|
-
* Hosts that know the stack's i18n config
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
265
|
+
* Hosts that know the stack's i18n config — the CLI `serve` boot path AND the
|
|
266
|
+
* standalone artifact boot (`createStandaloneStack`, which `os migrate`
|
|
267
|
+
* plan/apply and embedders go through) — resolve once with locales and stamp
|
|
268
|
+
* the decision back into the env via {@link stampSearchPinyinEnabled}, so
|
|
269
|
+
* downstream consumers constructed without config access (per-engine
|
|
270
|
+
* SchemaRegistry) read the same answer via the no-arg form (#3955).
|
|
242
271
|
*/
|
|
243
272
|
declare function resolveSearchPinyinEnabled(opts?: {
|
|
244
273
|
locales?: readonly string[];
|
|
245
274
|
}): boolean;
|
|
275
|
+
/**
|
|
276
|
+
* The locales a stack's `i18n` config declares — `defaultLocale`,
|
|
277
|
+
* `fallbackLocale`, then `supportedLocales`. Accepts the config loosely typed
|
|
278
|
+
* (`unknown`) so any boot path can pass whatever its stack config or compiled
|
|
279
|
+
* artifact carries without importing spec schemas; non-string entries and a
|
|
280
|
+
* non-object config collapse to `[]`.
|
|
281
|
+
*/
|
|
282
|
+
declare function collectConfiguredLocales(i18n: unknown): string[];
|
|
283
|
+
/**
|
|
284
|
+
* Resolve the pinyin-search decision from a stack's `i18n` config and stamp a
|
|
285
|
+
* positive result back into `OS_SEARCH_PINYIN_ENABLED` (#2486, #3955).
|
|
286
|
+
*
|
|
287
|
+
* Every boot path that SEES the stack config must stamp, because consumers
|
|
288
|
+
* constructed later without config access (each engine's `SchemaRegistry`
|
|
289
|
+
* provisioning the `__search` companion column, the `plugin-pinyin-search`
|
|
290
|
+
* gate) read the decision through the no-arg
|
|
291
|
+
* {@link resolveSearchPinyinEnabled}. A boot path that skips the stamp
|
|
292
|
+
* computes a schema view WITHOUT the companion columns — which is how
|
|
293
|
+
* `os migrate` came to flag the dev runtime's live `__search` columns as
|
|
294
|
+
* destructive orphans (#3955). Call sites: the CLI `serve`/`dev` boot
|
|
295
|
+
* (`objectstack.config.ts`) and `createStandaloneStack` (compiled artifact —
|
|
296
|
+
* `os migrate plan`/`apply`, embedders).
|
|
297
|
+
*
|
|
298
|
+
* An explicit `OS_SEARCH_PINYIN_ENABLED` always wins — the resolver reads it
|
|
299
|
+
* before consulting locales, so the stamp only materializes the
|
|
300
|
+
* locale-derived default. Only a positive decision is written: "unset" and
|
|
301
|
+
* "off" read identically through the no-arg resolver, and leaving the var
|
|
302
|
+
* untouched keeps a later boot free to re-derive from ITS config.
|
|
303
|
+
*/
|
|
304
|
+
declare function stampSearchPinyinEnabled(i18n: unknown): boolean;
|
|
246
305
|
/**
|
|
247
306
|
* SINGLE decision point for a sandbox script-runner DEFAULT (ms), resolved from
|
|
248
307
|
* the environment (framework#3259 / ADR-0102).
|
|
@@ -284,7 +343,7 @@ declare function _resetEnvDeprecationWarnings(): void;
|
|
|
284
343
|
*
|
|
285
344
|
* ObjectStack has more than one HTTP boundary. `@objectstack/rest` guards the
|
|
286
345
|
* REST data routes inside `mapDataError`; the dispatcher-plugin routes
|
|
287
|
-
* (`/analytics`, `/packages`, `/i18n`, `/
|
|
346
|
+
* (`/analytics`, `/packages`, `/i18n`, `/automation`, …) exit
|
|
288
347
|
* through `errorResponseBase`. Before #3867 only the first of those sanitised
|
|
289
348
|
* anything, so a driver error raised under `/analytics/query` reached the
|
|
290
349
|
* client verbatim — a real SQL statement in the response body:
|
|
@@ -323,6 +382,100 @@ declare const INTERNAL_ERROR_MESSAGE = "Internal server error";
|
|
|
323
382
|
*/
|
|
324
383
|
declare function looksLikeInternalErrorLeak(message: string | undefined | null): boolean;
|
|
325
384
|
|
|
385
|
+
/**
|
|
386
|
+
* Seek-based (keyset) pagination for the batch walks that read a whole object.
|
|
387
|
+
*
|
|
388
|
+
* # Why this exists rather than `limit`/`offset`
|
|
389
|
+
*
|
|
390
|
+
* A background walk that pages with a growing `offset` — rebuild an index,
|
|
391
|
+
* verify file references, backfill a projection — is wrong in two ways that a
|
|
392
|
+
* seek fixes at once.
|
|
393
|
+
*
|
|
394
|
+
* **It can skip rows.** `LIMIT n OFFSET k` is a slice of an arrangement, and
|
|
395
|
+
* the arrangement has to be the *same* one on every page for the slices to
|
|
396
|
+
* partition the set. Drivers now guarantee that for a single read
|
|
397
|
+
* (objectstack#4363), but not across a walk that *mutates as it goes*: a
|
|
398
|
+
* backfill that updates each page, or a rebuild that deletes, changes the very
|
|
399
|
+
* set the next offset counts into. Rows shift past the cursor and are never
|
|
400
|
+
* visited. For a verifier that decides which files are still referenced, or an
|
|
401
|
+
* index rebuild that deletes what it did not see, a skipped row is not a slow
|
|
402
|
+
* page — it is a wrong answer that looks like a clean run. A seek predicate
|
|
403
|
+
* carries the position *in the data* instead of counting from the start, so an
|
|
404
|
+
* update cannot move a row past it and a delete cannot shift one under it.
|
|
405
|
+
*
|
|
406
|
+
* **It is quadratic.** The database must produce and discard every skipped row
|
|
407
|
+
* to honor an offset, so walking n rows in pages of p costs O(n²/p). On a
|
|
408
|
+
* 2M-row table the last pages were measured at ~1.1 s each against ~0.09 s for
|
|
409
|
+
* the first. A seek starts each page at the cursor, so every page costs the
|
|
410
|
+
* same: O(n) for the walk, and index-served throughout.
|
|
411
|
+
*
|
|
412
|
+
* # What it requires
|
|
413
|
+
*
|
|
414
|
+
* A column that is **unique and orderable** — `id` by default, which every
|
|
415
|
+
* object this driver-managed platform creates carries. An object without one
|
|
416
|
+
* (a federated table, ADR-0015) cannot be walked this way; callers that scan
|
|
417
|
+
* arbitrary registry objects already skip what they cannot read, and that is
|
|
418
|
+
* the correct outcome here too rather than a silent partial scan.
|
|
419
|
+
*
|
|
420
|
+
* # Shape
|
|
421
|
+
*
|
|
422
|
+
* `read` is the caller's own query — this owns the loop, the cursor and the
|
|
423
|
+
* `where` merge, and nothing else. Deliberately one implementation rather than
|
|
424
|
+
* the six hand-rolled copies it replaces: the cursor merge is the part that is
|
|
425
|
+
* easy to get subtly wrong (an object whose own `where` already constrains the
|
|
426
|
+
* key), and six copies of it drift silently.
|
|
427
|
+
*
|
|
428
|
+
* @example
|
|
429
|
+
* const walk = keysetWalk<Row>(
|
|
430
|
+
* (q) => engine.find('sys_approval_request', { ...q, fields: ['id'], context: SYSTEM_CTX }),
|
|
431
|
+
* { where: { status: 'pending' }, pageSize: 500 },
|
|
432
|
+
* );
|
|
433
|
+
* for await (const page of walk.pages()) { … }
|
|
434
|
+
* if (walk.truncated) { … }
|
|
435
|
+
*/
|
|
436
|
+
/** The query a {@link keysetWalk} hands its reader: the caller's `where`, narrowed by the cursor. */
|
|
437
|
+
interface KeysetPageQuery {
|
|
438
|
+
/** The caller's `where`, AND-ed with the seek predicate once the walk has a cursor. */
|
|
439
|
+
where?: unknown;
|
|
440
|
+
/** Always ascending on the key column — the walk's order IS the seek order. */
|
|
441
|
+
orderBy: Array<{
|
|
442
|
+
field: string;
|
|
443
|
+
order: 'asc';
|
|
444
|
+
}>;
|
|
445
|
+
/** Page size. */
|
|
446
|
+
limit: number;
|
|
447
|
+
}
|
|
448
|
+
interface KeysetWalkOptions {
|
|
449
|
+
/** The caller's filter, applied to every page. */
|
|
450
|
+
where?: unknown;
|
|
451
|
+
/** Rows per page. */
|
|
452
|
+
pageSize: number;
|
|
453
|
+
/**
|
|
454
|
+
* Stop after this many rows and set {@link KeysetWalk.truncated}. Omit for an
|
|
455
|
+
* unbounded walk. A cap is not a failure — it is how a scan bounds its own
|
|
456
|
+
* cost — but it must be reported, or a partial scan reads as a complete one.
|
|
457
|
+
*/
|
|
458
|
+
max?: number;
|
|
459
|
+
/** Unique, orderable column to seek on. Defaults to `id`. */
|
|
460
|
+
key?: string;
|
|
461
|
+
}
|
|
462
|
+
interface KeysetWalk<T> {
|
|
463
|
+
/** Pages, in key order, until the source is exhausted or `max` is reached. */
|
|
464
|
+
pages(): AsyncGenerator<T[]>;
|
|
465
|
+
/** Rows yielded so far. */
|
|
466
|
+
readonly scanned: number;
|
|
467
|
+
/** True when `max` stopped the walk before the source was exhausted. */
|
|
468
|
+
readonly truncated: boolean;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Walk an object by seeking past the last key rather than counting from the
|
|
472
|
+
* start. See the module comment for why every batch scan should.
|
|
473
|
+
*
|
|
474
|
+
* `read` receives a {@link KeysetPageQuery} and returns the page; the caller
|
|
475
|
+
* owns everything else about the query (projection, context, object name).
|
|
476
|
+
*/
|
|
477
|
+
declare function keysetWalk<T extends Record<string, unknown>>(read: (query: KeysetPageQuery) => Promise<T[]>, options: KeysetWalkOptions): KeysetWalk<T>;
|
|
478
|
+
|
|
326
479
|
/**
|
|
327
480
|
* True when a dynamic `import()` / `require.resolve()` failed because the
|
|
328
481
|
* module is simply NOT INSTALLED — as opposed to the module being present but
|
|
@@ -339,6 +492,129 @@ declare function looksLikeInternalErrorLeak(message: string | undefined | null):
|
|
|
339
492
|
*/
|
|
340
493
|
declare function isModuleNotFoundError(err: unknown): boolean;
|
|
341
494
|
|
|
495
|
+
/**
|
|
496
|
+
* The ONE writer for the declared REST response envelope (#3973).
|
|
497
|
+
*
|
|
498
|
+
* `BaseResponseSchema` (`packages/spec/src/api/contract.zod.ts`) declares one
|
|
499
|
+
* envelope for every REST body the platform emits:
|
|
500
|
+
*
|
|
501
|
+
* { success: true, data }
|
|
502
|
+
* { success: false, error: { code, message } }
|
|
503
|
+
*
|
|
504
|
+
* The schema declares it once. Until this file, the code that *wrote* it was
|
|
505
|
+
* copied per route module — seven `sendOk` / `sendError` pairs after #3843 and
|
|
506
|
+
* #3983 converted the last drifting one, so the envelope's shape lived in
|
|
507
|
+
* fourteen places rather than one.
|
|
508
|
+
*
|
|
509
|
+
* ## Why a shared builder rather than seven agreeing copies
|
|
510
|
+
*
|
|
511
|
+
* `scripts/check-route-envelope.mjs` proves the copies agree today, and that is
|
|
512
|
+
* exactly why this is a cleanup and not a bug fix. But a guard proves agreement;
|
|
513
|
+
* it does not create it. An eighth module starts by copying the pair again —
|
|
514
|
+
* which is not hypothetical, it is the observed history: `share-link-routes.ts`
|
|
515
|
+
* was found by the repo-wide scan already drifting, and its drift had broken
|
|
516
|
+
* `client.shareLinks.create()` / `.list()` through `unwrapResponse` (#3983).
|
|
517
|
+
*
|
|
518
|
+
* ## Why here
|
|
519
|
+
*
|
|
520
|
+
* Placement was the open question in #3973, not design. `packages/spec` is
|
|
521
|
+
* schemas-only (Prime Directive #2), and the callers span `packages/rest`, four
|
|
522
|
+
* `services/*` and one `plugins/*`, which rules out anything that depends on
|
|
523
|
+
* them. `@objectstack/types` depends on nothing but `@objectstack/spec`, so
|
|
524
|
+
* every caller can reach it, and it is where the repo already puts a helper the
|
|
525
|
+
* HTTP boundaries share: {@link looksLikeInternalErrorLeak} lives one file over
|
|
526
|
+
* for the same reason, and made the same argument first — "do not ship driver
|
|
527
|
+
* internals to clients" is a property of the boundary, not of one router.
|
|
528
|
+
*
|
|
529
|
+
* Writing the declared envelope is the same kind of property.
|
|
530
|
+
*
|
|
531
|
+
* ## What this does NOT change
|
|
532
|
+
*
|
|
533
|
+
* Every byte on the wire. The seven pairs were already identical modulo the
|
|
534
|
+
* optional `status` and `extra` parameters unioned below; this file is their
|
|
535
|
+
* union, and each module's driven conformance suite still parses its real
|
|
536
|
+
* bodies against the real spec schemas.
|
|
537
|
+
*
|
|
538
|
+
* The dispatcher surface (`packages/runtime/src/domains/*`) is deliberately not
|
|
539
|
+
* a caller: those handlers RETURN `{ status, body }` for a central sender rather
|
|
540
|
+
* than writing to a response, so they are already consolidated behind their own
|
|
541
|
+
* `deps.success` / `deps.error` helpers and audited by the other half of
|
|
542
|
+
* `check-route-envelope.mjs`.
|
|
543
|
+
*/
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* The only thing an envelope builder needs from a response object.
|
|
547
|
+
*
|
|
548
|
+
* Structural on purpose, so this file depends on no HTTP contract at all:
|
|
549
|
+
* `IHttpResponse` (`@objectstack/spec/contracts`) satisfies it, and so does the
|
|
550
|
+
* `any`-typed `res` the three older route modules still carry. That is what lets
|
|
551
|
+
* a package import the builders without also importing a server abstraction.
|
|
552
|
+
*/
|
|
553
|
+
interface EnvelopeResponse {
|
|
554
|
+
status(code: number): EnvelopeResponse;
|
|
555
|
+
json(body: unknown): unknown;
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Emit a success body in the DECLARED envelope — `{ success: true, data }`.
|
|
559
|
+
*
|
|
560
|
+
* `data` carries the route's payload; it is not spread. A payload duplicated
|
|
561
|
+
* into a stray top-level key (`{ success: true, data: link, link }`) parses
|
|
562
|
+
* clean against `BaseResponseSchema` and is still drift — that shipped on
|
|
563
|
+
* `/share-links` for as long as nobody looked (#4038), which is why
|
|
564
|
+
* `envelopeViolations` exists beside the schema and why there is one `data`
|
|
565
|
+
* slot here rather than a spread.
|
|
566
|
+
*
|
|
567
|
+
* `status` defaults to 200 and is set explicitly even then. Five of the seven
|
|
568
|
+
* modules already did that; the two that called `res.json(...)` bare are
|
|
569
|
+
* unaffected, because the default they were relying on is the value now passed.
|
|
570
|
+
*/
|
|
571
|
+
declare function sendOk(res: EnvelopeResponse, data: unknown, status?: number): void;
|
|
572
|
+
/**
|
|
573
|
+
* Emit an error in the DECLARED envelope — `{ success: false, error: { code,
|
|
574
|
+
* message } }`, with `code` a semantic STRING and `message` a field OF `error`
|
|
575
|
+
* rather than a sibling of it.
|
|
576
|
+
*
|
|
577
|
+
* Both halves of that sentence were once wrong somewhere: `error` was a bare
|
|
578
|
+
* string in `service-storage` and `admin-routes` (so `body.error.message` read
|
|
579
|
+
* `undefined`), and `code` was the human message in `package-routes` (#3675 →
|
|
580
|
+
* #3689 → #3843).
|
|
581
|
+
*
|
|
582
|
+
* ## `code` is the closed ADR-0112 vocabulary, not `string`
|
|
583
|
+
*
|
|
584
|
+
* All seven copies typed this parameter `string`, so an invented code was caught
|
|
585
|
+
* only at runtime, by a conformance suite parsing a driven body against
|
|
586
|
+
* `ApiErrorSchema` — i.e. only on the routes a test happened to drive. `ErrorCode`
|
|
587
|
+
* is `StandardErrorCode ∪ ERROR_CODE_LEDGER` (`error-code-ledger.zod.ts`), the
|
|
588
|
+
* same union that schema validates against, so consolidating here moves the check
|
|
589
|
+
* to compile time for every call site at once. It cost no call-site churn: every
|
|
590
|
+
* code the seven modules emit was already registered.
|
|
591
|
+
*
|
|
592
|
+
* A new code is registered in `ERROR_CODE_LEDGER` under its owning package —
|
|
593
|
+
* and if the condition is generic (not found / permission / validation), the
|
|
594
|
+
* standard catalog is used instead of registering a synonym for it.
|
|
595
|
+
*
|
|
596
|
+
* ## `extra` is `ApiError`'s own optional fields, not a `Record`
|
|
597
|
+
*
|
|
598
|
+
* Merged into `error`, and typed as exactly what `ApiErrorSchema` declares
|
|
599
|
+
* beside `code` and `message` — `details`, `category`, `requestId`, `httpStatus`.
|
|
600
|
+
* `details` is the slot for structured context: `package-routes` puts a partial
|
|
601
|
+
* delete's per-item failures there, `settings-routes` the whole
|
|
602
|
+
* `SettingsActionResult`.
|
|
603
|
+
*
|
|
604
|
+
* This started as `Record<string, unknown>`, because `settings-routes` also hung
|
|
605
|
+
* `namespace` / `key` / `reason` / `fields` beside `code`, which the schema does
|
|
606
|
+
* not declare. Those bodies passed every gate anyway — `ApiErrorSchema` is a
|
|
607
|
+
* plain `z.object`, so unknown keys were STRIPPED rather than rejected, and
|
|
608
|
+
* `envelopeViolations` inspects only the body's top level — making them
|
|
609
|
+
* conformant *by stripping* rather than by declaration. #4224 moved that module's
|
|
610
|
+
* four branches onto `details`, which is what lets the parameter close here.
|
|
611
|
+
*
|
|
612
|
+
* Closing it at the shared builder is the part that lasts: an undeclared sibling
|
|
613
|
+
* is now a compile error in every module at once, rather than a key that quietly
|
|
614
|
+
* evaporates at the schema boundary in whichever module reintroduces it.
|
|
615
|
+
*/
|
|
616
|
+
declare function sendError(res: EnvelopeResponse, status: number, code: ErrorCode, message: string, extra?: Pick<ApiError, 'category' | 'httpStatus' | 'details' | 'requestId'>): void;
|
|
617
|
+
|
|
342
618
|
interface IKernel {
|
|
343
619
|
ql?: any;
|
|
344
620
|
start(): Promise<void>;
|
|
@@ -353,4 +629,4 @@ interface RuntimePlugin {
|
|
|
353
629
|
onStart?: (ctx: RuntimeContext) => void | Promise<void>;
|
|
354
630
|
}
|
|
355
631
|
|
|
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 };
|
|
632
|
+
export { type EnvelopeResponse, type IKernel, INTERNAL_ERROR_MESSAGE, type KeysetPageQuery, type KeysetWalk, type KeysetWalkOptions, type RuntimeContext, type RuntimePlugin, _resetEnvDeprecationWarnings, collectConfiguredLocales, emitDegradedBootBanner, isMcpServerEnabled, isModuleNotFoundError, keysetWalk, looksLikeInternalErrorLeak, readEnvWithDeprecation, resolveAllowDegradedTenancy, resolveAllowDevPlugin, resolveAllowDriverConnectFailure, resolveMcpStdioAutoStart, resolveMultiOrgEnabled, resolveOrgLimit, resolveSandboxTimeoutMs, resolveSearchPinyinEnabled, resolveTenancyPosture, sendError, sendOk, stampSearchPinyinEnabled };
|
package/dist/index.js
CHANGED
|
@@ -22,19 +22,25 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
INTERNAL_ERROR_MESSAGE: () => INTERNAL_ERROR_MESSAGE,
|
|
24
24
|
_resetEnvDeprecationWarnings: () => _resetEnvDeprecationWarnings,
|
|
25
|
+
collectConfiguredLocales: () => collectConfiguredLocales,
|
|
25
26
|
emitDegradedBootBanner: () => emitDegradedBootBanner,
|
|
26
27
|
isMcpServerEnabled: () => isMcpServerEnabled,
|
|
27
28
|
isModuleNotFoundError: () => isModuleNotFoundError,
|
|
29
|
+
keysetWalk: () => keysetWalk,
|
|
28
30
|
looksLikeInternalErrorLeak: () => looksLikeInternalErrorLeak,
|
|
29
31
|
readEnvWithDeprecation: () => readEnvWithDeprecation,
|
|
30
32
|
resolveAllowDegradedTenancy: () => resolveAllowDegradedTenancy,
|
|
33
|
+
resolveAllowDevPlugin: () => resolveAllowDevPlugin,
|
|
31
34
|
resolveAllowDriverConnectFailure: () => resolveAllowDriverConnectFailure,
|
|
32
35
|
resolveMcpStdioAutoStart: () => resolveMcpStdioAutoStart,
|
|
33
36
|
resolveMultiOrgEnabled: () => resolveMultiOrgEnabled,
|
|
34
37
|
resolveOrgLimit: () => resolveOrgLimit,
|
|
35
38
|
resolveSandboxTimeoutMs: () => resolveSandboxTimeoutMs,
|
|
36
39
|
resolveSearchPinyinEnabled: () => resolveSearchPinyinEnabled,
|
|
37
|
-
resolveTenancyPosture: () => resolveTenancyPosture
|
|
40
|
+
resolveTenancyPosture: () => resolveTenancyPosture,
|
|
41
|
+
sendError: () => sendError,
|
|
42
|
+
sendOk: () => sendOk,
|
|
43
|
+
stampSearchPinyinEnabled: () => stampSearchPinyinEnabled
|
|
38
44
|
});
|
|
39
45
|
module.exports = __toCommonJS(index_exports);
|
|
40
46
|
|
|
@@ -110,6 +116,11 @@ function resolveAllowDriverConnectFailure() {
|
|
|
110
116
|
if (raw == null) return false;
|
|
111
117
|
return ["1", "true", "on", "yes"].includes(String(raw).trim().toLowerCase());
|
|
112
118
|
}
|
|
119
|
+
function resolveAllowDevPlugin() {
|
|
120
|
+
const raw = readEnvWithDeprecation("OS_ALLOW_DEV_PLUGIN", [], { silent: true });
|
|
121
|
+
if (raw == null) return false;
|
|
122
|
+
return ["1", "true", "on", "yes"].includes(String(raw).trim().toLowerCase());
|
|
123
|
+
}
|
|
113
124
|
function isMcpServerEnabled() {
|
|
114
125
|
const raw = readEnvWithDeprecation("OS_MCP_SERVER_ENABLED", "MCP_SERVER_ENABLED", {
|
|
115
126
|
silent: true
|
|
@@ -141,6 +152,20 @@ function resolveSearchPinyinEnabled(opts) {
|
|
|
141
152
|
}
|
|
142
153
|
return (opts?.locales ?? []).some((l) => /^zh([-_]|$)/i.test(String(l ?? "").trim()));
|
|
143
154
|
}
|
|
155
|
+
function collectConfiguredLocales(i18n) {
|
|
156
|
+
const cfg = i18n && typeof i18n === "object" ? i18n : {};
|
|
157
|
+
return [
|
|
158
|
+
cfg.defaultLocale,
|
|
159
|
+
cfg.fallbackLocale,
|
|
160
|
+
...Array.isArray(cfg.supportedLocales) ? cfg.supportedLocales : []
|
|
161
|
+
].filter((l) => typeof l === "string");
|
|
162
|
+
}
|
|
163
|
+
function stampSearchPinyinEnabled(i18n) {
|
|
164
|
+
const enabled = resolveSearchPinyinEnabled({ locales: collectConfiguredLocales(i18n) });
|
|
165
|
+
const env = globalThis.process?.env;
|
|
166
|
+
if (enabled && env) env.OS_SEARCH_PINYIN_ENABLED = "true";
|
|
167
|
+
return enabled;
|
|
168
|
+
}
|
|
144
169
|
function resolveSandboxTimeoutMs(kind, fallback) {
|
|
145
170
|
const name = kind === "hook" ? "OS_SANDBOX_HOOK_TIMEOUT_MS" : kind === "action" ? "OS_SANDBOX_ACTION_TIMEOUT_MS" : "OS_SANDBOX_WALL_CEILING_MS";
|
|
146
171
|
const raw = readEnvWithDeprecation(name, [], { silent: true });
|
|
@@ -160,6 +185,67 @@ function looksLikeInternalErrorLeak(message) {
|
|
|
160
185
|
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
186
|
}
|
|
162
187
|
|
|
188
|
+
// src/keyset-walk.ts
|
|
189
|
+
function withCursor(where, key, cursor) {
|
|
190
|
+
const seek = { [key]: { $gt: cursor } };
|
|
191
|
+
if (where == null) return seek;
|
|
192
|
+
if (typeof where === "object" && Object.keys(where).length === 0) return seek;
|
|
193
|
+
return { $and: [where, seek] };
|
|
194
|
+
}
|
|
195
|
+
function keysetWalk(read, options) {
|
|
196
|
+
const key = options.key ?? "id";
|
|
197
|
+
const pageSize = options.pageSize;
|
|
198
|
+
let scanned = 0;
|
|
199
|
+
let truncated = false;
|
|
200
|
+
async function* pages() {
|
|
201
|
+
let cursor = void 0;
|
|
202
|
+
for (; ; ) {
|
|
203
|
+
const want = options.max == null ? pageSize : Math.min(pageSize, options.max - scanned);
|
|
204
|
+
if (want <= 0) {
|
|
205
|
+
truncated = true;
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const clipped = options.max != null && want < pageSize;
|
|
209
|
+
const page = await read({
|
|
210
|
+
where: cursor === void 0 ? options.where : withCursor(options.where, key, cursor),
|
|
211
|
+
orderBy: [{ field: key, order: "asc" }],
|
|
212
|
+
limit: clipped ? want + 1 : want
|
|
213
|
+
});
|
|
214
|
+
if (!Array.isArray(page) || page.length === 0) return;
|
|
215
|
+
const overflow = clipped && page.length > want;
|
|
216
|
+
const emit = overflow ? page.slice(0, want) : page;
|
|
217
|
+
scanned += emit.length;
|
|
218
|
+
yield emit;
|
|
219
|
+
if (overflow) {
|
|
220
|
+
truncated = true;
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const last = emit[emit.length - 1]?.[key];
|
|
224
|
+
if (last === void 0 || last === null) {
|
|
225
|
+
truncated = true;
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (cursor !== void 0 && !(String(last) > String(cursor))) {
|
|
229
|
+
truncated = true;
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
cursor = last;
|
|
233
|
+
if (emit.length < want) return;
|
|
234
|
+
if (options.max != null && scanned >= options.max && !clipped) continue;
|
|
235
|
+
if (options.max != null && scanned >= options.max) return;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
pages,
|
|
240
|
+
get scanned() {
|
|
241
|
+
return scanned;
|
|
242
|
+
},
|
|
243
|
+
get truncated() {
|
|
244
|
+
return truncated;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
163
249
|
// src/module-not-found.ts
|
|
164
250
|
function isModuleNotFoundError(err) {
|
|
165
251
|
const code = err?.code;
|
|
@@ -167,22 +253,36 @@ function isModuleNotFoundError(err) {
|
|
|
167
253
|
const msg = err instanceof Error ? err.message : String(err);
|
|
168
254
|
return msg.includes("Cannot find module") || msg.includes("Cannot find package");
|
|
169
255
|
}
|
|
256
|
+
|
|
257
|
+
// src/response-envelope.ts
|
|
258
|
+
function sendOk(res, data, status = 200) {
|
|
259
|
+
res.status(status).json({ success: true, data });
|
|
260
|
+
}
|
|
261
|
+
function sendError(res, status, code, message, extra) {
|
|
262
|
+
res.status(status).json({ success: false, error: { code, message, ...extra } });
|
|
263
|
+
}
|
|
170
264
|
// Annotate the CommonJS export names for ESM import in node:
|
|
171
265
|
0 && (module.exports = {
|
|
172
266
|
INTERNAL_ERROR_MESSAGE,
|
|
173
267
|
_resetEnvDeprecationWarnings,
|
|
268
|
+
collectConfiguredLocales,
|
|
174
269
|
emitDegradedBootBanner,
|
|
175
270
|
isMcpServerEnabled,
|
|
176
271
|
isModuleNotFoundError,
|
|
272
|
+
keysetWalk,
|
|
177
273
|
looksLikeInternalErrorLeak,
|
|
178
274
|
readEnvWithDeprecation,
|
|
179
275
|
resolveAllowDegradedTenancy,
|
|
276
|
+
resolveAllowDevPlugin,
|
|
180
277
|
resolveAllowDriverConnectFailure,
|
|
181
278
|
resolveMcpStdioAutoStart,
|
|
182
279
|
resolveMultiOrgEnabled,
|
|
183
280
|
resolveOrgLimit,
|
|
184
281
|
resolveSandboxTimeoutMs,
|
|
185
282
|
resolveSearchPinyinEnabled,
|
|
186
|
-
resolveTenancyPosture
|
|
283
|
+
resolveTenancyPosture,
|
|
284
|
+
sendError,
|
|
285
|
+
sendOk,
|
|
286
|
+
stampSearchPinyinEnabled
|
|
187
287
|
});
|
|
188
288
|
//# sourceMappingURL=index.js.map
|