okengine 0.14.1 → 0.16.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/package.json +3 -4
- package/site/content/docs/elements/ai.mdx +2 -1
- package/site/content/docs/elements/channel.mdx +1 -0
- package/site/content/docs/elements/clock.mdx +3 -0
- package/site/content/docs/elements/flow.mdx +9 -0
- package/site/content/docs/elements/gate.mdx +80 -22
- package/site/content/docs/elements/signal.mdx +4 -4
- package/site/content/docs/elements/store.mdx +84 -27
- package/site/content/docs/elements/vault.mdx +4 -0
- package/site/content/docs/get-started/basic-usage.mdx +22 -13
- package/site/content/docs/get-started/index.mdx +5 -0
- package/site/content/docs/get-started/introduction.mdx +11 -3
- package/site/content/docs/get-started/meta.json +1 -1
- package/site/content/docs/get-started/project-structure.mdx +932 -0
- package/site/content/docs/reference/cli.md +13 -10
- package/site/content/docs/reference/client.mdx +28 -21
- package/site/content/docs/reference/fx.mdx +29 -18
- package/site/content/docs/reference/security.md +2 -2
- package/src/auth/api-key-sql.test.ts +47 -0
- package/src/auth/api-key-sql.ts +254 -0
- package/src/auth/api-keys.ts +273 -14
- package/src/auth/auth.test.ts +56 -0
- package/src/auth/bindings.ts +14 -5
- package/src/auth/config.ts +5 -5
- package/src/auth/cross-plane.ts +10 -3
- package/src/auth/index.ts +25 -1
- package/src/auth/schema.ts +8 -0
- package/src/cli/ai-setup/ai-setup.test.ts +62 -0
- package/src/cli/ai-setup/apply.ts +32 -1
- package/src/cli/attach-host-console.test.ts +40 -2
- package/src/cli/attach-host-console.ts +9 -0
- package/src/cli/build.test.ts +4 -2
- package/src/cli/db-seed.ts +48 -15
- package/src/cli/db.test.ts +104 -0
- package/src/cli/db.ts +122 -10
- package/src/cli/dev-controls.test.ts +20 -0
- package/src/cli/dev-controls.ts +13 -0
- package/src/cli/dev.test.ts +15 -4
- package/src/cli/dev.ts +102 -29
- package/src/cli/ensure-drizzle-config.ts +2 -1
- package/src/cli/tui/DevLive.tsx +12 -2
- package/src/cli/tui/keys.test.ts +2 -1
- package/src/cli/tui/keys.ts +3 -2
- package/src/compiler/aot.test.ts +1 -1
- package/src/compiler/effects-infer.ts +17 -2
- package/src/compiler/extract.test.ts +409 -33
- package/src/compiler/extract.ts +318 -67
- package/src/compiler/flow-path.test.ts +96 -0
- package/src/compiler/flow-path.ts +173 -0
- package/src/compiler/generate-adopt.test.ts +112 -3
- package/src/compiler/generate-adopt.ts +280 -26
- package/src/compiler/schema-from-ast.ts +609 -0
- package/src/console/server/access.test.ts +116 -8
- package/src/console/server/access.ts +70 -23
- package/src/console/server/app.ts +1 -0
- package/src/console/server/bind.ts +2 -2
- package/src/console/server/console.test.ts +3 -0
- 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 +233 -98
- package/src/console/server/gates.test.ts +24 -0
- package/src/console/server/gates.ts +8 -21
- package/src/console/server/index.ts +1 -0
- package/src/console/server/invoke-user-flow.test.ts +3 -1
- package/src/console/server/invoke-user-flow.ts +17 -2
- package/src/console/server/iso-at.test.ts +9 -0
- package/src/console/server/iso-at.ts +11 -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/security-headers.ts +6 -3
- package/src/console/server/spa-proxy.test.ts +1 -0
- package/src/console/server/sql-catalog.ts +147 -7
- package/src/console/server/sql-rls.test.ts +16 -0
- package/src/console/server/state.ts +37 -55
- package/src/console/server/store.test.ts +7 -4
- package/src/console/server/store.ts +142 -58
- package/src/console/ui-next/dist/assets/access-page-CAGHrA9H.js +4 -0
- package/src/console/ui-next/dist/assets/agent-disclosure-BP0Y0Sux.js +1 -0
- 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/{duration-tone-oiRxPVsZ.js → duration-tone-sC3lGABz.js} +1 -1
- 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-B-OUtiAu.js +1 -0
- package/src/console/ui-next/dist/assets/{highlighted-json-CvDPvveV.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-CGoZkILK.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-BDyXalNR.js +4 -0
- package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +1 -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-CL_X0GCj.js → skeleton-D-czQJT6.js} +1 -1
- package/src/console/ui-next/dist/assets/store-page-Xh8Kn3rx.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-DkOXA12R.js +54 -0
- package/src/console/ui-next/dist/assets/units-page-Dpk40kOQ.js +1 -0
- package/src/console/ui-next/dist/assets/{use-vault-list-Cl79j_ku.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-B1dB9Ft0.js +2 -0
- package/src/console/ui-next/dist/index.html +12 -10
- package/src/console/ui-next/index.html +3 -3
- package/src/console/ui-next/src/client.ts +202 -7
- package/src/console/ui-next/src/components/motion/table/index.tsx +16 -0
- package/src/console/ui-next/src/components/motion/table/types.ts +4 -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/kbd.tsx +2 -2
- 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/traces/http-method.ts +19 -0
- package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +19 -1
- package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
- package/src/console/ui-next/src/features/not-found/not-found-page.tsx +1 -1
- package/src/console/ui-next/src/features/store/detail/browse-section.tsx +45 -1
- package/src/console/ui-next/src/features/store/detail/resource-panel.tsx +78 -16
- package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +155 -14
- package/src/console/ui-next/src/features/store/explorer/store-tree.tsx +33 -133
- package/src/console/ui-next/src/features/store/files/file-preview.tsx +4 -1
- package/src/console/ui-next/src/features/store/grid/rls-policy-sheet.tsx +326 -165
- package/src/console/ui-next/src/features/store/grid/sql-insert-sheet.tsx +6 -0
- package/src/console/ui-next/src/features/store/grid/store-data-grid.tsx +50 -3
- package/src/console/ui-next/src/features/store/lib/files-meta.test.ts +3 -0
- package/src/console/ui-next/src/features/store/lib/files-meta.ts +12 -0
- package/src/console/ui-next/src/features/store/lib/query-defaults.test.ts +25 -0
- package/src/console/ui-next/src/features/store/lib/query-defaults.ts +85 -3
- package/src/console/ui-next/src/features/store/lib/query-gate.ts +30 -4
- package/src/console/ui-next/src/features/store/lib/rls-policy.test.ts +139 -0
- package/src/console/ui-next/src/features/store/lib/rls-policy.ts +223 -21
- package/src/console/ui-next/src/features/store/lib/schema-graph.ts +4 -0
- package/src/console/ui-next/src/features/store/lib/sql-catalog.test.ts +24 -0
- package/src/console/ui-next/src/features/store/lib/sql-catalog.ts +19 -0
- package/src/console/ui-next/src/features/store/lib/store-tree.test.ts +6 -0
- package/src/console/ui-next/src/features/store/lib/store-tree.ts +13 -0
- package/src/console/ui-next/src/features/store/query/query-console.tsx +48 -14
- package/src/console/ui-next/src/features/store/query/query-gate-menu.tsx +19 -20
- package/src/console/ui-next/src/features/store/query/query-gate-parts.tsx +2 -1
- package/src/console/ui-next/src/features/store/query/query-results.tsx +110 -83
- package/src/console/ui-next/src/features/store/schema/schema-table-node.tsx +17 -0
- package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +112 -72
- package/src/console/ui-next/src/features/units/lib/contract-input.test.ts +206 -1
- package/src/console/ui-next/src/features/units/lib/contract-input.ts +55 -0
- package/src/console/ui-next/src/features/vault/lib/types.ts +1 -1
- 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-store.ts +43 -15
- package/src/console/xss.gate.test.ts +1 -0
- package/src/docker/compose-health.test.ts +27 -0
- package/src/docker/compose-health.ts +19 -3
- package/src/docker/docker.test.ts +3 -0
- package/src/docker/recipes/pgdog.ts +2 -0
- package/src/drivers/clock-postgres.ts +13 -9
- package/src/drivers/instances-postgres.ts +4 -6
- package/src/drivers/journal-postgres.ts +13 -9
- package/src/drivers/pg-rls.test.ts +87 -0
- package/src/drivers/pg-rls.ts +233 -7
- package/src/drivers/pglite.ts +26 -3
- package/src/drivers/postgres.test.ts +23 -0
- package/src/drivers/postgres.ts +133 -9
- package/src/drivers/types.ts +10 -0
- package/src/elements/clock/declare.ts +26 -1
- package/src/elements/gate/boot.ts +5 -5
- package/src/elements/gate/declare.ts +38 -4
- package/src/elements/gate/flatten.ts +1 -1
- package/src/elements/gate/runtime.ts +1 -1
- package/src/elements/store/cache.ts +1 -1
- package/src/elements/store/declare.ts +9 -6
- package/src/elements/store/emit-drizzle.ts +44 -8
- package/src/elements/store/resource.test.ts +1 -1
- package/src/elements/store/rls-identity.test.ts +95 -0
- package/src/elements/store/rls-identity.ts +116 -0
- package/src/elements/store/runtime.ts +18 -0
- package/src/elements/store/schema-decl.test.ts +37 -0
- package/src/elements/store/schema-decl.ts +260 -41
- package/src/elements/store/seed.ts +1 -1
- package/src/elements/store/sql-condition.test.ts +12 -0
- package/src/elements/store/sql-condition.ts +26 -2
- package/src/elements/store/sql-rls-isolation.test.ts +165 -0
- package/src/elements/store/sql-rls-stamp.test.ts +117 -0
- package/src/elements/store/sql-select.types.test.ts +71 -0
- package/src/elements/store/sql-session.ts +135 -35
- package/src/elements/store/upsert-app.test.ts +1 -1
- package/src/elements/store.ts +17 -0
- package/src/http.ts +20 -3
- package/src/index.ts +11 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
- package/src/kernel/adopt-routes.ts +30 -2
- package/src/kernel/api-key-host-persist.test.ts +143 -0
- package/src/kernel/app.ts +145 -16
- package/src/kernel/auth-resolve.ts +10 -5
- package/src/kernel/auto-cache.test.ts +8 -8
- package/src/kernel/auto-registry.test.ts +49 -9
- package/src/kernel/boot.test.ts +5 -5
- package/src/kernel/boot.ts +2 -2
- package/src/kernel/effects-stamping.test.ts +3 -3
- package/src/kernel/element-registries.ts +12 -5
- package/src/kernel/errors.ts +24 -0
- package/src/kernel/flow-units.ts +64 -0
- package/src/kernel/flow.ts +29 -17
- package/src/kernel/fx-auth-keys.test.ts +88 -0
- package/src/kernel/fx-auth-keys.ts +198 -0
- package/src/kernel/fx.test.ts +17 -1
- package/src/kernel/fx.ts +59 -18
- package/src/kernel/horizontal-child.ts +4 -4
- package/src/kernel/http-path-pending.ts +20 -0
- package/src/kernel/http-resource.ts +4 -0
- package/src/kernel/http-stream.test.ts +4 -4
- package/src/kernel/pipeline-api-key.test.ts +192 -7
- package/src/kernel/pipeline.test.ts +2 -2
- package/src/kernel/pipeline.ts +4 -2
- package/src/kernel/plugin-elements.test.ts +1 -1
- package/src/kernel/plugin-needs.test.ts +1 -1
- package/src/kernel/ready.test.ts +2 -2
- package/src/kernel/resource-mount.test.ts +14 -0
- package/src/kernel/run-duration.test.ts +2 -2
- package/src/kernel/stamp-http.test.ts +79 -0
- package/src/kernel/stamp-http.ts +75 -0
- package/src/kernel/triggers.ts +52 -27
- package/src/manifest/types.ts +22 -3
- package/src/release/build-lib.ts +1 -0
- package/src/release/measure.ts +2 -2
- package/src/runtime/json-code-block.test.ts +2 -2
- package/src/runtime/serve.test.ts +4 -4
- package/src/term.test.ts +52 -4
- package/src/term.ts +125 -17
- package/src/test/reset-element-registries.ts +8 -3
- package/src/cli/ask-seed.test.ts +0 -96
- package/src/cli/ask-seed.ts +0 -82
- package/src/console/ui-next/dist/assets/cache-glyph-BanhLsEY.js +0 -1
- package/src/console/ui-next/dist/assets/call-pii-button-bqkxMrJH.js +0 -1
- package/src/console/ui-next/dist/assets/collapsible-DYb0xU8C.js +0 -1
- package/src/console/ui-next/dist/assets/confirm-sheet-DDCRmG62.js +0 -1
- package/src/console/ui-next/dist/assets/explorer-empty-HjTnVQoR.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-DxDsOd4f.js +0 -1
- package/src/console/ui-next/dist/assets/http-method-CJCBYL2j.js +0 -1
- package/src/console/ui-next/dist/assets/index-Ce6WKWKM.js +0 -66
- package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
- package/src/console/ui-next/dist/assets/link-DF7SZ9Ek.js +0 -1
- package/src/console/ui-next/dist/assets/observability-page-BEZDzyYh.js +0 -4
- package/src/console/ui-next/dist/assets/query-gate-parts-1m8m1iNp.js +0 -2
- package/src/console/ui-next/dist/assets/react-B1ML8gxg.js +0 -1
- package/src/console/ui-next/dist/assets/replica-lag-C8_BRt2x.js +0 -18
- package/src/console/ui-next/dist/assets/sheet-form-D-ata7jy.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-Cgzuv4k1.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-DO4IsVqv.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-v3LXdYpr.js +0 -45
- package/src/console/ui-next/dist/assets/trace-detail-sheet-DFLFfUUX.js +0 -2
- package/src/console/ui-next/dist/assets/units-page-C0gW6Kdo.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-DuKzqwzW.js +0 -2
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RLS stamps on pooled postgres must `reserve()` one slot, then BEGIN/SET
|
|
3
|
+
* on that slot. `begin()` + parent `unsafe()` checkouts a second slot and
|
|
4
|
+
* deadlocks a tiny pool (`checkout timeout` from PgDog).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, expect, test } from "bun:test";
|
|
8
|
+
import { connectPostgres, type PostgresClientLike } from "../../drivers/postgres.ts";
|
|
9
|
+
import { createSqlStoreHandle } from "./sql-session.ts";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Two-slot pool. `reserve()` holds a slot until `release()`. Unreserved
|
|
13
|
+
* `BEGIN` occupies a slot until COMMIT on that same checkout.
|
|
14
|
+
*/
|
|
15
|
+
function createTinyPoolClient(size = 2): PostgresClientLike & {
|
|
16
|
+
readonly reserveCalls: { n: number };
|
|
17
|
+
readonly rootBegins: { n: number };
|
|
18
|
+
} {
|
|
19
|
+
const slots = Array.from({ length: size }, () => ({ held: false }));
|
|
20
|
+
const reserveCalls = { n: 0 };
|
|
21
|
+
const rootBegins = { n: 0 };
|
|
22
|
+
|
|
23
|
+
function checkout(): { held: boolean } {
|
|
24
|
+
const idle = slots.find((slot) => !slot.held);
|
|
25
|
+
if (!idle) {
|
|
26
|
+
throw new Error("checkout timeout");
|
|
27
|
+
}
|
|
28
|
+
return idle;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function rowsFor(sql: string): Record<string, unknown>[] {
|
|
32
|
+
return /select/i.test(sql) && !/set_config/i.test(sql) ? [{ id: "a", owner: "alice" }] : [];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const client: PostgresClientLike & {
|
|
36
|
+
readonly reserveCalls: { n: number };
|
|
37
|
+
readonly rootBegins: { n: number };
|
|
38
|
+
} = {
|
|
39
|
+
reserveCalls,
|
|
40
|
+
rootBegins,
|
|
41
|
+
async reserve() {
|
|
42
|
+
reserveCalls.n += 1;
|
|
43
|
+
const slot = checkout();
|
|
44
|
+
slot.held = true;
|
|
45
|
+
return {
|
|
46
|
+
async unsafe(sql) {
|
|
47
|
+
return rowsFor(sql);
|
|
48
|
+
},
|
|
49
|
+
release() {
|
|
50
|
+
slot.held = false;
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
async unsafe(sql) {
|
|
55
|
+
const slot = checkout();
|
|
56
|
+
const head = sql.trim().split(/\s+/)[0]?.toUpperCase();
|
|
57
|
+
if (head === "BEGIN") {
|
|
58
|
+
rootBegins.n += 1;
|
|
59
|
+
slot.held = true;
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
if (head === "COMMIT" || head === "ROLLBACK") {
|
|
63
|
+
slot.held = false;
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
return rowsFor(sql);
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
return client;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
describe("sql-session RLS stamp on pooled postgres", () => {
|
|
73
|
+
test("pins the stamp through reserve — no leaked root BEGIN", async () => {
|
|
74
|
+
const pool = createTinyPoolClient(2);
|
|
75
|
+
const connection = await connectPostgres({ client: pool, role: "primary" });
|
|
76
|
+
const handle = createSqlStoreHandle("sql:app", {
|
|
77
|
+
connection,
|
|
78
|
+
classifications: new Map(),
|
|
79
|
+
routedRole: "primary",
|
|
80
|
+
domainDdl: "off",
|
|
81
|
+
rls: { gate: "member", userId: "alice", scopes: ["member"] },
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const [a, b] = await Promise.all([
|
|
85
|
+
handle.raw(`SELECT id, owner FROM notes`),
|
|
86
|
+
handle.raw(`SELECT id, owner FROM notes`),
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
expect(a).toEqual([{ id: "a", owner: "alice" }]);
|
|
90
|
+
expect(b).toEqual([{ id: "a", owner: "alice" }]);
|
|
91
|
+
expect(pool.reserveCalls.n).toBe(2);
|
|
92
|
+
expect(pool.rootBegins.n).toBe(0);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("table browse SELECT pins through reserve like any other stamped query", async () => {
|
|
96
|
+
const pool = createTinyPoolClient(2);
|
|
97
|
+
const connection = await connectPostgres({ client: pool, role: "primary" });
|
|
98
|
+
const handle = createSqlStoreHandle("sql:app", {
|
|
99
|
+
connection,
|
|
100
|
+
classifications: new Map(),
|
|
101
|
+
routedRole: "primary",
|
|
102
|
+
domainDdl: "off",
|
|
103
|
+
rls: { gate: "member", userId: "alice", scopes: ["member"] },
|
|
104
|
+
});
|
|
105
|
+
const rows = await handle.raw(`SELECT * FROM "notes" LIMIT 50`);
|
|
106
|
+
expect(rows).toEqual([{ id: "a", owner: "alice" }]);
|
|
107
|
+
expect(pool.reserveCalls.n).toBe(1);
|
|
108
|
+
expect(pool.rootBegins.n).toBe(0);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("standalone BEGIN on a 2-slot pool reproduces checkout timeout", async () => {
|
|
112
|
+
const pool = createTinyPoolClient(2);
|
|
113
|
+
await expect(pool.unsafe("BEGIN")).resolves.toEqual([]);
|
|
114
|
+
await expect(pool.unsafe("BEGIN")).resolves.toEqual([]);
|
|
115
|
+
await expect(pool.unsafe("SET LOCAL ROLE oke_app")).rejects.toThrow("checkout timeout");
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-level proof: `select().from(store.schema.table())` resolves to the
|
|
3
|
+
* declared column JS shape — not `SqlRow`.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
|
|
7
|
+
import type { SqlRow } from "../../drivers/types.ts";
|
|
8
|
+
import { field, schemaTable } from "./schema-decl.ts";
|
|
9
|
+
import type { InferSelectRow, SqlStoreHandle } from "./sql-session.ts";
|
|
10
|
+
import { defineTable } from "./table.ts";
|
|
11
|
+
|
|
12
|
+
/** Compile-time equality. */
|
|
13
|
+
type Eq<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
|
|
14
|
+
type Assert<T extends true> = T;
|
|
15
|
+
|
|
16
|
+
const items = schemaTable("items", {
|
|
17
|
+
id: field.text().primaryKey(),
|
|
18
|
+
title: field.text().notNull(),
|
|
19
|
+
note: field.text(),
|
|
20
|
+
count: field.integer().notNull(),
|
|
21
|
+
score: field.integer(),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
type ExpectedRow = {
|
|
25
|
+
id: string;
|
|
26
|
+
title: string;
|
|
27
|
+
note: string | null;
|
|
28
|
+
count: number;
|
|
29
|
+
score: number | null;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
async function selectAll(db: SqlStoreHandle) {
|
|
33
|
+
return db.select().from(items);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function selectWhere(db: SqlStoreHandle) {
|
|
37
|
+
return db.select().from(items).where({ id: "x" });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function selectOrder(db: SqlStoreHandle) {
|
|
41
|
+
return db.select().from(items).orderBy(items.title);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function selectLimit(db: SqlStoreHandle) {
|
|
45
|
+
return db.select().from(items).limit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function selectProj(db: SqlStoreHandle) {
|
|
49
|
+
return db.select({ title: items.title }).from(items);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const bare = defineTable("bare", { id: { name: "id" } });
|
|
53
|
+
|
|
54
|
+
async function selectBare(db: SqlStoreHandle) {
|
|
55
|
+
return db.select().from(bare);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type _Infer = Assert<Eq<InferSelectRow<typeof items>, ExpectedRow>>;
|
|
59
|
+
type _All = Assert<Eq<Awaited<ReturnType<typeof selectAll>>, ExpectedRow[]>>;
|
|
60
|
+
type _Where = Assert<Eq<Awaited<ReturnType<typeof selectWhere>>, ExpectedRow[]>>;
|
|
61
|
+
type _Order = Assert<Eq<Awaited<ReturnType<typeof selectOrder>>, ExpectedRow[]>>;
|
|
62
|
+
type _Limit = Assert<Eq<Awaited<ReturnType<typeof selectLimit>>, ExpectedRow[]>>;
|
|
63
|
+
type _Proj = Assert<Eq<Awaited<ReturnType<typeof selectProj>>, SqlRow[]>>;
|
|
64
|
+
type _Bare = Assert<Eq<Awaited<ReturnType<typeof selectBare>>, SqlRow[]>>;
|
|
65
|
+
|
|
66
|
+
describe("select().from() row inference", () => {
|
|
67
|
+
test("type-level row shape matches declared columns", () => {
|
|
68
|
+
const _keep: _Infer & _All & _Where & _Order & _Limit & _Proj & _Bare = true;
|
|
69
|
+
expect(_keep).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
|
|
8
8
|
import type { DomainDdlMode } from "../../config/index.ts";
|
|
9
9
|
import type { ClassificationMap, SqlConnection, SqlRow } from "../../drivers/types.ts";
|
|
10
|
+
import {
|
|
11
|
+
buildRlsIdentityPreludeSql,
|
|
12
|
+
installOkeRlsHelpers,
|
|
13
|
+
RLS_CONTEXT_DRIVERS,
|
|
14
|
+
type RlsIdentity,
|
|
15
|
+
} from "../../drivers/pg-rls.ts";
|
|
10
16
|
import { throwOke } from "../../kernel/errors.ts";
|
|
11
17
|
import { maskRows, tableFromSql } from "./classify.ts";
|
|
12
18
|
import { isMissingDomainRelationError } from "./missing-relation.ts";
|
|
@@ -28,6 +34,54 @@ import {
|
|
|
28
34
|
|
|
29
35
|
export type { WhereMap } from "./sql-condition.ts";
|
|
30
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
|
+
|
|
46
|
+
/** Serialize stamp frames on a shared connection so concurrent identities cannot interleave. */
|
|
47
|
+
const rlsStampTails = new WeakMap<SqlConnection, Promise<unknown>>();
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* True when SQL must not enter an RLS identity frame (txn control or DDL).
|
|
51
|
+
*
|
|
52
|
+
* @param sql - Statement
|
|
53
|
+
*/
|
|
54
|
+
export function isRlsStampExemptSql(sql: string): boolean {
|
|
55
|
+
return /^\s*(begin|commit|rollback|create|alter|drop|truncate|grant|revoke|comment)\b/i.test(sql);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Run `fn` after any in-flight stamp on `connection` finishes.
|
|
60
|
+
*
|
|
61
|
+
* @param connection - Shared SQL connection
|
|
62
|
+
* @param fn - Stamp frame
|
|
63
|
+
*/
|
|
64
|
+
async function withRlsStampLock<T>(connection: SqlConnection, fn: () => Promise<T>): Promise<T> {
|
|
65
|
+
const prev = rlsStampTails.get(connection) ?? Promise.resolve();
|
|
66
|
+
let release!: () => void;
|
|
67
|
+
const gate = new Promise<void>((resolve) => {
|
|
68
|
+
release = resolve;
|
|
69
|
+
});
|
|
70
|
+
rlsStampTails.set(
|
|
71
|
+
connection,
|
|
72
|
+
prev.then(
|
|
73
|
+
() => gate,
|
|
74
|
+
() => gate,
|
|
75
|
+
),
|
|
76
|
+
);
|
|
77
|
+
await prev.catch(() => undefined);
|
|
78
|
+
try {
|
|
79
|
+
return await fn();
|
|
80
|
+
} finally {
|
|
81
|
+
release();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
31
85
|
/** Options for a SQL session. */
|
|
32
86
|
export interface SqlSessionOptions {
|
|
33
87
|
/** Open connection (already role-routed). */
|
|
@@ -44,50 +98,52 @@ export interface SqlSessionOptions {
|
|
|
44
98
|
* and local+autoPush). Default `ensure` for backward-compatible tests.
|
|
45
99
|
*/
|
|
46
100
|
readonly domainDdl?: DomainDdlMode;
|
|
101
|
+
/** Gate identity — stamps `oke.*` on postgres / pglite. */
|
|
102
|
+
readonly rls?: RlsIdentity;
|
|
47
103
|
}
|
|
48
104
|
|
|
49
105
|
/**
|
|
50
106
|
* Continuations after `.orderBy(...)` — awaitable, optional `.limit` /
|
|
51
107
|
* `.offset`.
|
|
52
108
|
*/
|
|
53
|
-
export interface SelectOrderBuilder extends PromiseLike<
|
|
109
|
+
export interface SelectOrderBuilder<TRow = SqlRow> extends PromiseLike<TRow[]> {
|
|
54
110
|
/**
|
|
55
111
|
* Cap the number of returned rows.
|
|
56
112
|
*
|
|
57
113
|
* @param n - Max rows
|
|
58
114
|
*/
|
|
59
|
-
limit(n: number): Promise<
|
|
115
|
+
limit(n: number): Promise<TRow[]>;
|
|
60
116
|
/**
|
|
61
117
|
* Skip the first `n` rows (`LIMIT … OFFSET …`).
|
|
62
118
|
*
|
|
63
119
|
* @param n - Rows to skip
|
|
64
120
|
*/
|
|
65
|
-
offset(n: number): Promise<
|
|
121
|
+
offset(n: number): Promise<TRow[]>;
|
|
66
122
|
}
|
|
67
123
|
|
|
68
124
|
/**
|
|
69
125
|
* Continuations after `.where(...)` on a select — awaitable, optional
|
|
70
126
|
* `.orderBy(...)` / `.limit(...)` / `.offset(...)`.
|
|
71
127
|
*/
|
|
72
|
-
export interface SelectWhereBuilder extends PromiseLike<
|
|
128
|
+
export interface SelectWhereBuilder<TRow = SqlRow> extends PromiseLike<TRow[]> {
|
|
73
129
|
/**
|
|
74
130
|
* Order rows with Drizzle `asc()` / `desc()` terms.
|
|
75
131
|
*
|
|
76
132
|
* @param orders - Order terms
|
|
77
133
|
*/
|
|
78
|
-
orderBy(...orders: readonly unknown[]): SelectOrderBuilder
|
|
134
|
+
orderBy(...orders: readonly unknown[]): SelectOrderBuilder<TRow>;
|
|
79
135
|
/**
|
|
80
136
|
* Cap the number of returned rows.
|
|
81
137
|
*
|
|
82
138
|
* @param n - Max rows
|
|
83
139
|
*/
|
|
84
|
-
limit(n: number): Promise<
|
|
140
|
+
limit(n: number): Promise<TRow[]>;
|
|
85
141
|
/**
|
|
86
142
|
* Skip the first `n` rows.
|
|
87
143
|
*
|
|
88
144
|
* @param n - Rows to skip
|
|
89
145
|
*/
|
|
90
|
-
offset(n: number): Promise<
|
|
146
|
+
offset(n: number): Promise<TRow[]>;
|
|
91
147
|
}
|
|
92
148
|
|
|
93
149
|
/**
|
|
@@ -95,42 +151,49 @@ export interface SelectWhereBuilder extends PromiseLike<SqlRow[]> {
|
|
|
95
151
|
* `.where(...)` / `.orderBy(...)` / `.limit(...)` / `.offset(...)` in any
|
|
96
152
|
* order.
|
|
97
153
|
*/
|
|
98
|
-
export interface SelectFromBuilder extends PromiseLike<
|
|
154
|
+
export interface SelectFromBuilder<TRow = SqlRow> extends PromiseLike<TRow[]> {
|
|
99
155
|
/**
|
|
100
156
|
* Filter with a plain equality map or a Drizzle SQL condition
|
|
101
157
|
* (`eq`, `and`, `or`, `lt`, `like`, …).
|
|
102
158
|
*
|
|
103
159
|
* @param where - Condition
|
|
104
160
|
*/
|
|
105
|
-
where(where: unknown): SelectWhereBuilder
|
|
161
|
+
where(where: unknown): SelectWhereBuilder<TRow>;
|
|
106
162
|
/**
|
|
107
163
|
* Order rows with Drizzle `asc()` / `desc()` terms.
|
|
108
164
|
*
|
|
109
165
|
* @param orders - Order terms
|
|
110
166
|
*/
|
|
111
|
-
orderBy(...orders: readonly unknown[]): SelectOrderBuilder
|
|
167
|
+
orderBy(...orders: readonly unknown[]): SelectOrderBuilder<TRow>;
|
|
112
168
|
/**
|
|
113
169
|
* Cap the number of returned rows.
|
|
114
170
|
*
|
|
115
171
|
* @param n - Max rows
|
|
116
172
|
*/
|
|
117
|
-
limit(n: number): Promise<
|
|
173
|
+
limit(n: number): Promise<TRow[]>;
|
|
118
174
|
/**
|
|
119
175
|
* Skip the first `n` rows.
|
|
120
176
|
*
|
|
121
177
|
* @param n - Rows to skip
|
|
122
178
|
*/
|
|
123
|
-
offset(n: number): Promise<
|
|
179
|
+
offset(n: number): Promise<TRow[]>;
|
|
124
180
|
}
|
|
125
181
|
|
|
126
|
-
/**
|
|
127
|
-
|
|
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> {
|
|
128
189
|
/**
|
|
129
190
|
* Choose the source table.
|
|
130
191
|
*
|
|
131
192
|
* @param table - Table handle or Drizzle table
|
|
132
193
|
*/
|
|
133
|
-
from(
|
|
194
|
+
from<TTable>(
|
|
195
|
+
table: TTable,
|
|
196
|
+
): SelectFromBuilder<TLocked extends undefined ? InferSelectRow<TTable> : TLocked>;
|
|
134
197
|
}
|
|
135
198
|
|
|
136
199
|
/** Fluent insert builder. */
|
|
@@ -197,12 +260,13 @@ export interface SqlStoreHandle {
|
|
|
197
260
|
/** Underlying driver id. */
|
|
198
261
|
readonly driverId: SqlConnection["driverId"];
|
|
199
262
|
/**
|
|
200
|
-
* Start a select.
|
|
201
|
-
* (`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}.
|
|
202
265
|
*
|
|
203
266
|
* @param columns - Optional `{ alias: drizzleColumn }` map
|
|
204
267
|
*/
|
|
205
|
-
select(
|
|
268
|
+
select(): SelectBuilder<undefined>;
|
|
269
|
+
select(columns: unknown): SelectBuilder<SqlRow>;
|
|
206
270
|
/**
|
|
207
271
|
* Start an insert into `table`.
|
|
208
272
|
*
|
|
@@ -339,8 +403,9 @@ export function createSqlStoreHandle(
|
|
|
339
403
|
ref: `sql:${string}`,
|
|
340
404
|
options: SqlSessionOptions,
|
|
341
405
|
): SqlStoreHandle {
|
|
342
|
-
const { connection, classifications, revealPii } = options;
|
|
406
|
+
const { connection, classifications, revealPii, rls } = options;
|
|
343
407
|
const domainDdl: DomainDdlMode = options.domainDdl ?? "ensure";
|
|
408
|
+
let helpersReady = false;
|
|
344
409
|
|
|
345
410
|
function mask(rows: SqlRow[], table?: string): SqlRow[] {
|
|
346
411
|
return maskRows(rows, { classifications, table, revealPii });
|
|
@@ -362,11 +427,41 @@ export function createSqlStoreHandle(
|
|
|
362
427
|
}
|
|
363
428
|
|
|
364
429
|
function query(sql: string, params: readonly unknown[] = []): Promise<SqlRow[]> {
|
|
365
|
-
return withSchemaGuard(() =>
|
|
430
|
+
return withSchemaGuard(() => withRlsStamp((conn) => conn.query(sql, params), sql));
|
|
366
431
|
}
|
|
367
432
|
|
|
368
433
|
function exec(sql: string, params: readonly unknown[] = []): Promise<{ changes: number }> {
|
|
369
|
-
return withSchemaGuard(() =>
|
|
434
|
+
return withSchemaGuard(() => withRlsStamp((conn) => conn.exec(sql, params), sql));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async function withRlsStamp<T>(fn: (conn: SqlConnection) => Promise<T>, sql: string): Promise<T> {
|
|
438
|
+
if (!rls || !RLS_CONTEXT_DRIVERS.has(connection.driverId)) return fn(connection);
|
|
439
|
+
if (isRlsStampExemptSql(sql)) return fn(connection);
|
|
440
|
+
const run = (): Promise<T> => applyRlsStamp(fn);
|
|
441
|
+
// PGlite is one backend session — concurrent identities must not interleave.
|
|
442
|
+
// Pooled postgres pins each stamp via `transaction()` instead.
|
|
443
|
+
if (connection.driverId === "pglite") {
|
|
444
|
+
return withRlsStampLock(connection, run);
|
|
445
|
+
}
|
|
446
|
+
return run();
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
async function applyRlsStamp<T>(fn: (conn: SqlConnection) => Promise<T>): Promise<T> {
|
|
450
|
+
if (!rls) return fn(connection);
|
|
451
|
+
if (!helpersReady) {
|
|
452
|
+
await installOkeRlsHelpers((sql) => connection.exec(sql));
|
|
453
|
+
helpersReady = true;
|
|
454
|
+
}
|
|
455
|
+
const frame = async (tx: SqlConnection): Promise<T> => {
|
|
456
|
+
for (const stmt of buildRlsIdentityPreludeSql(rls)) {
|
|
457
|
+
await tx.exec(stmt.sql, stmt.params ?? []);
|
|
458
|
+
}
|
|
459
|
+
return fn(tx);
|
|
460
|
+
};
|
|
461
|
+
if (!connection.transaction) {
|
|
462
|
+
throw new Error("RLS stamp needs SqlConnection.transaction to pin SET LOCAL");
|
|
463
|
+
}
|
|
464
|
+
return connection.transaction(frame);
|
|
370
465
|
}
|
|
371
466
|
|
|
372
467
|
async function ensureFromMeta(table: TableHandle | unknown): Promise<void> {
|
|
@@ -522,20 +617,20 @@ export function createSqlStoreHandle(
|
|
|
522
617
|
};
|
|
523
618
|
}
|
|
524
619
|
|
|
525
|
-
const handle
|
|
620
|
+
const handle = {
|
|
526
621
|
ref,
|
|
527
622
|
routedRole: options.routedRole,
|
|
528
623
|
driverId: connection.driverId,
|
|
529
624
|
|
|
530
|
-
select(columns
|
|
625
|
+
select(columns?: unknown) {
|
|
531
626
|
return {
|
|
532
|
-
from(table) {
|
|
627
|
+
from(table: TableHandle | unknown) {
|
|
533
628
|
return selectFrom(table, columns);
|
|
534
629
|
},
|
|
535
630
|
};
|
|
536
631
|
},
|
|
537
632
|
|
|
538
|
-
insert(table): InsertBuilder {
|
|
633
|
+
insert(table: TableHandle | unknown): InsertBuilder {
|
|
539
634
|
const name = resolveTableName(table);
|
|
540
635
|
return {
|
|
541
636
|
values(row) {
|
|
@@ -570,7 +665,7 @@ export function createSqlStoreHandle(
|
|
|
570
665
|
};
|
|
571
666
|
},
|
|
572
667
|
|
|
573
|
-
update(table): UpdateBuilder {
|
|
668
|
+
update(table: TableHandle | unknown): UpdateBuilder {
|
|
574
669
|
const name = resolveTableName(table);
|
|
575
670
|
return {
|
|
576
671
|
set(row) {
|
|
@@ -597,7 +692,7 @@ export function createSqlStoreHandle(
|
|
|
597
692
|
};
|
|
598
693
|
},
|
|
599
694
|
|
|
600
|
-
async findById(table, idValue) {
|
|
695
|
+
async findById(table: TableHandle | unknown, idValue: string) {
|
|
601
696
|
await ensureFromMeta(table);
|
|
602
697
|
const name = resolveTableName(table);
|
|
603
698
|
const pk = resolvePkColumn(table);
|
|
@@ -638,7 +733,7 @@ export function createSqlStoreHandle(
|
|
|
638
733
|
return builder;
|
|
639
734
|
}) as SqlStoreHandle["delete"],
|
|
640
735
|
|
|
641
|
-
async exists(table, idOrWhere) {
|
|
736
|
+
async exists(table: TableHandle | unknown, idOrWhere: string | WhereMap) {
|
|
642
737
|
await ensureFromMeta(table);
|
|
643
738
|
const name = resolveTableName(table);
|
|
644
739
|
const where: WhereMap =
|
|
@@ -656,7 +751,12 @@ export function createSqlStoreHandle(
|
|
|
656
751
|
return rows.length > 0;
|
|
657
752
|
},
|
|
658
753
|
|
|
659
|
-
async upsert(
|
|
754
|
+
async upsert(
|
|
755
|
+
table: TableHandle | unknown,
|
|
756
|
+
matchOn: WhereMap | unknown,
|
|
757
|
+
values: SqlRow,
|
|
758
|
+
upsertOptions?: { readonly onExisting?: "update" },
|
|
759
|
+
) {
|
|
660
760
|
await ensureFromMeta(table);
|
|
661
761
|
const name = resolveTableName(table);
|
|
662
762
|
const compiled = compileTableWhere(table, matchOn);
|
|
@@ -688,7 +788,7 @@ export function createSqlStoreHandle(
|
|
|
688
788
|
return { status: "changed" as const };
|
|
689
789
|
},
|
|
690
790
|
|
|
691
|
-
async increment(table, idValue, column, by = 1) {
|
|
791
|
+
async increment(table: TableHandle | unknown, idValue: string, column: string, by = 1) {
|
|
692
792
|
await ensureFromMeta(table);
|
|
693
793
|
const name = resolveTableName(table);
|
|
694
794
|
const pk = resolvePkColumn(table);
|
|
@@ -707,7 +807,7 @@ export function createSqlStoreHandle(
|
|
|
707
807
|
return asNumber(row[sqlCol], sqlCol);
|
|
708
808
|
},
|
|
709
809
|
|
|
710
|
-
async raw(sql, params = []) {
|
|
810
|
+
async raw(sql: string, params: readonly unknown[] = []) {
|
|
711
811
|
const table = tableFromSql(sql);
|
|
712
812
|
// Memory SQL (console-next seed) implements DML on `exec`, not `query`.
|
|
713
813
|
// SELECT / RETURNING still go through `query` so callers get rows.
|
|
@@ -719,7 +819,7 @@ export function createSqlStoreHandle(
|
|
|
719
819
|
return mask(rows, table);
|
|
720
820
|
},
|
|
721
821
|
|
|
722
|
-
async count(table, where) {
|
|
822
|
+
async count(table: TableHandle | unknown, where?: unknown) {
|
|
723
823
|
await ensureFromMeta(table);
|
|
724
824
|
const name = resolveTableName(table);
|
|
725
825
|
let sql = `SELECT COUNT(*) AS "count" FROM ${quoteIdent(name)}`;
|
|
@@ -735,7 +835,7 @@ export function createSqlStoreHandle(
|
|
|
735
835
|
return asNumber(rows[0]?.count ?? rows[0]?.COUNT ?? 0, "count");
|
|
736
836
|
},
|
|
737
837
|
|
|
738
|
-
async page(table, options) {
|
|
838
|
+
async page(table: TableHandle | unknown, options: SqlPageOptions) {
|
|
739
839
|
if (options.after !== undefined && options.before !== undefined) {
|
|
740
840
|
throw new Error("page(): after and before (keyset) cannot combine");
|
|
741
841
|
}
|
|
@@ -760,7 +860,7 @@ export function createSqlStoreHandle(
|
|
|
760
860
|
});
|
|
761
861
|
},
|
|
762
862
|
|
|
763
|
-
async ensureTable(table) {
|
|
863
|
+
async ensureTable(table: TableHandle) {
|
|
764
864
|
const cols = Object.values(table.columns);
|
|
765
865
|
const pk = resolvePkColumn(table);
|
|
766
866
|
const intType =
|
|
@@ -785,7 +885,7 @@ export function createSqlStoreHandle(
|
|
|
785
885
|
},
|
|
786
886
|
};
|
|
787
887
|
|
|
788
|
-
return handle;
|
|
888
|
+
return handle as SqlStoreHandle;
|
|
789
889
|
}
|
|
790
890
|
|
|
791
891
|
/**
|
|
@@ -45,7 +45,7 @@ describe("fx.store(db).upsert via app.fetch (real boot)", () => {
|
|
|
45
45
|
const db = store.sql("app", { schema: { notes } });
|
|
46
46
|
|
|
47
47
|
const seed = on(
|
|
48
|
-
http.post("/seed").
|
|
48
|
+
http.post("/seed").public(),
|
|
49
49
|
flow("notes.seed", {
|
|
50
50
|
in: UpsertIn,
|
|
51
51
|
out: UpsertOut,
|
package/src/elements/store.ts
CHANGED
|
@@ -26,6 +26,11 @@ export {
|
|
|
26
26
|
schema,
|
|
27
27
|
schemaTable,
|
|
28
28
|
schemaRelations,
|
|
29
|
+
schemaRls,
|
|
30
|
+
schemaPolicy,
|
|
31
|
+
schemaPolicyGate,
|
|
32
|
+
schemaPolicyOwner,
|
|
33
|
+
schemaPolicyScope,
|
|
29
34
|
isSchemaTableDecl,
|
|
30
35
|
isSchemaColumnDecl,
|
|
31
36
|
isSchemaRelationsDecl,
|
|
@@ -42,7 +47,11 @@ export type {
|
|
|
42
47
|
SchemaColumnInput,
|
|
43
48
|
SchemaTableDecl,
|
|
44
49
|
SchemaTableWithColumns,
|
|
50
|
+
InferColumnJs,
|
|
51
|
+
FinalizeColumn,
|
|
45
52
|
SchemaRelationsDecl,
|
|
53
|
+
SchemaPolicyDecl,
|
|
54
|
+
SchemaTableExtra,
|
|
46
55
|
SchemaRelationEntry,
|
|
47
56
|
SchemaRelationOne,
|
|
48
57
|
SchemaRelationMany,
|
|
@@ -114,6 +123,7 @@ export type {
|
|
|
114
123
|
SelectFromBuilder,
|
|
115
124
|
SelectWhereBuilder,
|
|
116
125
|
SelectOrderBuilder,
|
|
126
|
+
InferSelectRow,
|
|
117
127
|
InsertBuilder,
|
|
118
128
|
InsertValuesBuilder,
|
|
119
129
|
SqlSessionOptions,
|
|
@@ -123,6 +133,13 @@ export type {
|
|
|
123
133
|
WhereMap,
|
|
124
134
|
} from "./store/sql-session.ts";
|
|
125
135
|
|
|
136
|
+
export {
|
|
137
|
+
resolveRlsIdentity,
|
|
138
|
+
rlsIdentityFromAuth,
|
|
139
|
+
firstPolicyOrPublic,
|
|
140
|
+
policyGateForScopes,
|
|
141
|
+
} from "./store/rls-identity.ts";
|
|
142
|
+
export type { RlsIdentityRow, ResolveRlsIdentityInput } from "./store/rls-identity.ts";
|
|
126
143
|
export { createStoreRuntime, putTier2 } from "./store/runtime.ts";
|
|
127
144
|
export type {
|
|
128
145
|
StoreRuntime,
|
package/src/http.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* ```ts
|
|
9
9
|
* import { on, flow, http, gate, oke, createBunRuntime } from "okengine/http";
|
|
10
10
|
*
|
|
11
|
-
* on(http.get("/").
|
|
11
|
+
* on(http.get("/").public(), flow("ping", { do: () => "Hi" }));
|
|
12
12
|
* const app = oke({ name: "ping" });
|
|
13
13
|
* createBunRuntime().serve(app);
|
|
14
14
|
* ```
|
|
@@ -24,15 +24,32 @@ export { fail, type FlowFailure } from "./kernel/errors.ts";
|
|
|
24
24
|
export { isFlowFailure } from "./kernel/hooks.ts";
|
|
25
25
|
export { plugin, isPlugin, type PluginDef } from "./kernel/plugin.ts";
|
|
26
26
|
export { createBunRuntime, APP_PORT, type Runtime, type ServeOptions } from "./runtime/index.ts";
|
|
27
|
-
export type {
|
|
27
|
+
export type {
|
|
28
|
+
OkeApp,
|
|
29
|
+
OkeOptions,
|
|
30
|
+
ReadyState,
|
|
31
|
+
RegisteredFlowUnits,
|
|
32
|
+
RoutesFromRegisteredUnits,
|
|
33
|
+
} from "./kernel/app.ts";
|
|
34
|
+
export { registerFlowUnits } from "./kernel/flow-units.ts";
|
|
35
|
+
export { stampFlowName, stampHttpPath } from "./kernel/stamp-http.ts";
|
|
28
36
|
|
|
29
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
oke as okeCore,
|
|
39
|
+
type OkeApp,
|
|
40
|
+
type OkeOptions,
|
|
41
|
+
type RoutesFromRegisteredUnits,
|
|
42
|
+
} from "./kernel/app.ts";
|
|
30
43
|
|
|
31
44
|
/**
|
|
32
45
|
* Create an HTTP-oriented app — defaults `router` to `"edge"` when omitted.
|
|
33
46
|
*
|
|
34
47
|
* @param options - Application options
|
|
35
48
|
*/
|
|
49
|
+
export function oke(
|
|
50
|
+
options: OkeOptions & { readonly registry: "ignore" },
|
|
51
|
+
): OkeApp<Record<string, never>, Record<string, never>>;
|
|
52
|
+
export function oke(options: OkeOptions): OkeApp<{}, RoutesFromRegisteredUnits>;
|
|
36
53
|
export function oke(options: OkeOptions): OkeApp {
|
|
37
54
|
return okeCore({
|
|
38
55
|
...options,
|
package/src/index.ts
CHANGED
|
@@ -14,10 +14,19 @@
|
|
|
14
14
|
* @module
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
export {
|
|
17
|
+
export {
|
|
18
|
+
oke,
|
|
19
|
+
type OkeApp,
|
|
20
|
+
type OkeOptions,
|
|
21
|
+
type ReadyState,
|
|
22
|
+
type RegisteredFlowUnits,
|
|
23
|
+
type RoutesFromRegisteredUnits,
|
|
24
|
+
} from "./kernel/app.ts";
|
|
18
25
|
export { on, type Binding } from "./kernel/on.ts";
|
|
19
26
|
export { flow, isFlow, type FlowDef } from "./kernel/flow.ts";
|
|
20
27
|
export { http, every, internal, table } from "./kernel/triggers.ts";
|
|
28
|
+
export { registerFlowUnits } from "./kernel/flow-units.ts";
|
|
29
|
+
export { stampFlowName, stampHttpPath } from "./kernel/stamp-http.ts";
|
|
21
30
|
export { fail, type FlowFailure, type FlowErrorValue } from "./kernel/errors.ts";
|
|
22
31
|
export { isFlowFailure } from "./kernel/hooks.ts";
|
|
23
32
|
export { plugin, isPlugin, type PluginDef, type PluginCapabilities } from "./kernel/plugin.ts";
|
|
@@ -53,6 +62,7 @@ export {
|
|
|
53
62
|
type StoreDecl,
|
|
54
63
|
type StoreRuntime,
|
|
55
64
|
type SqlStoreHandle,
|
|
65
|
+
type InferSelectRow,
|
|
56
66
|
type TableHandle,
|
|
57
67
|
type SchemaTableDecl,
|
|
58
68
|
type SchemaColumnDecl,
|