@voltro/runtime 0.11.3 → 0.12.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/CHANGELOG.md +436 -0
- package/dist/index.d.ts +511 -17
- package/dist/index.js +1768 -1355
- package/package.json +6 -6
package/dist/index.d.ts
CHANGED
|
@@ -673,6 +673,11 @@ export declare interface ApiKeyRow {
|
|
|
673
673
|
readonly keyPrefix: string;
|
|
674
674
|
readonly scopes: ReadonlyArray<string> | null;
|
|
675
675
|
readonly lastUsedAt: number | null;
|
|
676
|
+
/** WHO THE KEY ACTS AS — null for an org key. Distinct from `createdBy`. */
|
|
677
|
+
readonly onBehalfOf?: string | null;
|
|
678
|
+
/** Coarse usage counter, buffered — see `apiKeyUsage.ts`. Optional on the type
|
|
679
|
+
* because a row written before the column existed reads back without it. */
|
|
680
|
+
readonly requestCount?: number;
|
|
676
681
|
readonly expiresAt: number | null;
|
|
677
682
|
readonly revokedAt: number | null;
|
|
678
683
|
readonly createdBy: string | null;
|
|
@@ -692,10 +697,28 @@ export declare interface ApiKeyServiceShape {
|
|
|
692
697
|
readonly resolveByHash: (hash: string, now?: number) => Promise<ResolvedApiKey | null>;
|
|
693
698
|
/** Verify a raw token (hashes then resolves). */
|
|
694
699
|
readonly verify: (token: string, now?: number) => Promise<ResolvedApiKey | null>;
|
|
695
|
-
/**
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
700
|
+
/**
|
|
701
|
+
* Revoke a key (irreversible).
|
|
702
|
+
*
|
|
703
|
+
* `tenantId` is the CALLER's tenant and is REQUIRED, not optional: a key id is
|
|
704
|
+
* the only thing needed to revoke, and ids leak (logs, support tickets, an
|
|
705
|
+
* error message). Without this check an admin of tenant A could revoke tenant
|
|
706
|
+
* B's key by id — the guard on the route asks "is an admin", never "an admin
|
|
707
|
+
* of THIS key's tenant". Returns `false` for a key belonging to anyone else,
|
|
708
|
+
* indistinguishable from "no such key", so it cannot be used to probe.
|
|
709
|
+
*
|
|
710
|
+
* Pass `null` only for the tenant-less system key space. There is deliberately
|
|
711
|
+
* no "skip the check" value: an optional scope on a destructive operation is a
|
|
712
|
+
* scope somebody forgets.
|
|
713
|
+
*/
|
|
714
|
+
readonly revoke: (id: string, tenantId: string | null, now?: number) => Promise<boolean>;
|
|
715
|
+
/**
|
|
716
|
+
* Rotate: revoke the old key + issue a replacement with the same
|
|
717
|
+
* name/scopes/tenant. Same required tenant scope as `revoke`, and here it
|
|
718
|
+
* matters more — rotate RETURNS A USABLE TOKEN, so an unscoped version would
|
|
719
|
+
* hand the caller a working credential for another tenant.
|
|
720
|
+
*/
|
|
721
|
+
readonly rotate: (id: string, tenantId: string | null, now?: number) => Promise<IssuedApiKey | null>;
|
|
699
722
|
/** List a tenant's keys (no secrets). */
|
|
700
723
|
readonly list: (tenantId: string | null) => Promise<ReadonlyArray<PublicApiKey>>;
|
|
701
724
|
}
|
|
@@ -708,6 +731,38 @@ export declare interface ApiKeyStore {
|
|
|
708
731
|
readonly patch: (id: string, fields: Partial<ApiKeyRow>) => Promise<void>;
|
|
709
732
|
}
|
|
710
733
|
|
|
734
|
+
export declare interface ApiKeyUsageBuffer {
|
|
735
|
+
/** Record one use. Synchronous, no I/O. */
|
|
736
|
+
readonly touch: (id: string, at?: number) => void;
|
|
737
|
+
/** Write everything pending now. */
|
|
738
|
+
readonly flushNow: () => Promise<void>;
|
|
739
|
+
/** Flush and stop the timer. Idempotent. */
|
|
740
|
+
readonly shutdown: () => Promise<void>;
|
|
741
|
+
/** Pending key count — for tests and the inspect surface. */
|
|
742
|
+
readonly pending: () => number;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
export declare interface ApiKeyUsageBufferOptions {
|
|
746
|
+
/** Apply one key's accumulated window. Must ADD `requests` to the stored
|
|
747
|
+
* count and move `lastUsedAt` forward — never assign either, or a second
|
|
748
|
+
* replica's window overwrites this one. */
|
|
749
|
+
readonly flush: (id: string, delta: ApiKeyUsageDelta) => Promise<void>;
|
|
750
|
+
/** Flush cadence. Default 30s. */
|
|
751
|
+
readonly intervalMs?: number;
|
|
752
|
+
/** Flush early once this many distinct keys are pending, so a burst across
|
|
753
|
+
* many keys doesn't sit unbounded in memory for a whole window. Default 500. */
|
|
754
|
+
readonly maxPending?: number;
|
|
755
|
+
/** Injectable for tests. */
|
|
756
|
+
readonly now?: () => number;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
export declare interface ApiKeyUsageDelta {
|
|
760
|
+
/** Requests counted for this key in the flushed window. Always ≥ 1. */
|
|
761
|
+
readonly requests: number;
|
|
762
|
+
/** The latest use observed in the window (epoch ms). */
|
|
763
|
+
readonly lastUsedAt: number;
|
|
764
|
+
}
|
|
765
|
+
|
|
711
766
|
/**
|
|
712
767
|
* Typed authorization slice on `ctx.access` — the ergonomic, cast-free face of
|
|
713
768
|
* the caller's EFFECTIVE scope set (raw subject scopes ∪ rbac role-derived
|
|
@@ -731,6 +786,28 @@ export declare interface AppAccess {
|
|
|
731
786
|
|
|
732
787
|
export declare interface AppContext {
|
|
733
788
|
readonly store: FluentStore;
|
|
789
|
+
/**
|
|
790
|
+
* The same store, scoped to ONE tenant.
|
|
791
|
+
*
|
|
792
|
+
* For non-request work — a schedule, a subscriber, a workflow step — whose
|
|
793
|
+
* subject is `system` with `tenantId: null`. Reads there see every tenant and
|
|
794
|
+
* a write to a `tenant()` table fails with `TenantScopeViolation`, so a
|
|
795
|
+
* per-tenant cron has to say which tenant it means. Every fan-out cron is
|
|
796
|
+
* literally a loop doing this by hand, and doing it by hand means each
|
|
797
|
+
* `.where('tenantId', t.id)` is one forgotten call away from reading the whole
|
|
798
|
+
* table:
|
|
799
|
+
*
|
|
800
|
+
* for (const t of await ctx.store.select('tenants').all()) {
|
|
801
|
+
* const scoped = ctx.storeForTenant(t.id)
|
|
802
|
+
* await scoped.insert('digests', { … }) // tenantId stamped, not passed
|
|
803
|
+
* }
|
|
804
|
+
*
|
|
805
|
+
* Inside a REQUEST this is almost always the wrong tool: the subject already
|
|
806
|
+
* carries a tenant, and reaching for another one is a cross-tenant read with
|
|
807
|
+
* extra steps. It exists because the system subject has no tenant to infer,
|
|
808
|
+
* not to let a request pick a different one.
|
|
809
|
+
*/
|
|
810
|
+
readonly storeForTenant: (tenantId: string) => FluentStore;
|
|
734
811
|
readonly request: RuntimeContext;
|
|
735
812
|
/** Typed authorization slice — the caller's effective scopes. Use
|
|
736
813
|
* `ctx.access.has(scope)` / `yield* ctx.access.require(scope)` instead of
|
|
@@ -954,6 +1031,14 @@ export declare interface AttachAnalyticsMirrorOptions {
|
|
|
954
1031
|
readonly run?: (effect: Effect.Effect<void, unknown>) => Promise<unknown>;
|
|
955
1032
|
}
|
|
956
1033
|
|
|
1034
|
+
/** The descriptor shape the audit needs — a `defineQuery` result carries it. */
|
|
1035
|
+
export declare interface AuditableQuery {
|
|
1036
|
+
readonly name: string;
|
|
1037
|
+
readonly output: Schema.Schema.Any;
|
|
1038
|
+
/** Declared source table(s) — the tables whose serverOnly columns must not leak. */
|
|
1039
|
+
readonly source?: string | ReadonlyArray<string> | undefined;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
957
1042
|
/**
|
|
958
1043
|
* Resolve once a server returned by {@link startRpcServer} has actually
|
|
959
1044
|
* bound its port (node `'listening'` event). REJECTS on `'error'`
|
|
@@ -1525,10 +1610,14 @@ export declare const countRunningWorkflows: (store: DataStore) => Promise<number
|
|
|
1525
1610
|
* `(input, ctx) => …` executor for a `*.server.ts` default export.
|
|
1526
1611
|
*/
|
|
1527
1612
|
export declare const crud: {
|
|
1528
|
-
/** Tenant-scoped list
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1613
|
+
/** Tenant-scoped list, redacted — with optional filter / sort / pagination from
|
|
1614
|
+
* the request input, so a real list view doesn't have to be hand-written. */
|
|
1615
|
+
list: (table: string, options?: CrudListOptions) => (input: unknown, ctx: AppContext) => Promise<ReadonlyArray<Row>>;
|
|
1616
|
+
/** One row by id, or `null` when absent — never throws. Redacted, with optional
|
|
1617
|
+
* eager-loaded relations (`include`). */
|
|
1618
|
+
getById: (table: string, options?: CrudReadOptions & {
|
|
1619
|
+
readonly include?: CrudInclude;
|
|
1620
|
+
}) => (input: {
|
|
1532
1621
|
readonly id: string;
|
|
1533
1622
|
}, ctx: AppContext) => Promise<Row | null>;
|
|
1534
1623
|
/** Insert the input as a new row (id / tenant / audit auto-stamped). The echoed
|
|
@@ -1539,6 +1628,16 @@ export declare const crud: {
|
|
|
1539
1628
|
update: (table: string, options?: CrudWriteOptions) => (input: {
|
|
1540
1629
|
readonly id: string;
|
|
1541
1630
|
} & Record<string, unknown>, ctx: AppContext) => Promise<Row | null>;
|
|
1631
|
+
/**
|
|
1632
|
+
* Tenant-scoped COUNT of the matching rows — the total a page-based UI needs to
|
|
1633
|
+
* render "page 3 of 12". Takes the SAME `filter` as `list` (share the option
|
|
1634
|
+
* object so the two can't disagree about which rows they mean) and ignores
|
|
1635
|
+
* paging: it counts the whole filtered set, not the current page. A real
|
|
1636
|
+
* `COUNT(*)` aggregate, not a fetch-and-length.
|
|
1637
|
+
*
|
|
1638
|
+
* export default crud.count('absenceRequests', { filter: (i) => ({ status: i.status }) })
|
|
1639
|
+
*/
|
|
1640
|
+
count: (table: string, options?: Pick<CrudListOptions, "filter" | "scope">) => (input: unknown, ctx: AppContext) => Promise<number>;
|
|
1542
1641
|
/** Delete a row by id; returns `{ deleted }`. Guard the DESCRIPTOR. */
|
|
1543
1642
|
remove: (table: string) => (input: {
|
|
1544
1643
|
readonly id: string;
|
|
@@ -1547,6 +1646,101 @@ export declare const crud: {
|
|
|
1547
1646
|
}>;
|
|
1548
1647
|
};
|
|
1549
1648
|
|
|
1649
|
+
/** The eager-load spec `.with()` accepts — nested relations, each branch taking
|
|
1650
|
+
* its own `where` / `orderBy` / `limit` (nested filtering + sort). Reused as-is
|
|
1651
|
+
* so `crud.list`'s `include` is exactly what a hand-written `.with(...)` takes. */
|
|
1652
|
+
export declare type CrudInclude = Parameters<SelectBuilder['with']>[0];
|
|
1653
|
+
|
|
1654
|
+
/**
|
|
1655
|
+
* Options for `crud.list` — the read ergonomics every real list view needs, so a
|
|
1656
|
+
* generated list isn't limited to "all rows". All optional and additive: a bare
|
|
1657
|
+
* `crud.list('t')` still returns every (tenant-scoped, redacted) row.
|
|
1658
|
+
*/
|
|
1659
|
+
export declare interface CrudListOptions extends CrudReadOptions {
|
|
1660
|
+
/**
|
|
1661
|
+
* Build a WHERE from the request input — return a column→value map. Only
|
|
1662
|
+
* entries whose value is not `undefined` are applied, so an absent filter field
|
|
1663
|
+
* is simply ignored (`{ employeeId: input.employeeId, status: input.status }`).
|
|
1664
|
+
* The descriptor's `input` schema declares those fields; this maps them to a
|
|
1665
|
+
* scoped `.where(column, value)` on the store query.
|
|
1666
|
+
*
|
|
1667
|
+
* Request input ONLY, on purpose — see `scope` for the caller-derived half.
|
|
1668
|
+
*/
|
|
1669
|
+
readonly filter?: (input: Readonly<Record<string, unknown>>) => Readonly<Record<string, unknown>>;
|
|
1670
|
+
/**
|
|
1671
|
+
* The caller-derived WHERE — what this subject may SEE, as opposed to what it
|
|
1672
|
+
* asked for. Merged into the same query as `filter`, and it WINS on a key
|
|
1673
|
+
* collision, so a caller cannot widen its own scope by sending that field.
|
|
1674
|
+
*
|
|
1675
|
+
* scope: (ctx) => ({ ownerId: ctx.request.subject.id })
|
|
1676
|
+
*
|
|
1677
|
+
* Why this is a SEPARATE option rather than a second argument to `filter`:
|
|
1678
|
+
* the two answer different questions, and only one of them is a security
|
|
1679
|
+
* boundary. Kept apart, "does this list declare a `scope`?" is a question a
|
|
1680
|
+
* reviewer — or a future boot audit — can actually ask. Folded into `filter`,
|
|
1681
|
+
* it becomes "does this filter happen to read ctx somewhere in its body?",
|
|
1682
|
+
* which nothing can check.
|
|
1683
|
+
*
|
|
1684
|
+
* The gap this closes: tenant scope is applied automatically, but anything
|
|
1685
|
+
* narrower — owner, team, role — was not expressible at all. Replacing a
|
|
1686
|
+
* hand-written handler that carried such a narrowing with `crud.list`
|
|
1687
|
+
* therefore WIDENED the result set, silently and with no error. One app lost
|
|
1688
|
+
* exactly that across eight list views.
|
|
1689
|
+
*
|
|
1690
|
+
* Pass the same `scope` to `crud.count`, or the total contradicts the pages.
|
|
1691
|
+
*/
|
|
1692
|
+
readonly scope?: (ctx: AppContext) => Readonly<Record<string, unknown>>;
|
|
1693
|
+
/**
|
|
1694
|
+
* Page the result from the request. BOTH styles are accepted, so a caller uses
|
|
1695
|
+
* whichever its UI thinks in:
|
|
1696
|
+
*
|
|
1697
|
+
* - **page-based** — `input.page` (1-based) + `input.pageSize` (default 100).
|
|
1698
|
+
* A table UI showing "page 3 of 12" sends `?page=3&pageSize=20`.
|
|
1699
|
+
* - **offset-based** — `input.limit` / `input.offset` (defaults 100 / 0).
|
|
1700
|
+
*
|
|
1701
|
+
* `page` wins when both are present. Pair with `crud.count` for the total a
|
|
1702
|
+
* page-based UI needs to render the last-page number.
|
|
1703
|
+
*/
|
|
1704
|
+
readonly paginate?: boolean;
|
|
1705
|
+
/**
|
|
1706
|
+
* Upper bound on the rows ONE request may ask for (default 1000). The page
|
|
1707
|
+
* size is caller-controlled, so without a cap `?limit=1000000` on a
|
|
1708
|
+
* `publicApi` list is a one-request resource-exhaustion lever for anyone who
|
|
1709
|
+
* can reach the endpoint. Raise it deliberately for an export-style endpoint;
|
|
1710
|
+
* a `limit`/`pageSize` above it is clamped, not rejected.
|
|
1711
|
+
*/
|
|
1712
|
+
readonly maxPageSize?: number;
|
|
1713
|
+
/** Multi-column sort, applied in order (`[{ column: 'createdAt', direction:
|
|
1714
|
+
* 'desc' }, …]`). */
|
|
1715
|
+
readonly sort?: ReadonlyArray<CrudSort>;
|
|
1716
|
+
/**
|
|
1717
|
+
* Eager-load related rows via `.with(...)` — the SAME spec a hand-written query
|
|
1718
|
+
* takes, so nested relations, and per-branch `where` / `orderBy` / `limit`
|
|
1719
|
+
* (nested filtering + sort) all work:
|
|
1720
|
+
*
|
|
1721
|
+
* include: { employee: { with: { team: true } }, tags: { orderBy: 'name' } }
|
|
1722
|
+
*/
|
|
1723
|
+
readonly include?: CrudInclude;
|
|
1724
|
+
/**
|
|
1725
|
+
* SQL column projection — narrow the `SELECT` so wide columns are never READ,
|
|
1726
|
+
* not merely dropped at the wire boundary. The output schema already strips
|
|
1727
|
+
* undeclared columns on encode (so nothing extra ships either way); this is the
|
|
1728
|
+
* PERFORMANCE half: a table with a large `json()` blob or a long text body that
|
|
1729
|
+
* a list view never shows shouldn't cost the read, the transfer from the DB, or
|
|
1730
|
+
* the decode.
|
|
1731
|
+
*
|
|
1732
|
+
* columns: ['id', 'title', 'createdAt'] // the big `body` is never read
|
|
1733
|
+
*
|
|
1734
|
+
* `.serverOnly()` columns are removed from the projection automatically — they
|
|
1735
|
+
* are stripped from the response anyway, so reading them is pure waste.
|
|
1736
|
+
*
|
|
1737
|
+
* TRAP with `include`: an eager branch joins on a foreign key, so a projection
|
|
1738
|
+
* that omits that FK column breaks the relation. Keep the FK in `columns` when
|
|
1739
|
+
* you also pass `include`.
|
|
1740
|
+
*/
|
|
1741
|
+
readonly columns?: ReadonlyArray<string>;
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1550
1744
|
/** Options common to a generated READ. */
|
|
1551
1745
|
export declare interface CrudReadOptions {
|
|
1552
1746
|
/** Columns stripped from every returned row — a secret/credential a generated
|
|
@@ -1556,6 +1750,12 @@ export declare interface CrudReadOptions {
|
|
|
1556
1750
|
readonly redact?: ReadonlyArray<string>;
|
|
1557
1751
|
}
|
|
1558
1752
|
|
|
1753
|
+
/** A single sort term for `crud.list`. */
|
|
1754
|
+
export declare interface CrudSort {
|
|
1755
|
+
readonly column: string;
|
|
1756
|
+
readonly direction?: 'asc' | 'desc';
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1559
1759
|
/** Options for a generated WRITE — `redact` applies to the row the write echoes. */
|
|
1560
1760
|
export declare interface CrudWriteOptions {
|
|
1561
1761
|
readonly redact?: ReadonlyArray<string>;
|
|
@@ -1585,7 +1785,6 @@ export declare interface DataLoader {
|
|
|
1585
1785
|
|
|
1586
1786
|
export { DataStore }
|
|
1587
1787
|
|
|
1588
|
-
/** DataStore-backed store over `_voltro_api_keys`. */
|
|
1589
1788
|
export declare const dataStoreApiKeyStore: (store: DataStore) => ApiKeyStore;
|
|
1590
1789
|
|
|
1591
1790
|
export declare const dataStoreIdempotencyStore: (store: DataStore) => IdempotencyStore;
|
|
@@ -1663,7 +1862,7 @@ export declare const defineEventTrigger: <EventPayload = unknown, WorkflowPayloa
|
|
|
1663
1862
|
* Runtime identity — returns `fn` unchanged; the whole value is the compile-time
|
|
1664
1863
|
* check. See the module header for the failure it prevents.
|
|
1665
1864
|
*/
|
|
1666
|
-
export declare const defineExecutor: <D extends ExecutorDescriptor,
|
|
1865
|
+
export declare const defineExecutor: <D extends ExecutorDescriptor, F extends (input: ExecutorInput<D>, ctx: AppContext) => ExecutorReturn<D, unknown, unknown>>(_descriptor: D, fn: F) => ((input: ExecutorInput<D>, ctx: AppContext) => ReturnType<F>);
|
|
1667
1866
|
|
|
1668
1867
|
/**
|
|
1669
1868
|
* Declare who delivers an effect. One per `*.outbox.ts` file.
|
|
@@ -1928,6 +2127,10 @@ export declare interface EffectStoreOps {
|
|
|
1928
2127
|
* discovered tables. */
|
|
1929
2128
|
export declare const emptySchemaRegistry: SchemaRegistry;
|
|
1930
2129
|
|
|
2130
|
+
/** Turn recording on from code. Idempotent. Must run BEFORE the stores that
|
|
2131
|
+
* should be observed are constructed — `observeStore` decides once, per store. */
|
|
2132
|
+
export declare const enableGraphObservation: () => void;
|
|
2133
|
+
|
|
1931
2134
|
/** Encrypt a value with the registered field cipher (same one `.encrypted()`
|
|
1932
2135
|
* columns use). Throws if no cipher is registered. */
|
|
1933
2136
|
export declare const encryptField: (plaintext: string) => string;
|
|
@@ -2103,6 +2306,9 @@ export declare interface FluentStoreBackend {
|
|
|
2103
2306
|
/** Forget the calling subject's credential. Returns whether a row was removed. */
|
|
2104
2307
|
export declare const forgetCredential: (store: VaultStore, connectionId: string, subjectId: string) => Promise<boolean>;
|
|
2105
2308
|
|
|
2309
|
+
/** A boot-ready message for a set of leaks (empty → `undefined`, i.e. clean). */
|
|
2310
|
+
export declare const formatServerOnlyLeaks: (leaks: ReadonlyArray<ServerOnlyLeak>) => string | undefined;
|
|
2311
|
+
|
|
2106
2312
|
/** Serialise a span's context as a `traceparent` header value. */
|
|
2107
2313
|
export declare const formatTraceparent: (ctx: TraceContext) => string;
|
|
2108
2314
|
|
|
@@ -2444,6 +2650,8 @@ export declare const isIdleNow: (opts: IdleCheckOptions) => Promise<boolean>;
|
|
|
2444
2650
|
|
|
2445
2651
|
export declare const isInfraError: (e: unknown) => boolean;
|
|
2446
2652
|
|
|
2653
|
+
export declare const isObservingGraph: () => boolean;
|
|
2654
|
+
|
|
2447
2655
|
/**
|
|
2448
2656
|
* A subject that AUTHENTICATED but resolved with NO active org — a `user` (not
|
|
2449
2657
|
* `anonymous`/`system`) carrying the empty-string tenant sentinel. Every
|
|
@@ -2484,7 +2692,12 @@ export declare interface IssueInput {
|
|
|
2484
2692
|
readonly name: string;
|
|
2485
2693
|
readonly scopes?: ReadonlyArray<string>;
|
|
2486
2694
|
readonly expiresAt?: Date | number | null;
|
|
2695
|
+
/** WHO MINTED IT. Provenance — set it always, including for an org key. */
|
|
2487
2696
|
readonly createdBy?: string | null;
|
|
2697
|
+
/** WHO IT ACTS AS. Omit for an ORG key (acts as no person); set it for a
|
|
2698
|
+
* personal key, which may be the minter or — when an admin mints for a
|
|
2699
|
+
* colleague — someone else entirely. */
|
|
2700
|
+
readonly onBehalfOf?: string | null;
|
|
2488
2701
|
}
|
|
2489
2702
|
|
|
2490
2703
|
export declare const isWorkflowEventTriggerDefinition: (value: unknown) => value is WorkflowEventTriggerDefinition;
|
|
@@ -2532,6 +2745,22 @@ export declare interface JunctionLinks {
|
|
|
2532
2745
|
add(targetIds: ReadonlyArray<string>): Promise<ReadonlyArray<string>>;
|
|
2533
2746
|
/** Unlink `targetIds` that are currently linked. Returns the ids actually removed. */
|
|
2534
2747
|
remove(targetIds: ReadonlyArray<string>): Promise<ReadonlyArray<string>>;
|
|
2748
|
+
/**
|
|
2749
|
+
* Reconcile links that carry PER-ROW PAYLOAD — a junction with business columns
|
|
2750
|
+
* (a membership `role`, a `capacity` value). Each row is
|
|
2751
|
+
* `{ [targetColumn]: id, …payload }`; the diff is on the (source, target) pair:
|
|
2752
|
+
* an added row is inserted with its payload, a removed row deleted, and a
|
|
2753
|
+
* SURVIVING row whose payload actually changed is UPDATED — one whose payload
|
|
2754
|
+
* is unchanged is left untouched, so a reactive consumer sees a change only
|
|
2755
|
+
* where the payload differs (the `set(targetIds)` form can't express payload;
|
|
2756
|
+
* this is the drop+reinsert replacement for a junction that carries data).
|
|
2757
|
+
* Payload is compared by strict equality per column (scalars — capacity, role).
|
|
2758
|
+
*/
|
|
2759
|
+
setRows(rows: ReadonlyArray<Readonly<Record<string, unknown>>>): Promise<{
|
|
2760
|
+
readonly added: ReadonlyArray<string>;
|
|
2761
|
+
readonly removed: ReadonlyArray<string>;
|
|
2762
|
+
readonly updated: ReadonlyArray<string>;
|
|
2763
|
+
}>;
|
|
2535
2764
|
}
|
|
2536
2765
|
|
|
2537
2766
|
export declare interface KvFacade {
|
|
@@ -2691,8 +2920,30 @@ export declare const makeAesCipher: (key: Buffer) => FieldCipher;
|
|
|
2691
2920
|
|
|
2692
2921
|
export declare const makeApiKeyService: (store: ApiKeyStore, opts?: {
|
|
2693
2922
|
prefix?: string;
|
|
2923
|
+
/** Buffers `lastUsedAt` + `requestCount` instead of writing a row per auth
|
|
2924
|
+
* check. Omit to keep the synchronous stamp (see `resolveByHash`). */
|
|
2925
|
+
usage?: ApiKeyUsageBuffer;
|
|
2694
2926
|
}) => ApiKeyServiceShape;
|
|
2695
2927
|
|
|
2928
|
+
export declare const makeApiKeyUsageBuffer: (options: ApiKeyUsageBufferOptions) => ApiKeyUsageBuffer;
|
|
2929
|
+
|
|
2930
|
+
/**
|
|
2931
|
+
* The flusher a buffered `ApiKeyUsageBuffer` is built with: read-modify-write of
|
|
2932
|
+
* `requestCount` + `lastUsedAt` for one key, once per window.
|
|
2933
|
+
*
|
|
2934
|
+
* Not atomic across replicas, and that is a deliberate limit rather than an
|
|
2935
|
+
* oversight. Two pods flushing the same key in the same instant can lose one
|
|
2936
|
+
* window's delta. Making it exact would need a dialect-specific
|
|
2937
|
+
* `SET requestCount = requestCount + ?`, which is real complexity in service of
|
|
2938
|
+
* a field whose stated purpose is "is this key still in use, and roughly how
|
|
2939
|
+
* much". If this number ever needs to be exact, that is the signal it is being
|
|
2940
|
+
* used for something it was not built for (billing), not a signal to add locking.
|
|
2941
|
+
*/
|
|
2942
|
+
export declare const makeApiKeyUsageFlusher: (store: ApiKeyStore) => (id: string, delta: {
|
|
2943
|
+
readonly requests: number;
|
|
2944
|
+
readonly lastUsedAt: number;
|
|
2945
|
+
}) => Promise<void>;
|
|
2946
|
+
|
|
2696
2947
|
/** Build the `ctx.access` slice for a subject. Reads the effective-scope seam
|
|
2697
2948
|
* LIVE (via getters/closures) so a scope rbac resolves after context
|
|
2698
2949
|
* construction is still reflected. Used by the shared AppContext builder AND
|
|
@@ -2794,6 +3045,24 @@ export declare const makeListConnectionsExecutor: (deps: ConnectionBuiltinDeps)
|
|
|
2794
3045
|
*/
|
|
2795
3046
|
export declare const makeMutationRunner: (deps: MutationRunnerDeps) => (mutation: MutationLike, input: unknown, requestContext: ServeRequestContext) => Promise<unknown>;
|
|
2796
3047
|
|
|
3048
|
+
/**
|
|
3049
|
+
* Run a QUERY once and resolve its value — the read counterpart to
|
|
3050
|
+
* `makeMutationRunner` / `makeActionRunner`, for callers that have no
|
|
3051
|
+
* subscription: a descriptor projected to a public REST endpoint.
|
|
3052
|
+
*
|
|
3053
|
+
* It is deliberately built ON TOP of `makeQueryDescriptorProducer` rather than
|
|
3054
|
+
* beside it. That producer is where the declarative `guards:` gate, the
|
|
3055
|
+
* per-request row-filter resolution and the tenant-scoping `finalize` live, so
|
|
3056
|
+
* reusing it makes the one-shot path enforce EXACTLY what the WS path enforces.
|
|
3057
|
+
* A second implementation here would be an authorization bypass waiting to
|
|
3058
|
+
* happen — the REST projection of a guarded query must fail the same way the
|
|
3059
|
+
* socket does, and it does because it runs the same code.
|
|
3060
|
+
*
|
|
3061
|
+
* Both handler shapes resolve: a descriptor-returning (reactive) query is
|
|
3062
|
+
* finalized and executed to rows; a computed query yields its value.
|
|
3063
|
+
*/
|
|
3064
|
+
export declare const makeOneShotQueryRunner: <D>(deps: OneShotQueryRunnerDeps<D>) => (query: MutationLike, input: unknown, requestContext: ServeRequestContext) => Promise<unknown>;
|
|
3065
|
+
|
|
2797
3066
|
export declare const makeOutboxFacade: (deps: OutboxFacadeDeps) => OutboxFacade;
|
|
2798
3067
|
|
|
2799
3068
|
export declare const makePostCommitWorkflowFacade: (base: WorkflowsAppContext, afterCommit: (work: () => Promise<unknown>) => void) => WorkflowsAppContext;
|
|
@@ -2837,6 +3106,25 @@ export declare const makeQueryDescriptorProducer: <D>(deps: QueryProducerDeps<D>
|
|
|
2837
3106
|
*/
|
|
2838
3107
|
export declare const makeQueryReauthorizer: (query: MutationLike, input: unknown) => (subject: Subject) => () => Promise<unknown>;
|
|
2839
3108
|
|
|
3109
|
+
/**
|
|
3110
|
+
* Subscribe a QUERY and stream its events to a non-rpc consumer — the SSE
|
|
3111
|
+
* projection of a `publicApi` query. Like `makeOneShotQueryRunner`, it runs the
|
|
3112
|
+
* shared `makeQueryDescriptorProducer`, so the declarative `guards:`, the row
|
|
3113
|
+
* filter and tenant scoping are the same code the socket path runs: an SSE
|
|
3114
|
+
* endpoint is exactly as gated as the WebSocket one.
|
|
3115
|
+
*
|
|
3116
|
+
* Returns an unsubscribe SYNCHRONOUSLY (the HTTP layer needs one immediately)
|
|
3117
|
+
* while the subscription opens in the background; unsubscribing before it
|
|
3118
|
+
* finishes tears it down as soon as it exists, so a client that disconnects
|
|
3119
|
+
* mid-setup cannot leak a subscription. A setup failure — including a guard
|
|
3120
|
+
* denial — is emitted as one `error` event rather than thrown, because by then
|
|
3121
|
+
* the response headers are already on the wire.
|
|
3122
|
+
*/
|
|
3123
|
+
export declare const makeQuerySubscriber: <D>(deps: QuerySubscriberDeps<D>) => (query: MutationLike, input: unknown, requestContext: ServeRequestContext, emit: (event: {
|
|
3124
|
+
readonly _tag: string;
|
|
3125
|
+
readonly [k: string]: unknown;
|
|
3126
|
+
}) => void) => (() => void);
|
|
3127
|
+
|
|
2840
3128
|
export declare const makeRouterActivity: () => RouterActivity;
|
|
2841
3129
|
|
|
2842
3130
|
/**
|
|
@@ -3217,6 +3505,50 @@ export declare interface OAuth2ConnectionDefinition extends ConnectionDefinition
|
|
|
3217
3505
|
readonly identify?: (tokens: ConnectionTokens) => Promise<ConnectionAccount>;
|
|
3218
3506
|
}
|
|
3219
3507
|
|
|
3508
|
+
/** One recorded `(procedure → table, op)` edge. */
|
|
3509
|
+
export declare interface ObservedEdge {
|
|
3510
|
+
readonly tag: string;
|
|
3511
|
+
readonly kind: ObservedProcedure['kind'];
|
|
3512
|
+
readonly table: string;
|
|
3513
|
+
readonly op: ObservedOp;
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3516
|
+
export declare interface ObservedGraph {
|
|
3517
|
+
/** Schema version of this file's shape, so `check` can refuse an old one
|
|
3518
|
+
* loudly instead of silently diffing against a different meaning. */
|
|
3519
|
+
readonly version: 1;
|
|
3520
|
+
readonly edges: ReadonlyArray<ObservedEdge>;
|
|
3521
|
+
/** Tags seen entering the recorder at all — the difference between "this
|
|
3522
|
+
* procedure touched no table" and "this procedure never ran", which is the
|
|
3523
|
+
* distinction the whole feature stands on. */
|
|
3524
|
+
readonly exercised: ReadonlyArray<string>;
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
/** Snapshot everything recorded so far. */
|
|
3528
|
+
export declare const observedGraph: () => ObservedGraph;
|
|
3529
|
+
|
|
3530
|
+
/** What a procedure did to a table. `read` covers every query path. */
|
|
3531
|
+
export declare type ObservedOp = 'read' | 'insert' | 'update' | 'delete' | 'upsert';
|
|
3532
|
+
|
|
3533
|
+
export declare interface ObservedProcedure {
|
|
3534
|
+
/** The procedure's effective rpc tag (`notes.create`). */
|
|
3535
|
+
readonly tag: string;
|
|
3536
|
+
readonly kind: 'query' | 'mutation' | 'action' | 'stream' | 'schedule' | 'workflow';
|
|
3537
|
+
}
|
|
3538
|
+
|
|
3539
|
+
/**
|
|
3540
|
+
* Wrap a `DataStore` so every operation is recorded. Returns the store unchanged
|
|
3541
|
+
* when recording is off, so the production path keeps the exact same object
|
|
3542
|
+
* (no added indirection, no added allocation).
|
|
3543
|
+
*
|
|
3544
|
+
* Wrapping the store BENEATH the mixin middleware rather than instrumenting the
|
|
3545
|
+
* middleware's ~20 public methods is deliberate: those methods delegate to each
|
|
3546
|
+
* other (`one`/`first`/`maybeOne` all go through `query`), so instrumenting them
|
|
3547
|
+
* individually would double-count some paths and miss any new one. Underneath,
|
|
3548
|
+
* each operation passes exactly once.
|
|
3549
|
+
*/
|
|
3550
|
+
export declare const observeStore: <S extends object>(store: S) => S;
|
|
3551
|
+
|
|
3220
3552
|
/**
|
|
3221
3553
|
* Listen for re-bind events. Returns an unsubscribe function. Used by
|
|
3222
3554
|
* the dispatcher to re-scope active subscriptions when a connection's
|
|
@@ -3225,6 +3557,15 @@ export declare interface OAuth2ConnectionDefinition extends ConnectionDefinition
|
|
|
3225
3557
|
*/
|
|
3226
3558
|
export declare const onBindConnectionSubject: (listener: (clientId: number, subject: Subject) => void) => () => void;
|
|
3227
3559
|
|
|
3560
|
+
export declare interface OneShotQueryRunnerDeps<D> extends QueryProducerDeps<D> {
|
|
3561
|
+
/**
|
|
3562
|
+
* Execute a FINALIZED (tenant-scoped) descriptor → rows. On the WS path the
|
|
3563
|
+
* dispatcher owns this step; a ONE-SHOT read (a `publicApi` REST GET, where
|
|
3564
|
+
* there is no subscription to drive) needs it inline.
|
|
3565
|
+
*/
|
|
3566
|
+
readonly queryRows: (descriptor: D) => Promise<ReadonlyArray<unknown>>;
|
|
3567
|
+
}
|
|
3568
|
+
|
|
3228
3569
|
/** Thrown by `.expectVersion(n).set(...)` when the optimistic-lock guard
|
|
3229
3570
|
* matched no row (the row was concurrently updated or deleted). */
|
|
3230
3571
|
export declare class OptimisticLockError extends OptimisticLockError_base {
|
|
@@ -3402,6 +3743,24 @@ export declare interface PatConnectionDefinition extends ConnectionDefinitionCom
|
|
|
3402
3743
|
readonly validate?: (token: string) => Promise<ConnectionAccount>;
|
|
3403
3744
|
}
|
|
3404
3745
|
|
|
3746
|
+
/**
|
|
3747
|
+
* TOP-LEVEL property names of a struct-ish schema, split by optionality.
|
|
3748
|
+
*
|
|
3749
|
+
* Deliberately not `schemaPropertyNames` (`./serverOnlyAudit`), which answers a
|
|
3750
|
+
* DIFFERENT question: that one walks the whole schema recursively and flattens
|
|
3751
|
+
* every name at any depth, because a leak check cares whether a column name
|
|
3752
|
+
* appears anywhere in an output. This one asks "what does this payload require
|
|
3753
|
+
* at its top level", so depth would be wrong and optionality is the point.
|
|
3754
|
+
*
|
|
3755
|
+
* Best effort — at runtime it only makes an error readable and never decides
|
|
3756
|
+
* validity — but `voltro doctor`'s static `workflows.start` audit calls the same
|
|
3757
|
+
* function, so the two cannot disagree about what a payload requires.
|
|
3758
|
+
*/
|
|
3759
|
+
export declare const payloadPropertyNames: (schema: Schema.Schema.Any) => {
|
|
3760
|
+
readonly required: ReadonlyArray<string>;
|
|
3761
|
+
readonly optional: ReadonlyArray<string>;
|
|
3762
|
+
};
|
|
3763
|
+
|
|
3405
3764
|
/** The 3-arg signature a plugin sees on its bind-ctx. */
|
|
3406
3765
|
export declare type PluginScheduleCoordinated = (name: string, intervalMs: number, effect: () => void | Promise<void>) => CoordinatedScheduleHandle;
|
|
3407
3766
|
|
|
@@ -3529,6 +3888,13 @@ export declare interface QueryProducerDeps<D> {
|
|
|
3529
3888
|
readonly interceptor?: ServeRpcInterceptor;
|
|
3530
3889
|
}
|
|
3531
3890
|
|
|
3891
|
+
export declare interface QuerySubscriberDeps<D> extends QueryProducerDeps<D> {
|
|
3892
|
+
/** Open a dispatcher subscription for a finalized descriptor. */
|
|
3893
|
+
readonly subscribeDescriptor: (descriptor: D, emit: (event: unknown) => void, context: ServeRequestContext) => Promise<() => void>;
|
|
3894
|
+
/** Open a dispatcher subscription for a COMPUTED query (re-runs on source change). */
|
|
3895
|
+
readonly subscribeComputed: (computed: ComputedQuery, emit: (event: unknown) => void, context: ServeRequestContext) => Promise<() => void>;
|
|
3896
|
+
}
|
|
3897
|
+
|
|
3532
3898
|
/** What a reaction does when it fires — run an agent or start a workflow. Both
|
|
3533
3899
|
* identified by name; the serve layer resolves + runs them as `agentActor`. */
|
|
3534
3900
|
export declare type ReactionAct = {
|
|
@@ -3685,6 +4051,14 @@ export declare const recordSubscriptionActive: (label: string, delta: number) =>
|
|
|
3685
4051
|
* latency + a delivery counter. Rides the framework's existing per-delivery tap. */
|
|
3686
4052
|
export declare const recordSubscriptionDelivery: (d: SubscriptionDeliveryRecord) => void;
|
|
3687
4053
|
|
|
4054
|
+
/**
|
|
4055
|
+
* Record one table access against the procedure on the current fiber. Silently
|
|
4056
|
+
* ignored outside a procedure scope — background work (migrations, the
|
|
4057
|
+
* scheduler's own bookkeeping, plugin boot) is not a procedure and must not
|
|
4058
|
+
* invent edges for one.
|
|
4059
|
+
*/
|
|
4060
|
+
export declare const recordTableAccess: (table: string, op: ObservedOp) => void;
|
|
4061
|
+
|
|
3688
4062
|
/** Feed a committed change into the process recorder (gated + loop-safe). The
|
|
3689
4063
|
* serve paths call this from their `store.onChange` tap. */
|
|
3690
4064
|
export declare const recordTimelineEvent: (change: CdcChange & {
|
|
@@ -4055,6 +4429,9 @@ export declare interface ResendOptions {
|
|
|
4055
4429
|
/** Test/dev-only — clear ALL overrides. Don't call from app code. */
|
|
4056
4430
|
export declare const _resetConnectionSubjectsForTest: () => void;
|
|
4057
4431
|
|
|
4432
|
+
/** Drop everything recorded. For tests; not called by the runtime. */
|
|
4433
|
+
export declare const resetObservedGraph: () => void;
|
|
4434
|
+
|
|
4058
4435
|
/** Reset to the env default (tests). */
|
|
4059
4436
|
export declare const resetSecretsBackend: () => void;
|
|
4060
4437
|
|
|
@@ -4106,9 +4483,11 @@ export declare const resolveCurrentRegion: (env?: NodeJS.ProcessEnv) => string |
|
|
|
4106
4483
|
export declare interface ResolvedApiKey {
|
|
4107
4484
|
readonly id: string;
|
|
4108
4485
|
readonly tenantId: string;
|
|
4109
|
-
/**
|
|
4110
|
-
* outside a user session (bootstrap / admin tooling). */
|
|
4486
|
+
/** WHO MINTED the key. Provenance — present for org keys too. */
|
|
4111
4487
|
readonly createdBy: string | null;
|
|
4488
|
+
/** WHO THE KEY ACTS AS. `null` = an ORG key. This is the identity attribution
|
|
4489
|
+
* follows and the one the Subject carries as `metadata.userId`. */
|
|
4490
|
+
readonly onBehalfOf: string | null;
|
|
4112
4491
|
readonly scopes: ReadonlyArray<string>;
|
|
4113
4492
|
}
|
|
4114
4493
|
|
|
@@ -4585,6 +4964,16 @@ export declare const runWithDeadlockRetry: <T>(work: () => Promise<T>, options?:
|
|
|
4585
4964
|
readonly delay?: (attempt: number) => Promise<void>;
|
|
4586
4965
|
}) => Promise<T>;
|
|
4587
4966
|
|
|
4967
|
+
/**
|
|
4968
|
+
* Run `work` attributed to `procedure`. Every store operation performed inside
|
|
4969
|
+
* (or in its async children) is recorded against that tag.
|
|
4970
|
+
*
|
|
4971
|
+
* A no-op when recording is off, including the ALS entry itself — an
|
|
4972
|
+
* AsyncLocalStorage `run` on every rpc call is not free, and a disabled feature
|
|
4973
|
+
* should cost nothing.
|
|
4974
|
+
*/
|
|
4975
|
+
export declare const runWithObservedProcedure: <T>(procedure: ObservedProcedure, work: () => T) => T;
|
|
4976
|
+
|
|
4588
4977
|
/**
|
|
4589
4978
|
* Run `work` inside a routing scope. Every `query()` call against a
|
|
4590
4979
|
* `ReplicatedDataStore` from inside `work` (or its async children)
|
|
@@ -4846,6 +5235,14 @@ export declare interface SchedulerHandle {
|
|
|
4846
5235
|
export declare interface SchedulerLogger {
|
|
4847
5236
|
info: (msg: string, fields?: Record<string, unknown>) => void;
|
|
4848
5237
|
warn: (msg: string, fields?: Record<string, unknown>) => void;
|
|
5238
|
+
/**
|
|
5239
|
+
* Failures. This channel did not exist, which is why a schedule whose handler
|
|
5240
|
+
* failed on EVERY firing was only ever a `warn` — invisible to `voltro logs
|
|
5241
|
+
* --level error`, and a schedule fires unattended, so that log line is the
|
|
5242
|
+
* whole discovery channel. Optional so an embedder passing a two-method logger
|
|
5243
|
+
* still compiles; it falls back to `warn` at the call site.
|
|
5244
|
+
*/
|
|
5245
|
+
error?: (msg: string, fields?: Record<string, unknown>) => void;
|
|
4849
5246
|
/** Optional debug channel for high-frequency expected events
|
|
4850
5247
|
* (lost coordination claims on sub-minute schedules etc.). */
|
|
4851
5248
|
debug?: (msg: string, fields?: Record<string, unknown>) => void;
|
|
@@ -4914,6 +5311,13 @@ export declare interface SchemaInfo {
|
|
|
4914
5311
|
readonly idScheme?: IdScheme;
|
|
4915
5312
|
}
|
|
4916
5313
|
|
|
5314
|
+
/**
|
|
5315
|
+
* Every property NAME that appears anywhere in a schema's shape. Recursive over
|
|
5316
|
+
* the common composition nodes so a column nested under `{ refs: [{ … }] }` or
|
|
5317
|
+
* behind a `NullOr` is still seen. Cycle-guarded via a seen-set on Suspend.
|
|
5318
|
+
*/
|
|
5319
|
+
export declare const schemaPropertyNames: (schema: Schema.Schema.Any) => ReadonlySet<string>;
|
|
5320
|
+
|
|
4917
5321
|
export declare interface SchemaRegistry {
|
|
4918
5322
|
readonly tables: ReadonlyMap<string, SchemaInfo>;
|
|
4919
5323
|
/** True iff the table was composed with the named mixin id. */
|
|
@@ -5061,6 +5465,20 @@ export declare interface ServeRequestContext {
|
|
|
5061
5465
|
readonly rowFilter?: RowFilterScope;
|
|
5062
5466
|
}
|
|
5063
5467
|
|
|
5468
|
+
/** One leak: a wire query that declares a serverOnly column in its output. */
|
|
5469
|
+
export declare interface ServerOnlyLeak {
|
|
5470
|
+
readonly query: string;
|
|
5471
|
+
readonly table: string;
|
|
5472
|
+
readonly column: string;
|
|
5473
|
+
}
|
|
5474
|
+
|
|
5475
|
+
/**
|
|
5476
|
+
* Every serverOnly column a query's output declares. `serverOnlyByTable` maps a
|
|
5477
|
+
* table name to its `.serverOnly()` column names. A query with no `source`, or
|
|
5478
|
+
* whose source has no serverOnly columns, yields nothing.
|
|
5479
|
+
*/
|
|
5480
|
+
export declare const serverOnlyLeaks: (query: AuditableQuery, serverOnlyByTable: ReadonlyMap<string, ReadonlyArray<string>>) => ReadonlyArray<ServerOnlyLeak>;
|
|
5481
|
+
|
|
5064
5482
|
/** A plugin interceptor — wraps the base run Effect (Effect-native chain). */
|
|
5065
5483
|
export declare type ServeRpcInterceptor = (base: Effect.Effect<unknown, unknown, never>, meta: {
|
|
5066
5484
|
readonly tag: string;
|
|
@@ -5182,6 +5600,51 @@ export declare const snapshotMetrics: Effect.Effect<ReadonlyArray<MetricSample>>
|
|
|
5182
5600
|
/** Eager snapshot for sync callers (the inspect endpoint). */
|
|
5183
5601
|
export declare const snapshotMetricsSync: () => ReadonlyArray<MetricSample>;
|
|
5184
5602
|
|
|
5603
|
+
/** Thrown (as a typed defect) when a request targets a blocked address. */
|
|
5604
|
+
export declare class SsrfBlockedError extends Error {
|
|
5605
|
+
readonly url: string;
|
|
5606
|
+
readonly reason: string;
|
|
5607
|
+
readonly _tag = "SsrfBlockedError";
|
|
5608
|
+
constructor(url: string, reason: string);
|
|
5609
|
+
}
|
|
5610
|
+
|
|
5611
|
+
/**
|
|
5612
|
+
* Decorator layer: consumes a base `HttpClient` and re-provides one that blocks
|
|
5613
|
+
* SSRF targets on the initial request AND on every redirect hop.
|
|
5614
|
+
*
|
|
5615
|
+
* Compose it BELOW the tracing layer and above the base fetch client:
|
|
5616
|
+
*
|
|
5617
|
+
* ssrfGuardHttpClientLayer(policy).pipe(Layer.provide(FetchHttpClient.layer))
|
|
5618
|
+
*/
|
|
5619
|
+
export declare const ssrfGuardHttpClientLayer: (policy?: SsrfPolicy) => Layer.Layer<HttpClient.HttpClient, never, HttpClient.HttpClient>;
|
|
5620
|
+
|
|
5621
|
+
export declare interface SsrfPolicy {
|
|
5622
|
+
/**
|
|
5623
|
+
* Hosts that bypass the guard. An entry may be an exact hostname
|
|
5624
|
+
* (`billing.internal`), a suffix wildcard (`*.svc.cluster.local`), or a
|
|
5625
|
+
* host:port (`127.0.0.1:8787`) when only one port should be reachable.
|
|
5626
|
+
*
|
|
5627
|
+
* This is the one escape hatch, and it is deliberately per-HOST rather than a
|
|
5628
|
+
* boolean: "we call one internal service" and "we do not check URLs" are very
|
|
5629
|
+
* different postures, and a boolean cannot tell them apart six months later.
|
|
5630
|
+
*/
|
|
5631
|
+
readonly allowHosts?: ReadonlyArray<string>;
|
|
5632
|
+
/** Max redirect hops to follow. Each one is revalidated. Default 5. */
|
|
5633
|
+
readonly maxRedirects?: number;
|
|
5634
|
+
}
|
|
5635
|
+
|
|
5636
|
+
/**
|
|
5637
|
+
* Decide whether a URL may be fetched. Returns `null` when allowed, else the
|
|
5638
|
+
* reason. Fails CLOSED: a URL that will not parse is blocked.
|
|
5639
|
+
*
|
|
5640
|
+
* DNS is deliberately NOT resolved — this is a synchronous check on the literal
|
|
5641
|
+
* target, so a hostname that RESOLVES to a private address (DNS rebinding) is not
|
|
5642
|
+
* caught here. Blocking the direct-IP vector is the high-severity, cheap half;
|
|
5643
|
+
* rebinding needs network-layer egress control and is called out in the docs
|
|
5644
|
+
* rather than silently implied.
|
|
5645
|
+
*/
|
|
5646
|
+
export declare const ssrfReason: (rawUrl: string, policy?: SsrfPolicy) => string | null;
|
|
5647
|
+
|
|
5185
5648
|
/**
|
|
5186
5649
|
* Start the rpc-based runtime server on Node. Composes:
|
|
5187
5650
|
* - NodeHttpServer + NodeContext at the platform layer
|
|
@@ -5189,10 +5652,15 @@ export declare const snapshotMetricsSync: () => ReadonlyArray<MetricSample>;
|
|
|
5189
5652
|
* - RpcServer.layerHttpRouter (websocket protocol) consuming
|
|
5190
5653
|
* RpcSerialization.layerJson and the user's handlers layer
|
|
5191
5654
|
*
|
|
5192
|
-
*
|
|
5193
|
-
*
|
|
5194
|
-
|
|
5195
|
-
|
|
5655
|
+
* Forks `Layer.launch` so the Node event loop stays alive for the lifetime of the
|
|
5656
|
+
* layer's scope, and returns that scope's `shutdown` alongside the node server —
|
|
5657
|
+
* so a programmatic close releases the layer, not just the socket. SIGINT/SIGTERM
|
|
5658
|
+
* are wired once per process (not once per call) and drain every live launch.
|
|
5659
|
+
*/
|
|
5660
|
+
export declare const startRpcServer: <Rpcs extends Rpc.Any>(options: RpcServerOptions<Rpcs>) => {
|
|
5661
|
+
server: ReturnType<typeof createServer>;
|
|
5662
|
+
shutdown: () => Promise<void>;
|
|
5663
|
+
};
|
|
5196
5664
|
|
|
5197
5665
|
export declare const startScheduler: (schedules: ReadonlyArray<BrandedScheduleDefinition>, deps: SchedulerDeps) => Promise<SchedulerHandle>;
|
|
5198
5666
|
|
|
@@ -6193,6 +6661,32 @@ export declare interface WorkflowLayerOptions<Context> {
|
|
|
6193
6661
|
readonly resolveStartContext?: (workflowName: string, executionId: string) => WorkflowCallerContext | undefined | Promise<WorkflowCallerContext | undefined>;
|
|
6194
6662
|
}
|
|
6195
6663
|
|
|
6664
|
+
/**
|
|
6665
|
+
* A start whose PAYLOAD does not match the workflow's schema.
|
|
6666
|
+
*
|
|
6667
|
+
* Distinct from a workflow that ran and failed, and the distinction is the whole
|
|
6668
|
+
* point: `ctx.workflows.start(name, payload)` is typed `(string, unknown)` — the
|
|
6669
|
+
* name is not checked against the registry and the payload is not checked against
|
|
6670
|
+
* anything — so a caller that drifts from the workflow's schema produces a failure
|
|
6671
|
+
* DEEP inside the engine, where it reads like the workflow itself misbehaved.
|
|
6672
|
+
*
|
|
6673
|
+
* A cron firing such a start hit that every single time and looked like a flaky
|
|
6674
|
+
* job. Naming the workflow, the missing fields, and the fact that the payload
|
|
6675
|
+
* (not the workflow) is what's wrong turns it into a one-read fix.
|
|
6676
|
+
*/
|
|
6677
|
+
export declare class WorkflowPayloadError extends Error {
|
|
6678
|
+
readonly workflowName: string;
|
|
6679
|
+
/** Top-level property names the schema requires and the payload omitted.
|
|
6680
|
+
* Empty when the mismatch is a type error rather than a missing key. */
|
|
6681
|
+
readonly missingFields: ReadonlyArray<string>;
|
|
6682
|
+
readonly detail: string;
|
|
6683
|
+
readonly _tag = "WorkflowPayloadError";
|
|
6684
|
+
constructor(workflowName: string,
|
|
6685
|
+
/** Top-level property names the schema requires and the payload omitted.
|
|
6686
|
+
* Empty when the mismatch is a type error rather than a missing key. */
|
|
6687
|
+
missingFields: ReadonlyArray<string>, detail: string);
|
|
6688
|
+
}
|
|
6689
|
+
|
|
6196
6690
|
/** Options for {@link WorkflowsAppContext.retry}. */
|
|
6197
6691
|
export declare interface WorkflowRetryOptions {
|
|
6198
6692
|
/** Re-run the workflow against this payload instead of the original
|
|
@@ -6330,6 +6824,6 @@ export declare interface WorkflowWaitOptions {
|
|
|
6330
6824
|
*/
|
|
6331
6825
|
export declare const wrapCaptureStore: (tx: DataStore, collect: (c: ForwardChange) => void) => DataStore;
|
|
6332
6826
|
|
|
6333
|
-
export declare const wrapStoreWithMixinBehaviour: (
|
|
6827
|
+
export declare const wrapStoreWithMixinBehaviour: (rawUnderlying: DataStore, ctx: StoreMiddlewareContext) => FluentStore;
|
|
6334
6828
|
|
|
6335
6829
|
export { }
|