okengine 0.15.2 → 0.17.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/manifest.v1.schema.json +21 -2
- package/package.json +1 -1
- package/site/content/docs/elements/clock.mdx +7 -0
- package/site/content/docs/elements/flow.mdx +20 -12
- package/site/content/docs/elements/gate.mdx +108 -3
- package/site/content/docs/elements/signal.mdx +46 -17
- package/site/content/docs/elements/store.mdx +36 -32
- package/site/content/docs/elements/vault.mdx +26 -0
- package/site/content/docs/get-started/project-structure.mdx +4 -4
- package/site/content/docs/reference/cli.md +1 -1
- package/site/content/docs/reference/client.mdx +75 -17
- package/site/content/docs/reference/configuration.mdx +7 -4
- package/site/content/docs/reference/errors.mdx +24 -17
- package/site/content/docs/reference/fx.mdx +39 -22
- package/site/content/docs/reference/security.md +1 -1
- package/src/auth/api-key-sql.test.ts +47 -0
- package/src/auth/api-key-sql.ts +261 -0
- package/src/auth/api-keys.ts +276 -14
- package/src/auth/auth.test.ts +56 -0
- package/src/auth/config.ts +24 -5
- package/src/auth/index.ts +62 -1
- package/src/auth/plugin.ts +6 -1
- package/src/auth/schema.ts +8 -0
- package/src/auth/sessions.ts +18 -0
- package/src/auth/tables.ts +32 -0
- package/src/auth/tenant-config.ts +74 -0
- package/src/auth/tenant-tables.ts +11 -0
- package/src/auth/tenants.test.ts +63 -0
- package/src/auth/tenants.ts +360 -0
- package/src/cli/attach-host-console.test.ts +37 -0
- package/src/cli/attach-host-console.ts +6 -0
- package/src/cli/build.ts +2 -2
- package/src/client/budget.test.ts +1 -1
- package/src/client/create.ts +75 -5
- package/src/client/index.ts +13 -0
- package/src/client/live.test.ts +422 -0
- package/src/client/live.ts +389 -0
- package/src/client/notes-contract.test.ts +41 -0
- package/src/client/types.ts +85 -6
- package/src/client-react/index.ts +85 -1
- package/src/client-react/use-live.test.ts +129 -0
- package/src/compiler/effects-infer.ts +37 -2
- package/src/compiler/extract.test.ts +143 -11
- package/src/compiler/extract.ts +210 -30
- package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +1 -2
- package/src/compiler/fixtures/skyport.expected.json +2 -3
- package/src/compiler/response.ts +41 -11
- package/src/console/server/access.test.ts +116 -8
- package/src/console/server/access.ts +70 -23
- package/src/console/server/app.ts +45 -11
- package/src/console/server/console.test.ts +6 -8
- package/src/console/server/dev-identities.test.ts +127 -0
- package/src/console/server/dev-identities.ts +229 -0
- package/src/console/server/flows-invoke.test.ts +21 -4
- package/src/console/server/flows.ts +113 -58
- package/src/console/server/gates.test.ts +24 -0
- package/src/console/server/gates.ts +10 -30
- package/src/console/server/index.ts +1 -0
- package/src/console/server/live.test.ts +26 -1
- package/src/console/server/live.ts +4 -2
- package/src/console/server/operator-db.ts +2 -0
- package/src/console/server/state.ts +29 -55
- package/src/console/server/store.test.ts +17 -0
- package/src/console/server/store.ts +43 -1
- package/src/console/ui-next/dist/assets/access-page-CXVWWMmD.js +4 -0
- package/src/console/ui-next/dist/assets/{agent-disclosure-C0X1fbWF.js → agent-disclosure-BP0Y0Sux.js} +1 -1
- package/src/console/ui-next/dist/assets/cache-glyph-B5X-NM0-.js +1 -0
- package/src/console/ui-next/dist/assets/call-pii-button-ChAUiLo9.js +1 -0
- package/src/console/ui-next/dist/assets/collapsible-DGnOM2ph.js +1 -0
- package/src/console/ui-next/dist/assets/copy-inline-button-DBHhgHKP.js +1 -0
- package/src/console/ui-next/dist/assets/detail-header-DhHM1iaZ.js +1 -0
- package/src/console/ui-next/dist/assets/dropdown-menu-_NjTEo5_.js +1 -0
- package/src/console/ui-next/dist/assets/element-icons-BVXtRyd3.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-empty-2uIhBu0_.js +1 -0
- package/src/console/ui-next/dist/assets/flows-page-XDpAJO8f.js +1 -0
- package/src/console/ui-next/dist/assets/{highlighted-json-CS_O8L-r.js → highlighted-json-Awq7gYdu.js} +1 -1
- package/src/console/ui-next/dist/assets/http-method-_UHM2ODJ.js +1 -0
- package/src/console/ui-next/dist/assets/index-BBj2QJCu.js +66 -0
- package/src/console/ui-next/dist/assets/index-Ck88Jmv8.css +2 -0
- package/src/console/ui-next/dist/assets/link-BX6Vqztd.js +1 -0
- package/src/console/ui-next/dist/assets/observability-page-BFaay44m.js +4 -0
- package/src/console/ui-next/dist/assets/replica-lag-DKRrbvdo.js +18 -0
- package/src/console/ui-next/dist/assets/request-meta-DV0ywz7t.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-JQIZlWfm.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-DKxNTe_m.js +1 -0
- package/src/console/ui-next/dist/assets/skeleton-D-czQJT6.js +1 -0
- package/src/console/ui-next/dist/assets/store-page-CS5-aETQ.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-B09O8rA6.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-BtyhmWb4.js +55 -0
- package/src/console/ui-next/dist/assets/units-page-Ca2Z-E52.js +1 -0
- package/src/console/ui-next/dist/assets/{use-vault-list-BLbZhzyF.js → use-vault-list-uk4WVboC.js} +1 -1
- package/src/console/ui-next/dist/assets/useMutation-B8EO02Ej.js +1 -0
- package/src/console/ui-next/dist/assets/useRender-BE2A9BWC.js +1 -0
- package/src/console/ui-next/dist/assets/vault-page-BmOeAFwg.js +2 -0
- package/src/console/ui-next/dist/index.html +11 -9
- package/src/console/ui-next/index.html +3 -3
- package/src/console/ui-next/src/client.ts +167 -0
- package/src/console/ui-next/src/components/shell/app-sidebar.tsx +2 -0
- package/src/console/ui-next/src/components/shell/command-items.test.ts +1 -0
- package/src/console/ui-next/src/components/shell/command-items.ts +8 -0
- package/src/console/ui-next/src/components/shell/shell-layout.tsx +2 -1
- package/src/console/ui-next/src/components/ui/sheet-form.tsx +8 -3
- package/src/console/ui-next/src/features/access/access-page.tsx +347 -0
- package/src/console/ui-next/src/features/access/data/use-access.ts +23 -0
- package/src/console/ui-next/src/features/access/detail/access-detail.tsx +552 -0
- package/src/console/ui-next/src/features/access/explorer/access-list.tsx +125 -0
- package/src/console/ui-next/src/features/access/lib/format-when.test.ts +139 -0
- package/src/console/ui-next/src/features/access/lib/format-when.ts +296 -0
- package/src/console/ui-next/src/features/access/lib/scope-groups.test.ts +34 -0
- package/src/console/ui-next/src/features/access/lib/scope-groups.ts +40 -0
- package/src/console/ui-next/src/features/access/sheets/access-allow-fields.tsx +236 -0
- package/src/console/ui-next/src/features/access/sheets/access-create-sheet.tsx +316 -0
- package/src/console/ui-next/src/features/access/sheets/access-edit-sheet.tsx +240 -0
- package/src/console/ui-next/src/features/access/sheets/access-expiry-fields.tsx +66 -0
- package/src/console/ui-next/src/features/access/sheets/access-refresh-sheet.tsx +104 -0
- package/src/console/ui-next/src/features/access/sheets/access-scope-field.tsx +115 -0
- package/src/console/ui-next/src/features/access/sheets/access-secret-sheet.tsx +89 -0
- package/src/console/ui-next/src/features/access/sheets/access-sheet-search.tsx +49 -0
- package/src/console/ui-next/src/features/access/state/access-selection.ts +84 -0
- package/src/console/ui-next/src/features/auth/auth-redirect.test.ts +5 -0
- package/src/console/ui-next/src/features/auth/auth-redirect.ts +18 -2
- package/src/console/ui-next/src/features/flows/fixture.ts +0 -1
- package/src/console/ui-next/src/features/not-found/not-found-page.tsx +1 -1
- package/src/console/ui-next/src/features/units/detail/flow-contract-panel.tsx +5 -1
- package/src/console/ui-next/src/features/units/lib/unit-tree.test.ts +15 -4
- package/src/console/ui-next/src/lib/document-meta.test.ts +1 -0
- package/src/console/ui-next/src/lib/document-meta.ts +2 -1
- package/src/console/ui-next/src/lib/last-module-search.test.ts +1 -0
- package/src/console/ui-next/src/lib/last-module-search.ts +2 -1
- package/src/console/ui-next/src/lib/shortcut.test.ts +2 -0
- package/src/console/ui-next/src/lib/shortcut.ts +3 -0
- package/src/console/ui-next/src/router.tsx +10 -1
- package/src/console/ui-next/ui-next-seed-app-schema.ts +88 -37
- package/src/console/ui-next/ui-next-seed-manifest-surface.ts +2 -9
- package/src/console/ui-next/ui-next-seed-manifest.ts +0 -1
- package/src/console/ui-next/ui-next-seed-store.ts +43 -15
- package/src/console/xss.gate.test.ts +1 -0
- package/src/drivers/index.ts +2 -0
- package/src/drivers/journal-postgres.ts +12 -3
- package/src/drivers/pg-rls.ts +26 -2
- package/src/drivers/pg-vault-rls.ts +68 -0
- package/src/drivers/signal-engine.ts +69 -15
- package/src/drivers/signal-live-iter.ts +65 -0
- package/src/drivers/signal-nats.ts +2 -1
- package/src/drivers/signal-postgres.ts +95 -12
- package/src/drivers/signal-redis.ts +2 -1
- package/src/drivers/signal-retention.ts +64 -0
- package/src/drivers/signal-types.ts +35 -5
- package/src/elements/clock/declare.ts +64 -2
- package/src/elements/clock/reconcile.ts +98 -25
- package/src/elements/clock/runtime.ts +13 -2
- package/src/elements/clock.test.ts +28 -0
- package/src/elements/clock.ts +9 -1
- package/src/elements/gate/declare.ts +2 -0
- package/src/elements/gate/permissions.ts +12 -0
- package/src/elements/gate.ts +6 -1
- package/src/elements/signal/declare.ts +44 -10
- package/src/elements/signal/delivery-modes.test.ts +90 -4
- package/src/elements/signal/order-lifecycle.test.ts +20 -4
- package/src/elements/signal/runtime.ts +42 -0
- package/src/elements/signal.test.ts +21 -8
- package/src/elements/signal.ts +1 -1
- package/src/elements/store/cache.ts +1 -1
- package/src/elements/store/declare.ts +7 -0
- package/src/elements/store/rls-identity.test.ts +29 -0
- package/src/elements/store/rls-identity.ts +3 -0
- package/src/elements/store/runtime.ts +14 -0
- package/src/elements/store/schema-decl.ts +162 -44
- package/src/elements/store/schema-tenant.ts +41 -0
- package/src/elements/store/sql-rls-isolation.test.ts +39 -0
- package/src/elements/store/sql-select.types.test.ts +71 -0
- package/src/elements/store/sql-session.ts +54 -32
- package/src/elements/store.ts +5 -0
- package/src/elements/vault/builtin-adapter.ts +15 -2
- package/src/elements/vault/declare.ts +8 -0
- package/src/elements/vault/runtime.ts +7 -0
- package/src/elements/vault/sql-rls-isolation.test.ts +145 -0
- package/src/elements/vault/storage.ts +9 -0
- package/src/elements/vault/test-helpers.ts +2 -1
- package/src/i18n/catalogs/ar.ts +19 -0
- package/src/i18n/catalogs/en.ts +19 -0
- package/src/index.ts +2 -0
- package/src/kernel/adopt-routes.ts +56 -8
- package/src/kernel/api-key-host-persist.test.ts +143 -0
- package/src/kernel/app-tenant.ts +122 -0
- package/src/kernel/app.ts +196 -57
- package/src/kernel/auth-resolve.ts +13 -5
- package/src/kernel/boot-bind/clock.ts +9 -3
- package/src/kernel/boot.test.ts +4 -4
- package/src/kernel/boot.ts +2 -0
- package/src/kernel/budget.test.ts +1 -1
- package/src/kernel/clock-durable.ts +8 -0
- package/src/kernel/clock-per-tenant-name.ts +5 -0
- package/src/kernel/clock-reconcile.ts +8 -0
- package/src/kernel/errors-live-resume.ts +15 -0
- package/src/kernel/errors-tenant.ts +29 -0
- package/src/kernel/errors.registry.test.ts +31 -3
- package/src/kernel/errors.ts +43 -3
- package/src/kernel/flow.ts +26 -5
- package/src/kernel/fx-auth-keys.test.ts +88 -0
- package/src/kernel/fx-auth-keys.ts +203 -0
- package/src/kernel/fx-auth-tenants.test.ts +87 -0
- package/src/kernel/fx-auth-tenants.ts +286 -0
- package/src/kernel/fx-live-stream.ts +149 -0
- package/src/kernel/fx-live.test.ts +157 -0
- package/src/kernel/fx-runtime.ts +15 -0
- package/src/kernel/fx-tenant-store.ts +213 -0
- package/src/kernel/fx.test.ts +108 -1
- package/src/kernel/fx.ts +206 -28
- package/src/kernel/hooks.ts +2 -2
- package/src/kernel/http-resource.ts +9 -18
- package/src/kernel/index.ts +2 -0
- package/src/kernel/journal.ts +12 -0
- package/src/kernel/live-http.test.ts +78 -0
- package/src/kernel/live-http.ts +114 -0
- package/src/kernel/live-resume.test.ts +125 -0
- package/src/kernel/on.ts +51 -0
- package/src/kernel/pipeline-api-key.test.ts +192 -7
- package/src/kernel/pipeline-tenant.ts +49 -0
- package/src/kernel/pipeline.test.ts +1 -1
- package/src/kernel/pipeline.ts +41 -4
- package/src/kernel/resource-mount.test.ts +10 -27
- package/src/kernel/tenant-resolve.test.ts +101 -0
- package/src/kernel/tenant-resolve.ts +124 -0
- package/src/kernel/tenant-roles.test.ts +87 -0
- package/src/kernel/triggers.ts +59 -21
- package/src/manifest/diff.test.ts +11 -2
- package/src/manifest/diff.ts +53 -11
- package/src/manifest/fixtures/skyport.excerpt.json +1 -1
- package/src/manifest/fixtures/skyport.manifest.json +0 -1
- package/src/manifest/types.ts +58 -6
- package/src/release/build-lib.ts +14 -1
- package/src/release/limits.ts +2 -2
- package/src/release/measure.ts +55 -1
- package/src/client/live-gap.test.ts +0 -35
- package/src/console/ui-next/dist/assets/cache-glyph-F1FI122b.js +0 -1
- package/src/console/ui-next/dist/assets/call-pii-button-DEDCl_j3.js +0 -1
- package/src/console/ui-next/dist/assets/collapsible-LPqGvfoz.js +0 -1
- package/src/console/ui-next/dist/assets/confirm-sheet-3ptDzXbz.js +0 -1
- package/src/console/ui-next/dist/assets/explorer-empty-BvYrygyO.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-Dluo1Bd7.js +0 -1
- package/src/console/ui-next/dist/assets/http-method--sWDdXSB.js +0 -1
- package/src/console/ui-next/dist/assets/index-Ca3HZMVq.js +0 -66
- package/src/console/ui-next/dist/assets/index-UtTDRQpS.css +0 -2
- package/src/console/ui-next/dist/assets/link-COeyggt-.js +0 -1
- package/src/console/ui-next/dist/assets/observability-page-HK9-BO8z.js +0 -4
- package/src/console/ui-next/dist/assets/replica-lag-DBIFFf7d.js +0 -18
- package/src/console/ui-next/dist/assets/sheet-form-Dnwa5oOR.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-Cgzuv4k1.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-CoqwEIi0.js +0 -1
- package/src/console/ui-next/dist/assets/skeleton-BHcfDYcb.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-BSohH3wM.js +0 -41
- package/src/console/ui-next/dist/assets/trace-detail-sheet-CFIGRnnA.js +0 -2
- package/src/console/ui-next/dist/assets/tree-expand-toggle-BgLl34w7.js +0 -54
- package/src/console/ui-next/dist/assets/units-page-B0cFE76A.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-B1SbYe10.js +0 -2
|
@@ -34,6 +34,15 @@ import {
|
|
|
34
34
|
|
|
35
35
|
export type { WhereMap } from "./sql-condition.ts";
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Inferred select-row shape for a table handle.
|
|
39
|
+
*
|
|
40
|
+
* Uses `$inferSelect` when present (OKE `store.schema.table()` and Drizzle
|
|
41
|
+
* `pgTable()` / `sqliteTable()`). Bare {@link TableHandle} / `unknown` stay
|
|
42
|
+
* {@link SqlRow}.
|
|
43
|
+
*/
|
|
44
|
+
export type InferSelectRow<T> = T extends { readonly $inferSelect: infer R } ? R : SqlRow;
|
|
45
|
+
|
|
37
46
|
/** Serialize stamp frames on a shared connection so concurrent identities cannot interleave. */
|
|
38
47
|
const rlsStampTails = new WeakMap<SqlConnection, Promise<unknown>>();
|
|
39
48
|
|
|
@@ -97,44 +106,44 @@ export interface SqlSessionOptions {
|
|
|
97
106
|
* Continuations after `.orderBy(...)` — awaitable, optional `.limit` /
|
|
98
107
|
* `.offset`.
|
|
99
108
|
*/
|
|
100
|
-
export interface SelectOrderBuilder extends PromiseLike<
|
|
109
|
+
export interface SelectOrderBuilder<TRow = SqlRow> extends PromiseLike<TRow[]> {
|
|
101
110
|
/**
|
|
102
111
|
* Cap the number of returned rows.
|
|
103
112
|
*
|
|
104
113
|
* @param n - Max rows
|
|
105
114
|
*/
|
|
106
|
-
limit(n: number): Promise<
|
|
115
|
+
limit(n: number): Promise<TRow[]>;
|
|
107
116
|
/**
|
|
108
117
|
* Skip the first `n` rows (`LIMIT … OFFSET …`).
|
|
109
118
|
*
|
|
110
119
|
* @param n - Rows to skip
|
|
111
120
|
*/
|
|
112
|
-
offset(n: number): Promise<
|
|
121
|
+
offset(n: number): Promise<TRow[]>;
|
|
113
122
|
}
|
|
114
123
|
|
|
115
124
|
/**
|
|
116
125
|
* Continuations after `.where(...)` on a select — awaitable, optional
|
|
117
126
|
* `.orderBy(...)` / `.limit(...)` / `.offset(...)`.
|
|
118
127
|
*/
|
|
119
|
-
export interface SelectWhereBuilder extends PromiseLike<
|
|
128
|
+
export interface SelectWhereBuilder<TRow = SqlRow> extends PromiseLike<TRow[]> {
|
|
120
129
|
/**
|
|
121
130
|
* Order rows with Drizzle `asc()` / `desc()` terms.
|
|
122
131
|
*
|
|
123
132
|
* @param orders - Order terms
|
|
124
133
|
*/
|
|
125
|
-
orderBy(...orders: readonly unknown[]): SelectOrderBuilder
|
|
134
|
+
orderBy(...orders: readonly unknown[]): SelectOrderBuilder<TRow>;
|
|
126
135
|
/**
|
|
127
136
|
* Cap the number of returned rows.
|
|
128
137
|
*
|
|
129
138
|
* @param n - Max rows
|
|
130
139
|
*/
|
|
131
|
-
limit(n: number): Promise<
|
|
140
|
+
limit(n: number): Promise<TRow[]>;
|
|
132
141
|
/**
|
|
133
142
|
* Skip the first `n` rows.
|
|
134
143
|
*
|
|
135
144
|
* @param n - Rows to skip
|
|
136
145
|
*/
|
|
137
|
-
offset(n: number): Promise<
|
|
146
|
+
offset(n: number): Promise<TRow[]>;
|
|
138
147
|
}
|
|
139
148
|
|
|
140
149
|
/**
|
|
@@ -142,42 +151,49 @@ export interface SelectWhereBuilder extends PromiseLike<SqlRow[]> {
|
|
|
142
151
|
* `.where(...)` / `.orderBy(...)` / `.limit(...)` / `.offset(...)` in any
|
|
143
152
|
* order.
|
|
144
153
|
*/
|
|
145
|
-
export interface SelectFromBuilder extends PromiseLike<
|
|
154
|
+
export interface SelectFromBuilder<TRow = SqlRow> extends PromiseLike<TRow[]> {
|
|
146
155
|
/**
|
|
147
156
|
* Filter with a plain equality map or a Drizzle SQL condition
|
|
148
157
|
* (`eq`, `and`, `or`, `lt`, `like`, …).
|
|
149
158
|
*
|
|
150
159
|
* @param where - Condition
|
|
151
160
|
*/
|
|
152
|
-
where(where: unknown): SelectWhereBuilder
|
|
161
|
+
where(where: unknown): SelectWhereBuilder<TRow>;
|
|
153
162
|
/**
|
|
154
163
|
* Order rows with Drizzle `asc()` / `desc()` terms.
|
|
155
164
|
*
|
|
156
165
|
* @param orders - Order terms
|
|
157
166
|
*/
|
|
158
|
-
orderBy(...orders: readonly unknown[]): SelectOrderBuilder
|
|
167
|
+
orderBy(...orders: readonly unknown[]): SelectOrderBuilder<TRow>;
|
|
159
168
|
/**
|
|
160
169
|
* Cap the number of returned rows.
|
|
161
170
|
*
|
|
162
171
|
* @param n - Max rows
|
|
163
172
|
*/
|
|
164
|
-
limit(n: number): Promise<
|
|
173
|
+
limit(n: number): Promise<TRow[]>;
|
|
165
174
|
/**
|
|
166
175
|
* Skip the first `n` rows.
|
|
167
176
|
*
|
|
168
177
|
* @param n - Rows to skip
|
|
169
178
|
*/
|
|
170
|
-
offset(n: number): Promise<
|
|
179
|
+
offset(n: number): Promise<TRow[]>;
|
|
171
180
|
}
|
|
172
181
|
|
|
173
|
-
/**
|
|
174
|
-
|
|
182
|
+
/**
|
|
183
|
+
* Fluent select builder.
|
|
184
|
+
*
|
|
185
|
+
* `TLocked` is `undefined` for `select()` (row type inferred from `.from`)
|
|
186
|
+
* and {@link SqlRow} for `select({ alias: col })` projections.
|
|
187
|
+
*/
|
|
188
|
+
export interface SelectBuilder<TLocked extends SqlRow | undefined = undefined> {
|
|
175
189
|
/**
|
|
176
190
|
* Choose the source table.
|
|
177
191
|
*
|
|
178
192
|
* @param table - Table handle or Drizzle table
|
|
179
193
|
*/
|
|
180
|
-
from(
|
|
194
|
+
from<TTable>(
|
|
195
|
+
table: TTable,
|
|
196
|
+
): SelectFromBuilder<TLocked extends undefined ? InferSelectRow<TTable> : TLocked>;
|
|
181
197
|
}
|
|
182
198
|
|
|
183
199
|
/** Fluent insert builder. */
|
|
@@ -244,12 +260,13 @@ export interface SqlStoreHandle {
|
|
|
244
260
|
/** Underlying driver id. */
|
|
245
261
|
readonly driverId: SqlConnection["driverId"];
|
|
246
262
|
/**
|
|
247
|
-
* Start a select.
|
|
248
|
-
* (`select({ clicks: links.clicks })`).
|
|
263
|
+
* Start a select. No-arg form infers the row from `.from(table)`.
|
|
264
|
+
* A column map (`select({ clicks: links.clicks })`) stays {@link SqlRow}.
|
|
249
265
|
*
|
|
250
266
|
* @param columns - Optional `{ alias: drizzleColumn }` map
|
|
251
267
|
*/
|
|
252
|
-
select(
|
|
268
|
+
select(): SelectBuilder<undefined>;
|
|
269
|
+
select(columns: unknown): SelectBuilder<SqlRow>;
|
|
253
270
|
/**
|
|
254
271
|
* Start an insert into `table`.
|
|
255
272
|
*
|
|
@@ -600,20 +617,20 @@ export function createSqlStoreHandle(
|
|
|
600
617
|
};
|
|
601
618
|
}
|
|
602
619
|
|
|
603
|
-
const handle
|
|
620
|
+
const handle = {
|
|
604
621
|
ref,
|
|
605
622
|
routedRole: options.routedRole,
|
|
606
623
|
driverId: connection.driverId,
|
|
607
624
|
|
|
608
|
-
select(columns
|
|
625
|
+
select(columns?: unknown) {
|
|
609
626
|
return {
|
|
610
|
-
from(table) {
|
|
627
|
+
from(table: TableHandle | unknown) {
|
|
611
628
|
return selectFrom(table, columns);
|
|
612
629
|
},
|
|
613
630
|
};
|
|
614
631
|
},
|
|
615
632
|
|
|
616
|
-
insert(table): InsertBuilder {
|
|
633
|
+
insert(table: TableHandle | unknown): InsertBuilder {
|
|
617
634
|
const name = resolveTableName(table);
|
|
618
635
|
return {
|
|
619
636
|
values(row) {
|
|
@@ -648,7 +665,7 @@ export function createSqlStoreHandle(
|
|
|
648
665
|
};
|
|
649
666
|
},
|
|
650
667
|
|
|
651
|
-
update(table): UpdateBuilder {
|
|
668
|
+
update(table: TableHandle | unknown): UpdateBuilder {
|
|
652
669
|
const name = resolveTableName(table);
|
|
653
670
|
return {
|
|
654
671
|
set(row) {
|
|
@@ -675,7 +692,7 @@ export function createSqlStoreHandle(
|
|
|
675
692
|
};
|
|
676
693
|
},
|
|
677
694
|
|
|
678
|
-
async findById(table, idValue) {
|
|
695
|
+
async findById(table: TableHandle | unknown, idValue: string) {
|
|
679
696
|
await ensureFromMeta(table);
|
|
680
697
|
const name = resolveTableName(table);
|
|
681
698
|
const pk = resolvePkColumn(table);
|
|
@@ -716,7 +733,7 @@ export function createSqlStoreHandle(
|
|
|
716
733
|
return builder;
|
|
717
734
|
}) as SqlStoreHandle["delete"],
|
|
718
735
|
|
|
719
|
-
async exists(table, idOrWhere) {
|
|
736
|
+
async exists(table: TableHandle | unknown, idOrWhere: string | WhereMap) {
|
|
720
737
|
await ensureFromMeta(table);
|
|
721
738
|
const name = resolveTableName(table);
|
|
722
739
|
const where: WhereMap =
|
|
@@ -734,7 +751,12 @@ export function createSqlStoreHandle(
|
|
|
734
751
|
return rows.length > 0;
|
|
735
752
|
},
|
|
736
753
|
|
|
737
|
-
async upsert(
|
|
754
|
+
async upsert(
|
|
755
|
+
table: TableHandle | unknown,
|
|
756
|
+
matchOn: WhereMap | unknown,
|
|
757
|
+
values: SqlRow,
|
|
758
|
+
upsertOptions?: { readonly onExisting?: "update" },
|
|
759
|
+
) {
|
|
738
760
|
await ensureFromMeta(table);
|
|
739
761
|
const name = resolveTableName(table);
|
|
740
762
|
const compiled = compileTableWhere(table, matchOn);
|
|
@@ -766,7 +788,7 @@ export function createSqlStoreHandle(
|
|
|
766
788
|
return { status: "changed" as const };
|
|
767
789
|
},
|
|
768
790
|
|
|
769
|
-
async increment(table, idValue, column, by = 1) {
|
|
791
|
+
async increment(table: TableHandle | unknown, idValue: string, column: string, by = 1) {
|
|
770
792
|
await ensureFromMeta(table);
|
|
771
793
|
const name = resolveTableName(table);
|
|
772
794
|
const pk = resolvePkColumn(table);
|
|
@@ -785,7 +807,7 @@ export function createSqlStoreHandle(
|
|
|
785
807
|
return asNumber(row[sqlCol], sqlCol);
|
|
786
808
|
},
|
|
787
809
|
|
|
788
|
-
async raw(sql, params = []) {
|
|
810
|
+
async raw(sql: string, params: readonly unknown[] = []) {
|
|
789
811
|
const table = tableFromSql(sql);
|
|
790
812
|
// Memory SQL (console-next seed) implements DML on `exec`, not `query`.
|
|
791
813
|
// SELECT / RETURNING still go through `query` so callers get rows.
|
|
@@ -797,7 +819,7 @@ export function createSqlStoreHandle(
|
|
|
797
819
|
return mask(rows, table);
|
|
798
820
|
},
|
|
799
821
|
|
|
800
|
-
async count(table, where) {
|
|
822
|
+
async count(table: TableHandle | unknown, where?: unknown) {
|
|
801
823
|
await ensureFromMeta(table);
|
|
802
824
|
const name = resolveTableName(table);
|
|
803
825
|
let sql = `SELECT COUNT(*) AS "count" FROM ${quoteIdent(name)}`;
|
|
@@ -813,7 +835,7 @@ export function createSqlStoreHandle(
|
|
|
813
835
|
return asNumber(rows[0]?.count ?? rows[0]?.COUNT ?? 0, "count");
|
|
814
836
|
},
|
|
815
837
|
|
|
816
|
-
async page(table, options) {
|
|
838
|
+
async page(table: TableHandle | unknown, options: SqlPageOptions) {
|
|
817
839
|
if (options.after !== undefined && options.before !== undefined) {
|
|
818
840
|
throw new Error("page(): after and before (keyset) cannot combine");
|
|
819
841
|
}
|
|
@@ -838,7 +860,7 @@ export function createSqlStoreHandle(
|
|
|
838
860
|
});
|
|
839
861
|
},
|
|
840
862
|
|
|
841
|
-
async ensureTable(table) {
|
|
863
|
+
async ensureTable(table: TableHandle) {
|
|
842
864
|
const cols = Object.values(table.columns);
|
|
843
865
|
const pk = resolvePkColumn(table);
|
|
844
866
|
const intType =
|
|
@@ -863,7 +885,7 @@ export function createSqlStoreHandle(
|
|
|
863
885
|
},
|
|
864
886
|
};
|
|
865
887
|
|
|
866
|
-
return handle;
|
|
888
|
+
return handle as SqlStoreHandle;
|
|
867
889
|
}
|
|
868
890
|
|
|
869
891
|
/**
|
package/src/elements/store.ts
CHANGED
|
@@ -31,6 +31,8 @@ export {
|
|
|
31
31
|
schemaPolicyGate,
|
|
32
32
|
schemaPolicyOwner,
|
|
33
33
|
schemaPolicyScope,
|
|
34
|
+
schemaPolicyTenant,
|
|
35
|
+
schemaUnscoped,
|
|
34
36
|
isSchemaTableDecl,
|
|
35
37
|
isSchemaColumnDecl,
|
|
36
38
|
isSchemaRelationsDecl,
|
|
@@ -47,6 +49,8 @@ export type {
|
|
|
47
49
|
SchemaColumnInput,
|
|
48
50
|
SchemaTableDecl,
|
|
49
51
|
SchemaTableWithColumns,
|
|
52
|
+
InferColumnJs,
|
|
53
|
+
FinalizeColumn,
|
|
50
54
|
SchemaRelationsDecl,
|
|
51
55
|
SchemaPolicyDecl,
|
|
52
56
|
SchemaTableExtra,
|
|
@@ -121,6 +125,7 @@ export type {
|
|
|
121
125
|
SelectFromBuilder,
|
|
122
126
|
SelectWhereBuilder,
|
|
123
127
|
SelectOrderBuilder,
|
|
128
|
+
InferSelectRow,
|
|
124
129
|
InsertBuilder,
|
|
125
130
|
InsertValuesBuilder,
|
|
126
131
|
SqlSessionOptions,
|
|
@@ -204,6 +204,18 @@ export interface BuiltinVaultAdapter extends VaultAdapter {
|
|
|
204
204
|
/** Numeric column as any supported driver may hand it back. */
|
|
205
205
|
type SqlNumber = number | string | bigint;
|
|
206
206
|
|
|
207
|
+
/**
|
|
208
|
+
* First path segment when the contract is stored as `{tenantId}/{name}`.
|
|
209
|
+
*
|
|
210
|
+
* @param path - Canonical vault path
|
|
211
|
+
*/
|
|
212
|
+
function tenantIdFromVaultPath(path: string): string | null {
|
|
213
|
+
const i = path.indexOf("/");
|
|
214
|
+
if (i <= 0) return null;
|
|
215
|
+
const id = path.slice(0, i);
|
|
216
|
+
return id.length > 0 ? id : null;
|
|
217
|
+
}
|
|
218
|
+
|
|
207
219
|
/** `oke_vault_secrets` row as returned by the driver. */
|
|
208
220
|
interface SecretRow {
|
|
209
221
|
id: string;
|
|
@@ -539,8 +551,8 @@ export function createBuiltinVaultAdapter(opts: CreateBuiltinVaultOptions): Buil
|
|
|
539
551
|
updated_at: Date | string;
|
|
540
552
|
}>(
|
|
541
553
|
`INSERT INTO oke_vault_secrets
|
|
542
|
-
(path, encrypted_value, iv, auth_tag, version, metadata, algorithm, kek_version, expires_at)
|
|
543
|
-
VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7, $8, $9)
|
|
554
|
+
(path, encrypted_value, iv, auth_tag, version, metadata, algorithm, kek_version, expires_at, tenant_id)
|
|
555
|
+
VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7, $8, $9, $10)
|
|
544
556
|
RETURNING id, created_at, updated_at`,
|
|
545
557
|
[
|
|
546
558
|
path,
|
|
@@ -552,6 +564,7 @@ export function createBuiltinVaultAdapter(opts: CreateBuiltinVaultOptions): Buil
|
|
|
552
564
|
ALGORITHM,
|
|
553
565
|
kekVersion,
|
|
554
566
|
expiresAt ?? null,
|
|
567
|
+
tenantIdFromVaultPath(path),
|
|
555
568
|
],
|
|
556
569
|
);
|
|
557
570
|
const row = inserted[0];
|
|
@@ -24,6 +24,11 @@ export interface VaultSecretOptions {
|
|
|
24
24
|
* in the clear. Defaults to `true` for secrets and `false` for config.
|
|
25
25
|
*/
|
|
26
26
|
readonly sensitive?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Resolve per `fx.tenant.id` at request time (not boot).
|
|
29
|
+
* Default true when `gate.auth.tenant` is on.
|
|
30
|
+
*/
|
|
31
|
+
readonly perTenant?: boolean;
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
/** Declared vault contract handle. */
|
|
@@ -38,6 +43,8 @@ export interface VaultSecretDecl {
|
|
|
38
43
|
readonly dev?: string;
|
|
39
44
|
/** Whether cleartext must never leave the runtime (default by kind). */
|
|
40
45
|
readonly sensitive: boolean;
|
|
46
|
+
/** Request-time tenant path when true. */
|
|
47
|
+
readonly perTenant?: boolean;
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
/**
|
|
@@ -82,6 +89,7 @@ function declareSecret(name: string, options: VaultSecretOptions = {}): VaultSec
|
|
|
82
89
|
...(options.rotate !== undefined ? { rotate: options.rotate } : {}),
|
|
83
90
|
...(options.schema !== undefined ? { schema: options.schema } : {}),
|
|
84
91
|
...(options.dev !== undefined ? { dev: options.dev } : {}),
|
|
92
|
+
...(options.perTenant !== undefined ? { perTenant: options.perTenant } : {}),
|
|
85
93
|
};
|
|
86
94
|
secretRegistry.push(decl);
|
|
87
95
|
return decl;
|
|
@@ -95,6 +95,10 @@ export interface CreateVaultRuntimeOptions {
|
|
|
95
95
|
readonly allowGaps?: boolean;
|
|
96
96
|
/** Clock for last-read timestamps (tests). */
|
|
97
97
|
readonly now?: () => number;
|
|
98
|
+
/**
|
|
99
|
+
* When true, contracts with `perTenant !== false` are not boot-gap fatal.
|
|
100
|
+
*/
|
|
101
|
+
readonly tenantEnabled?: boolean;
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
/** Vault runtime surface. */
|
|
@@ -244,6 +248,9 @@ export function createVaultRuntime(options: CreateVaultRuntimeOptions = {}): Vau
|
|
|
244
248
|
|
|
245
249
|
const gaps: VaultGap[] = [];
|
|
246
250
|
for (const c of contracts.values()) {
|
|
251
|
+
const perTenant =
|
|
252
|
+
c.perTenant === true || (options.tenantEnabled === true && c.perTenant !== false);
|
|
253
|
+
if (perTenant) continue;
|
|
247
254
|
const value = merged.get(c.name);
|
|
248
255
|
if (value === undefined || value.length === 0) {
|
|
249
256
|
gaps.push({
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `oke_vault_secrets` tenant RLS — same concurrent-identity proof as
|
|
3
|
+
* domain tables: two tenants, one shared PGlite connection, row-level
|
|
4
|
+
* isolation via `oke.tenant()`.
|
|
5
|
+
*
|
|
6
|
+
* Policy is installed by production DDL ({@link ensureVaultTables} /
|
|
7
|
+
* {@link installOkeRlsHelpers}), not by this file.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
11
|
+
import { connectPglite } from "../../drivers/pglite.ts";
|
|
12
|
+
import { installOkeRlsHelpers } from "../../drivers/pg-rls.ts";
|
|
13
|
+
import type { SqlConnection } from "../../drivers/types.ts";
|
|
14
|
+
import { createSqlStoreHandle, type SqlStoreHandle } from "../store/sql-session.ts";
|
|
15
|
+
import { sqlConnectionAsExec } from "./builtin-adapter.ts";
|
|
16
|
+
import { ensureVaultTables } from "./storage.ts";
|
|
17
|
+
|
|
18
|
+
const CIPHER = String.raw`'\x00'::bytea`;
|
|
19
|
+
|
|
20
|
+
const SEED = `
|
|
21
|
+
INSERT INTO oke_vault_secrets (path, encrypted_value, iv, auth_tag, version, tenant_id)
|
|
22
|
+
VALUES
|
|
23
|
+
('acme/stripe', ${CIPHER}, ${CIPHER}, ${CIPHER}, 1, 'acme'),
|
|
24
|
+
('globex/stripe', ${CIPHER}, ${CIPHER}, ${CIPHER}, 1, 'globex'),
|
|
25
|
+
('GLOBAL_KEY', ${CIPHER}, ${CIPHER}, ${CIPHER}, 1, NULL)
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
/** File-scoped warmed PGlite. */
|
|
29
|
+
let conn: SqlConnection;
|
|
30
|
+
/** Stamped as tenant acme. */
|
|
31
|
+
let asAcme: SqlStoreHandle;
|
|
32
|
+
/** Stamped as tenant globex. */
|
|
33
|
+
let asGlobex: SqlStoreHandle;
|
|
34
|
+
/** No stamp — table-owner / superuser bypass (ENABLE without FORCE). */
|
|
35
|
+
let asOwner: SqlStoreHandle;
|
|
36
|
+
|
|
37
|
+
beforeAll(async () => {
|
|
38
|
+
conn = await connectPglite({ url: "memory://vault-sql-rls-isolation", role: "primary" });
|
|
39
|
+
await installOkeRlsHelpers((sql) => conn.exec(sql));
|
|
40
|
+
await ensureVaultTables(sqlConnectionAsExec(conn));
|
|
41
|
+
await conn.exec(SEED);
|
|
42
|
+
asAcme = createSqlStoreHandle("sql:app", {
|
|
43
|
+
connection: conn,
|
|
44
|
+
classifications: new Map(),
|
|
45
|
+
routedRole: "primary",
|
|
46
|
+
domainDdl: "off",
|
|
47
|
+
rls: { gate: "member", userId: "u1", scopes: ["member"], tenantId: "acme" },
|
|
48
|
+
});
|
|
49
|
+
asGlobex = createSqlStoreHandle("sql:app", {
|
|
50
|
+
connection: conn,
|
|
51
|
+
classifications: new Map(),
|
|
52
|
+
routedRole: "primary",
|
|
53
|
+
domainDdl: "off",
|
|
54
|
+
rls: { gate: "member", userId: "u1", scopes: ["member"], tenantId: "globex" },
|
|
55
|
+
});
|
|
56
|
+
asOwner = createSqlStoreHandle("sql:app", {
|
|
57
|
+
connection: conn,
|
|
58
|
+
classifications: new Map(),
|
|
59
|
+
routedRole: "primary",
|
|
60
|
+
domainDdl: "off",
|
|
61
|
+
});
|
|
62
|
+
}, 15_000);
|
|
63
|
+
|
|
64
|
+
afterAll(async () => {
|
|
65
|
+
await conn.close();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe("oke_vault_secrets tenant RLS (pglite)", () => {
|
|
69
|
+
test("production DDL installed oke_vault_secrets_tenant", async () => {
|
|
70
|
+
const rows = await conn.query(
|
|
71
|
+
`SELECT policyname FROM pg_policies
|
|
72
|
+
WHERE tablename = 'oke_vault_secrets' AND policyname = 'oke_vault_secrets_tenant'`,
|
|
73
|
+
);
|
|
74
|
+
expect(rows).toEqual([{ policyname: "oke_vault_secrets_tenant" }]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("sequential identities see own tenant plus global NULL rows", async () => {
|
|
78
|
+
const acme = await asAcme.raw(`SELECT path, tenant_id FROM oke_vault_secrets ORDER BY path`);
|
|
79
|
+
const globex = await asGlobex.raw(
|
|
80
|
+
`SELECT path, tenant_id FROM oke_vault_secrets ORDER BY path`,
|
|
81
|
+
);
|
|
82
|
+
expect(acme).toEqual([
|
|
83
|
+
{ path: "GLOBAL_KEY", tenant_id: null },
|
|
84
|
+
{ path: "acme/stripe", tenant_id: "acme" },
|
|
85
|
+
]);
|
|
86
|
+
expect(globex).toEqual([
|
|
87
|
+
{ path: "GLOBAL_KEY", tenant_id: null },
|
|
88
|
+
{ path: "globex/stripe", tenant_id: "globex" },
|
|
89
|
+
]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("concurrent identities do not leak across the shared connection", async () => {
|
|
93
|
+
const [acme, globex, acmeAgain] = await Promise.all([
|
|
94
|
+
asAcme.raw(`SELECT path, tenant_id FROM oke_vault_secrets ORDER BY path`),
|
|
95
|
+
asGlobex.raw(`SELECT path, tenant_id FROM oke_vault_secrets ORDER BY path`),
|
|
96
|
+
asAcme.raw(`SELECT path, tenant_id FROM oke_vault_secrets ORDER BY path`),
|
|
97
|
+
]);
|
|
98
|
+
expect(acme).toEqual([
|
|
99
|
+
{ path: "GLOBAL_KEY", tenant_id: null },
|
|
100
|
+
{ path: "acme/stripe", tenant_id: "acme" },
|
|
101
|
+
]);
|
|
102
|
+
expect(globex).toEqual([
|
|
103
|
+
{ path: "GLOBAL_KEY", tenant_id: null },
|
|
104
|
+
{ path: "globex/stripe", tenant_id: "globex" },
|
|
105
|
+
]);
|
|
106
|
+
expect(acmeAgain).toEqual([
|
|
107
|
+
{ path: "GLOBAL_KEY", tenant_id: null },
|
|
108
|
+
{ path: "acme/stripe", tenant_id: "acme" },
|
|
109
|
+
]);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("unstamped owner still sees every ciphertext row", async () => {
|
|
113
|
+
const rows = await asOwner.raw(`SELECT path, tenant_id FROM oke_vault_secrets ORDER BY path`);
|
|
114
|
+
expect(rows).toEqual([
|
|
115
|
+
{ path: "GLOBAL_KEY", tenant_id: null },
|
|
116
|
+
{ path: "acme/stripe", tenant_id: "acme" },
|
|
117
|
+
{ path: "globex/stripe", tenant_id: "globex" },
|
|
118
|
+
]);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe("oke_vault_secrets tenant RLS — tables before helpers", () => {
|
|
123
|
+
test("helper install attaches the policy after the table exists", async () => {
|
|
124
|
+
const late = await connectPglite({
|
|
125
|
+
url: "memory://vault-sql-rls-late-helpers",
|
|
126
|
+
role: "primary",
|
|
127
|
+
});
|
|
128
|
+
try {
|
|
129
|
+
await ensureVaultTables(sqlConnectionAsExec(late));
|
|
130
|
+
const before = await late.query(
|
|
131
|
+
`SELECT COUNT(*)::text AS n FROM pg_policies
|
|
132
|
+
WHERE tablename = 'oke_vault_secrets' AND policyname = 'oke_vault_secrets_tenant'`,
|
|
133
|
+
);
|
|
134
|
+
expect(before[0]?.n).toBe("0");
|
|
135
|
+
await installOkeRlsHelpers((sql) => late.exec(sql));
|
|
136
|
+
const after = await late.query(
|
|
137
|
+
`SELECT COUNT(*)::text AS n FROM pg_policies
|
|
138
|
+
WHERE tablename = 'oke_vault_secrets' AND policyname = 'oke_vault_secrets_tenant'`,
|
|
139
|
+
);
|
|
140
|
+
expect(after[0]?.n).toBe("1");
|
|
141
|
+
} finally {
|
|
142
|
+
await late.close();
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -14,6 +14,7 @@ import type { AuditAction, AuditEntry, AuditWriter } from "./audit.ts";
|
|
|
14
14
|
import { VaultError } from "./errors.ts";
|
|
15
15
|
import type { VaultErrorCode } from "./errors.ts";
|
|
16
16
|
import type { VaultActorType } from "./types.ts";
|
|
17
|
+
import { ensureVaultSecretsTenantRls } from "../../drivers/pg-vault-rls.ts";
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Minimal SQL surface the Vault needs.
|
|
@@ -75,6 +76,7 @@ CREATE TABLE IF NOT EXISTS oke_vault_secrets (
|
|
|
75
76
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
76
77
|
expires_at timestamptz,
|
|
77
78
|
deleted_at timestamptz,
|
|
79
|
+
tenant_id text,
|
|
78
80
|
CONSTRAINT oke_vault_secrets_path_version_key UNIQUE (path, version)
|
|
79
81
|
)`;
|
|
80
82
|
|
|
@@ -168,6 +170,8 @@ const POST_DDL_STATEMENTS: readonly string[] = [
|
|
|
168
170
|
`ALTER TABLE oke_vault_status ADD COLUMN IF NOT EXISTS rewrap_key_hash text`,
|
|
169
171
|
`ALTER TABLE oke_vault_status ADD COLUMN IF NOT EXISTS rotate_locked_by text`,
|
|
170
172
|
`ALTER TABLE oke_vault_status ADD COLUMN IF NOT EXISTS rotate_lease_expires_at bigint`,
|
|
173
|
+
`ALTER TABLE oke_vault_secrets ADD COLUMN IF NOT EXISTS tenant_id text`,
|
|
174
|
+
`CREATE INDEX IF NOT EXISTS oke_vault_secrets_tenant_idx ON oke_vault_secrets (tenant_id)`,
|
|
171
175
|
`CREATE INDEX IF NOT EXISTS oke_vault_audit_seq_idx ON oke_vault_audit (seq)`,
|
|
172
176
|
`CREATE INDEX IF NOT EXISTS oke_vault_audit_created_idx ON oke_vault_audit (created_at, id)`,
|
|
173
177
|
`CREATE INDEX IF NOT EXISTS oke_vault_audit_path_idx ON oke_vault_audit (path)`,
|
|
@@ -200,6 +204,11 @@ export async function ensureVaultTables(db: SqlExec): Promise<void> {
|
|
|
200
204
|
throw new VaultError("BACKEND_ERROR", "vault: failed to ensure vault tables");
|
|
201
205
|
}
|
|
202
206
|
}
|
|
207
|
+
try {
|
|
208
|
+
await ensureVaultSecretsTenantRls((sql: string) => db.execute(sql));
|
|
209
|
+
} catch {
|
|
210
|
+
throw new VaultError("BACKEND_ERROR", "vault: failed to ensure vault tables");
|
|
211
|
+
}
|
|
203
212
|
}
|
|
204
213
|
|
|
205
214
|
/**
|
|
@@ -251,7 +251,8 @@ function isDdl(text: string): boolean {
|
|
|
251
251
|
return (
|
|
252
252
|
text.startsWith("create table") ||
|
|
253
253
|
text.startsWith("alter table") ||
|
|
254
|
-
text.startsWith("create index")
|
|
254
|
+
text.startsWith("create index") ||
|
|
255
|
+
text.startsWith("do $oke_vault_rls$")
|
|
255
256
|
);
|
|
256
257
|
}
|
|
257
258
|
|
package/src/i18n/catalogs/ar.ts
CHANGED
|
@@ -36,6 +36,22 @@ export const builtinAr = {
|
|
|
36
36
|
cause: 'التدفق "{flow}" يُصدِر الإشارة "{resource}" بلا مشترك.',
|
|
37
37
|
fix: "أضف on({resource}, …) أو عيّن الإشارة '{'optional: true'}'.",
|
|
38
38
|
},
|
|
39
|
+
"1014": {
|
|
40
|
+
cause: 'مؤشر البث "{afterId}" ليس على شريط "{signal}".',
|
|
41
|
+
fix: "أعد الاتصال بدون Last-Event-ID لإعادة تشغيل الشريط المتبقي.",
|
|
42
|
+
},
|
|
43
|
+
"1015": {
|
|
44
|
+
cause: "هذه العملية تحتاج مستأجراً، ولم يُحدَّد مستأجر لهذا الطلب.",
|
|
45
|
+
fix: "استدعِ fx.auth.switchTenant(id) أو أرسل مطالبة tid موقّعة أو رأس المستأجر.",
|
|
46
|
+
},
|
|
47
|
+
"1016": {
|
|
48
|
+
cause: 'المتصل ليس عضواً في المستأجر "{tenant}".',
|
|
49
|
+
fix: "اختر مستأجراً من fx.auth.listTenants() أو أضف المستخدم كعضو.",
|
|
50
|
+
},
|
|
51
|
+
"1017": {
|
|
52
|
+
cause: 'نطاق دور المستأجر "{scope}" ليس نطاقاً تطبيقياً معلناً.',
|
|
53
|
+
fix: "استخدم اسماً من كتالوج البيان لهذا التطبيق (وليس console:*).",
|
|
54
|
+
},
|
|
39
55
|
"1101": {
|
|
40
56
|
cause: "جدول النطاق غير موجود — لم تُطبَّق الترحيلات.",
|
|
41
57
|
fix: "شغّل `oke db migrate` على هذه البيئة.",
|
|
@@ -62,6 +78,9 @@ export const builtinAr = {
|
|
|
62
78
|
"Forbidden.ip_denied": "عنوان IP الخاص بك محظور.",
|
|
63
79
|
"Forbidden.ip_not_allowed": "عنوان IP الخاص بك غير مسموح.",
|
|
64
80
|
"Forbidden.policy_denied": "رفضت السياسة هذا الطلب.",
|
|
81
|
+
"Forbidden.tenant_required": "هذا الطلب يحتاج مستأجراً.",
|
|
82
|
+
"Forbidden.not_member": "لست عضواً في ذلك المستأجر.",
|
|
83
|
+
"Forbidden.unknown_scope": "نطاق دور المستأجر هذا ليس نطاقاً تطبيقياً معلناً.",
|
|
65
84
|
"AuthRateLimited.rate_limited": "محاولات مصادقة كثيرة جداً. حاول لاحقاً.",
|
|
66
85
|
},
|
|
67
86
|
} as const;
|
package/src/i18n/catalogs/en.ts
CHANGED
|
@@ -36,6 +36,22 @@ export const builtinEn = {
|
|
|
36
36
|
cause: 'Flow "{flow}" emits signal "{resource}" with no subscriber.',
|
|
37
37
|
fix: "Add on({resource}, …) or mark the signal '{'optional: true'}'.",
|
|
38
38
|
},
|
|
39
|
+
"1014": {
|
|
40
|
+
cause: 'Live cursor "{afterId}" is not on the tape for "{signal}".',
|
|
41
|
+
fix: "Reconnect without Last-Event-ID to replay the remaining tape.",
|
|
42
|
+
},
|
|
43
|
+
"1015": {
|
|
44
|
+
cause: "This operation needs a tenant, but none is resolved for this request.",
|
|
45
|
+
fix: "Call fx.auth.switchTenant(id), send a signed tid claim, or pass the tenant header.",
|
|
46
|
+
},
|
|
47
|
+
"1016": {
|
|
48
|
+
cause: 'The caller is not a member of tenant "{tenant}".',
|
|
49
|
+
fix: "Pick a tenant from fx.auth.listTenants() or add the user as a member.",
|
|
50
|
+
},
|
|
51
|
+
"1017": {
|
|
52
|
+
cause: 'Tenant role scope "{scope}" is not a declared application scope.',
|
|
53
|
+
fix: "Use a name from this app's Manifest catalog (never console:*).",
|
|
54
|
+
},
|
|
39
55
|
"1101": {
|
|
40
56
|
cause: "domain table not found — migrations have not been applied.",
|
|
41
57
|
fix: "run `oke db migrate` against this environment.",
|
|
@@ -63,6 +79,9 @@ export const builtinEn = {
|
|
|
63
79
|
"Forbidden.ip_denied": "Your IP address is blocked.",
|
|
64
80
|
"Forbidden.ip_not_allowed": "Your IP address is not allowed.",
|
|
65
81
|
"Forbidden.policy_denied": "Policy denied this request.",
|
|
82
|
+
"Forbidden.tenant_required": "This request needs a tenant.",
|
|
83
|
+
"Forbidden.not_member": "You are not a member of that tenant.",
|
|
84
|
+
"Forbidden.unknown_scope": "That tenant role scope is not a declared application scope.",
|
|
66
85
|
"AuthRateLimited.rate_limited": "Too many authentication attempts. Try again later.",
|
|
67
86
|
},
|
|
68
87
|
} as const;
|
package/src/index.ts
CHANGED
|
@@ -62,6 +62,7 @@ export {
|
|
|
62
62
|
type StoreDecl,
|
|
63
63
|
type StoreRuntime,
|
|
64
64
|
type SqlStoreHandle,
|
|
65
|
+
type InferSelectRow,
|
|
65
66
|
type TableHandle,
|
|
66
67
|
type SchemaTableDecl,
|
|
67
68
|
type SchemaColumnDecl,
|
|
@@ -79,6 +80,7 @@ export {
|
|
|
79
80
|
createSignalRuntime,
|
|
80
81
|
type SignalDecl,
|
|
81
82
|
type SignalOptions,
|
|
83
|
+
type SignalRetention,
|
|
82
84
|
type SignalRuntime,
|
|
83
85
|
type DeadLetter,
|
|
84
86
|
type SignalFailureReason,
|