okengine 0.10.2 → 0.11.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 +76 -10
- package/site/content/docs/ai/mcp.mdx +0 -1
- package/site/content/docs/deployment/docker-swarm.mdx +48 -111
- package/site/content/docs/deployment/docker.mdx +43 -36
- package/site/content/docs/deployment/index.mdx +9 -9
- package/site/content/docs/deployment/kubernetes.mdx +4 -4
- package/site/content/docs/elements/ai.mdx +7 -13
- package/site/content/docs/elements/channel.mdx +9 -13
- package/site/content/docs/elements/clock.mdx +8 -14
- package/site/content/docs/elements/flow.mdx +46 -54
- package/site/content/docs/elements/gate.mdx +6 -11
- package/site/content/docs/elements/signal.mdx +2 -8
- package/site/content/docs/elements/store.mdx +79 -93
- package/site/content/docs/elements/vault.mdx +89 -76
- package/site/content/docs/get-started/basic-usage.mdx +8 -14
- package/site/content/docs/get-started/installation.mdx +36 -36
- package/site/content/docs/get-started/introduction.mdx +11 -0
- package/site/content/docs/get-started/why.mdx +3 -4
- package/site/content/docs/index.mdx +0 -1
- package/site/content/docs/meta.json +0 -1
- package/site/content/docs/plugins/anonymous.mdx +1 -1
- package/site/content/docs/plugins/magic-link.mdx +11 -11
- package/site/content/docs/plugins/otp.mdx +2 -2
- package/site/content/docs/plugins/passkey.mdx +1 -1
- package/site/content/docs/plugins/two-factor.mdx +1 -1
- package/site/content/docs/plugins/username.mdx +1 -1
- package/site/content/docs/recipes/caddy.mdx +1 -1
- package/site/content/docs/recipes/cockroachdb.mdx +2 -2
- package/site/content/docs/recipes/dragonfly.mdx +1 -1
- package/site/content/docs/recipes/index.mdx +1 -2
- package/site/content/docs/recipes/llama-cpp.mdx +21 -23
- package/site/content/docs/recipes/mailpit.mdx +11 -11
- package/site/content/docs/recipes/meilisearch.mdx +10 -10
- package/site/content/docs/recipes/meta.json +0 -1
- package/site/content/docs/recipes/ollama.mdx +1 -1
- package/site/content/docs/recipes/pgdog.mdx +12 -9
- package/site/content/docs/recipes/postgres.mdx +3 -3
- package/site/content/docs/recipes/redis.mdx +2 -2
- package/site/content/docs/recipes/rustfs.mdx +10 -7
- package/site/content/docs/recipes/sglang.mdx +1 -1
- package/site/content/docs/recipes/supabase-docker.mdx +1 -1
- package/site/content/docs/recipes/timescale.mdx +2 -2
- package/site/content/docs/recipes/valkey.mdx +1 -1
- package/site/content/docs/recipes/vllm.mdx +1 -1
- package/site/content/docs/recipes/yugabytedb.mdx +2 -2
- package/site/content/docs/reference/cli.md +61 -38
- package/site/content/docs/reference/configuration.mdx +145 -70
- package/site/content/docs/reference/environment-variables.mdx +36 -28
- package/site/content/docs/reference/errors.mdx +28 -29
- package/site/content/docs/reference/fx.mdx +14 -7
- package/site/content/docs/reference/i18n.mdx +17 -6
- package/site/content/docs/reference/index.mdx +5 -0
- package/site/content/docs/reference/meta.json +1 -0
- package/site/content/docs/reference/migrating-environments.mdx +158 -0
- package/site/content/docs/reference/plugins.mdx +2 -2
- package/src/auth/gate-auth.test.ts +12 -10
- package/src/cli/ai-setup/ai-setup.test.ts +30 -16
- package/src/cli/ai-setup/apply.ts +111 -15
- package/src/cli/ai-setup/catalog.ts +3 -3
- package/src/cli/ai-setup/index.ts +1 -1
- package/src/cli/ai-setup/prompts.ts +1 -1
- package/src/cli/ai-setup/recommend.test.ts +1 -1
- package/src/cli/ask-seed.test.ts +3 -3
- package/src/cli/build.ts +8 -9
- package/src/cli/db-auto-push.test.ts +6 -3
- package/src/cli/db-auto-push.ts +7 -5
- package/src/cli/db-seed.ts +7 -8
- package/src/cli/db.test.ts +70 -71
- package/src/cli/db.ts +86 -12
- package/src/cli/dev-controller.test.ts +35 -0
- package/src/cli/dev-controller.ts +298 -0
- package/src/cli/dev-controls.ts +11 -65
- package/src/cli/dev-db-push.test.ts +47 -15
- package/src/cli/dev-schema-sync.test.ts +11 -11
- package/src/cli/dev-schema-sync.ts +10 -10
- package/src/cli/dev-session-lock.test.ts +106 -0
- package/src/cli/dev-session-lock.ts +209 -0
- package/src/cli/dev.test.ts +246 -114
- package/src/cli/dev.ts +153 -205
- package/src/cli/doc-staleness.test.ts +3 -6
- package/src/cli/docker-clean.test.ts +33 -1
- package/src/cli/docker-clean.ts +28 -33
- package/src/cli/docker-cli.test.ts +12 -11
- package/src/cli/docker.ts +95 -20
- package/src/cli/doctor.test.ts +40 -0
- package/src/cli/doctor.ts +142 -9
- package/src/cli/drizzle-env.test.ts +11 -13
- package/src/cli/drizzle-env.ts +18 -11
- package/src/cli/ensure-drizzle-config.ts +11 -14
- package/src/cli/hero-meta.test.ts +15 -15
- package/src/cli/hero-meta.ts +10 -9
- package/src/cli/index.ts +36 -6
- package/src/cli/load-config.images.test.ts +44 -10
- package/src/cli/load-config.source.test.ts +50 -0
- package/src/cli/load-config.ts +35 -29
- package/src/cli/meilisearch-local.ts +1 -2
- package/src/cli/mode.ts +7 -58
- package/src/cli/registry.ts +175 -19
- package/src/cli/resolve-dev-sql-env.test.ts +8 -16
- package/src/cli/resolve-dev-sql-env.ts +13 -15
- package/src/cli/safe-defaults.test.ts +29 -11
- package/src/cli/schema.ts +65 -16
- package/src/cli/test.ts +58 -0
- package/src/cli/tui/App.tsx +252 -0
- package/src/cli/tui/DevLive.tsx +116 -0
- package/src/cli/tui/components/ConfirmDialog.tsx +48 -0
- package/src/cli/tui/components/HeaderBar.tsx +67 -0
- package/src/cli/tui/components/LogStream.tsx +47 -0
- package/src/cli/tui/components/PanelCard.tsx +39 -0
- package/src/cli/tui/components/SelectRow.tsx +37 -0
- package/src/cli/tui/components/SlashPalette.tsx +172 -0
- package/src/cli/tui/components/Spinner.tsx +32 -0
- package/src/cli/tui/components/StatusBar.tsx +51 -0
- package/src/cli/tui/components/StatusDot.tsx +37 -0
- package/src/cli/tui/components/TabBar.tsx +49 -0
- package/src/cli/tui/components/Wordmark.tsx +43 -0
- package/src/cli/tui/format.ts +38 -0
- package/src/cli/tui/keys.test.ts +18 -0
- package/src/cli/tui/keys.ts +26 -0
- package/src/cli/tui/launch.test.ts +18 -0
- package/src/cli/tui/launch.ts +57 -0
- package/src/cli/tui/panels/Dashboard.tsx +201 -0
- package/src/cli/tui/panels/Database.tsx +198 -0
- package/src/cli/tui/panels/Dev.tsx +257 -0
- package/src/cli/tui/panels/Docker.tsx +357 -0
- package/src/cli/tui/panels/Navigator.test.ts +22 -0
- package/src/cli/tui/panels/Navigator.tsx +213 -0
- package/src/cli/tui/slash-catalog.test.ts +31 -0
- package/src/cli/tui/slash-catalog.ts +184 -0
- package/src/cli/tui/slash-run.ts +54 -0
- package/src/cli/tui/state/ports.ts +95 -0
- package/src/cli/tui/theme.ts +24 -0
- package/src/cli/vault-cmd.test.ts +402 -0
- package/src/cli/vault-cmd.ts +727 -69
- package/src/cli/vault-secure-input.test.ts +56 -0
- package/src/cli/vault-secure-input.ts +124 -0
- package/src/compiler/effects-infer.ts +7 -0
- package/src/compiler/extract.ts +12 -0
- package/src/compiler/fixtures/skyport/oke.config.ts +7 -5
- package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +1 -1
- package/src/compiler/fixtures/skyport.expected.json +1 -1
- package/src/compiler/generate-adopt.ts +27 -1
- package/src/compiler-entry.ts +7 -0
- package/src/config/define-config.test.ts +111 -47
- package/src/config/driver-defaults.test.ts +115 -0
- package/src/config/driver-defaults.ts +156 -0
- package/src/config/index.ts +246 -131
- package/src/config/resolve-driver.test.ts +14 -19
- package/src/console/server/app.ts +4 -2
- package/src/console/server/console.test.ts +58 -1
- package/src/console/server/flows.ts +22 -2
- package/src/console/server/index.ts +3 -0
- package/src/console/server/operator-db.test.ts +55 -13
- package/src/console/server/operator-db.ts +305 -263
- package/src/console/server/serve.ts +16 -7
- package/src/console/server/state.ts +31 -9
- package/src/console/server/vault.test.ts +120 -5
- package/src/console/server/vault.ts +208 -12
- package/src/console/ui/dist/assets/{index-D-9KFce5.js → index--Y0Uaz-m.js} +2 -2
- package/src/console/ui/dist/assets/panel-vault-BkZdJPX8.js +1 -0
- package/src/console/ui/dist/assets/style-C3L349iZ.css +3 -0
- package/src/console/ui/dist/index.html +2 -2
- package/src/console/ui/shell/client.ts +16 -0
- package/src/console/ui/shell/panels/vault/VaultPanel.tsx +54 -1
- package/src/console/ui/vault/VaultA11yView.tsx +33 -1
- package/src/console/ui/vault/backend.test.ts +75 -0
- package/src/console/ui/vault/backend.ts +117 -0
- package/src/console/ui/vault/fixture.ts +21 -2
- package/src/console/ui/vault/index.ts +11 -1
- package/src/console/ui/vault/types.ts +26 -0
- package/src/docker/compose.ts +164 -61
- package/src/docker/derive.ts +106 -12
- package/src/docker/docker.test.ts +156 -110
- package/src/docker/helpers.ts +98 -0
- package/src/docker/images-config.test.ts +163 -0
- package/src/docker/index.ts +30 -5
- package/src/docker/recipes/index.ts +0 -3
- package/src/docker/recipes/llama-cpp.ts +4 -4
- package/src/docker/recipes/pgdog.ts +9 -2
- package/src/docker/resources.test.ts +71 -0
- package/src/docker/resources.ts +176 -0
- package/src/docker/stack-id.test.ts +11 -4
- package/src/docker/stack-id.ts +42 -32
- package/src/docker/stack.integration.test.ts +4 -15
- package/src/docker/types.ts +35 -3
- package/src/drivers/ai-ollama.integration.test.ts +2 -2
- package/src/drivers/conformance.test.ts +11 -12
- package/src/drivers/drizzle-dialect.test.ts +0 -4
- package/src/drivers/drizzle-dialect.ts +7 -13
- package/src/drivers/index.ts +24 -7
- package/src/drivers/journal-postgres.test.ts +3 -1
- package/src/drivers/journal-postgres.ts +4 -2
- package/src/drivers/meilisearch.ts +1 -1
- package/src/drivers/pglite.ts +28 -5
- package/src/drivers/types.ts +9 -11
- package/src/drivers/vault-aws-secrets-manager.test.ts +159 -0
- package/src/drivers/vault-aws-secrets-manager.ts +333 -0
- package/src/drivers/vault-builtin.test.ts +110 -0
- package/src/drivers/vault-builtin.ts +184 -0
- package/src/drivers/vault-driver-removal.test.ts +13 -0
- package/src/drivers/vault-managed.test.ts +95 -0
- package/src/drivers/vault-managed.ts +101 -25
- package/src/drivers/vault-types.ts +25 -5
- package/src/elements/ai/runtime.ts +1 -1
- package/src/elements/clock/chaos.test.ts +4 -1
- package/src/elements/clock/durable.ts +57 -4
- package/src/elements/clock.test.ts +13 -14
- package/src/elements/gate/constants.ts +18 -0
- package/src/elements/gate.test.ts +1 -1
- package/src/elements/gate.ts +2 -2
- package/src/elements/store/domain-ddl.test.ts +5 -12
- package/src/elements/store/emit-drizzle.ts +44 -19
- package/src/elements/store/index-boot.test.ts +15 -116
- package/src/elements/store/load-plugin-tables.ts +1 -1
- package/src/elements/store/resource-list-docs.fixture.ts +12 -6
- package/src/elements/store/resource-list-docs.test.ts +64 -44
- package/src/elements/store/resource.test.ts +17 -10
- package/src/elements/store/runtime.ts +9 -12
- package/src/elements/store/schema-decl.test.ts +16 -32
- package/src/elements/store/seed.test.ts +1 -2
- package/src/elements/store/seed.ts +7 -8
- package/src/elements/store/sql-condition.test.ts +2 -2
- package/src/elements/store/sql-session.test.ts +8 -10
- package/src/elements/store/sql-session.ts +10 -2
- package/src/elements/store.test.ts +13 -13
- package/src/elements/vault/audit.ts +233 -0
- package/src/elements/vault/boot-chain.test.ts +78 -0
- package/src/elements/vault/boot-chain.ts +43 -39
- package/src/elements/vault/builtin-adapter.test.ts +479 -0
- package/src/elements/vault/builtin-adapter.ts +1184 -0
- package/src/elements/vault/crypto.test.ts +237 -0
- package/src/elements/vault/crypto.ts +431 -0
- package/src/elements/vault/declare.ts +164 -3
- package/src/elements/vault/errors.ts +56 -0
- package/src/elements/vault/kms-unseal.test.ts +76 -0
- package/src/elements/vault/kms-unseal.ts +181 -0
- package/src/elements/vault/path.ts +90 -0
- package/src/elements/vault/resilience.test.ts +102 -0
- package/src/elements/vault/resilience.ts +107 -0
- package/src/elements/vault/runtime.ts +36 -4
- package/src/elements/vault/security-checklist.test.ts +130 -0
- package/src/elements/vault/storage.ts +448 -0
- package/src/elements/vault/types.ts +288 -0
- package/src/elements/vault/unseal.ts +150 -0
- package/src/elements/vault.test.ts +72 -4
- package/src/elements/vault.ts +91 -2
- package/src/full.ts +248 -0
- package/src/http.ts +41 -0
- package/src/i18n/interpolate-simple.ts +37 -0
- package/src/i18n/messages.ts +51 -4
- package/src/i18n-entry.ts +7 -0
- package/src/index.ts +35 -145
- package/src/journal-entry.ts +20 -0
- package/src/kernel/adopt-barrel-fresh.test.ts +25 -10
- package/src/kernel/app.ts +40 -42
- package/src/kernel/auto-registry.test.ts +2 -2
- package/src/kernel/boot-bind/ai.test.ts +5 -5
- package/src/kernel/boot-bind/ai.ts +2 -1
- package/src/kernel/boot-bind/channel.test.ts +6 -6
- package/src/kernel/boot-bind/channel.ts +5 -4
- package/src/kernel/boot-bind/clock.ts +5 -5
- package/src/kernel/boot-bind/gate.ts +6 -7
- package/src/kernel/boot-bind/honor-config.test.ts +42 -42
- package/src/kernel/boot-bind/journal.ts +5 -5
- package/src/kernel/boot-bind/signal.ts +3 -1
- package/src/kernel/boot-bind/store.test.ts +55 -37
- package/src/kernel/boot-bind/store.ts +24 -34
- package/src/kernel/boot-bind/vault.ts +19 -4
- package/src/kernel/boot.test.ts +12 -4
- package/src/kernel/boot.ts +31 -19
- package/src/kernel/budget-entry.ts +1 -1
- package/src/kernel/compensate.test.ts +288 -1
- package/src/kernel/compensate.ts +126 -0
- package/src/kernel/concurrency.test.ts +10 -9
- package/src/kernel/effects-stamping.test.ts +27 -2
- package/src/kernel/element-registries.ts +5 -3
- package/src/kernel/flow.ts +4 -2
- package/src/kernel/fx.test.ts +119 -3
- package/src/kernel/fx.ts +215 -40
- package/src/kernel/index.ts +14 -0
- package/src/kernel/journal.ts +130 -7
- package/src/kernel/pipeline.test.ts +20 -6
- package/src/kernel/redacted.test.ts +35 -0
- package/src/kernel/redacted.ts +24 -2
- package/src/kernel/router/create-default.ts +16 -0
- package/src/kernel/router/create-edge.ts +18 -0
- package/src/kernel/router/create.ts +23 -0
- package/src/kernel/router/index.ts +23 -0
- package/src/kernel/router/linear.ts +88 -0
- package/src/kernel/router/regexp.ts +188 -0
- package/src/kernel/router/smart.ts +120 -0
- package/src/kernel/router/trie.ts +108 -0
- package/src/kernel/router/types.ts +56 -0
- package/src/kernel/router.ts +18 -532
- package/src/kernel-entry.ts +43 -0
- package/src/manifest/fixtures/skyport.excerpt.json +1 -1
- package/src/manifest/fixtures/skyport.manifest.json +1 -1
- package/src/plugins/config-source.test.ts +7 -7
- package/src/release/build-lib.ts +58 -0
- package/src/release/exports.test.ts +4 -2
- package/src/release/exports.ts +30 -4
- package/src/release/measure.ts +101 -22
- package/src/runs/duckdb.ts +7 -2
- package/src/shared/optional-peer.ts +41 -0
- package/src/term.test.ts +3 -44
- package/src/test/create-test-app.ts +12 -0
- package/src/test/provisions.integration.test.ts +1 -1
- package/src/test/reset-element-registries.ts +2 -0
- package/src/upgrade/codemods.test.ts +7 -20
- package/src/upgrade/codemods.ts +93 -60
- package/site/content/docs/console/access.mdx +0 -29
- package/site/content/docs/console/ai.mdx +0 -35
- package/site/content/docs/console/architecture.mdx +0 -35
- package/site/content/docs/console/channels.mdx +0 -37
- package/site/content/docs/console/clock.mdx +0 -51
- package/site/content/docs/console/flows.mdx +0 -31
- package/site/content/docs/console/gates.mdx +0 -73
- package/site/content/docs/console/index.mdx +0 -54
- package/site/content/docs/console/manifest-diff.mdx +0 -34
- package/site/content/docs/console/meta.json +0 -24
- package/site/content/docs/console/overview.mdx +0 -40
- package/site/content/docs/console/plugins.mdx +0 -41
- package/site/content/docs/console/privacy.mdx +0 -32
- package/site/content/docs/console/runs.mdx +0 -50
- package/site/content/docs/console/signals.mdx +0 -40
- package/site/content/docs/console/store.mdx +0 -32
- package/site/content/docs/console/tenancy.mdx +0 -32
- package/site/content/docs/console/traces.mdx +0 -38
- package/site/content/docs/console/vault.mdx +0 -41
- package/site/content/docs/recipes/openbao.mdx +0 -141
- package/src/cli/ask-dev-mode.ts +0 -40
- package/src/cli/dev-mode.test.ts +0 -45
- package/src/cli/dev-mode.ts +0 -78
- package/src/cli/openbao-bootstrap.test.ts +0 -150
- package/src/cli/openbao-bootstrap.ts +0 -321
- package/src/cli/openbao-restart.integration.test.ts +0 -145
- package/src/console/ui/dist/assets/panel-vault-Za1GyJfM.js +0 -1
- package/src/console/ui/dist/assets/style-C8MxEWPd.css +0 -3
- package/src/docker/recipes/openbao.ts +0 -47
- package/src/drivers/libsql.ts +0 -179
- package/src/drivers/sqlite.ts +0 -44
- package/src/drivers/vault-openbao.test.ts +0 -97
- package/src/drivers/vault-openbao.ts +0 -143
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gate rate-limit constants — kept free of strategy / kv-lua imports so
|
|
3
|
+
* `gate.public` declaration does not pull Lua registration into HTTP-only apps.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { RateStrategy } from "../../manifest/types.ts";
|
|
7
|
+
|
|
8
|
+
/** Default rate strategy (unified-theory §16). */
|
|
9
|
+
export const DEFAULT_RATE_STRATEGY: RateStrategy = "sliding-window-counter";
|
|
10
|
+
|
|
11
|
+
/** All five strategy ids. */
|
|
12
|
+
export const ALL_RATE_STRATEGIES: readonly RateStrategy[] = [
|
|
13
|
+
"fixed-window",
|
|
14
|
+
"sliding-window-counter",
|
|
15
|
+
"sliding-log",
|
|
16
|
+
"token-bucket",
|
|
17
|
+
"leaky-bucket",
|
|
18
|
+
];
|
|
@@ -177,7 +177,7 @@ describe("gate boot posture", () => {
|
|
|
177
177
|
});
|
|
178
178
|
|
|
179
179
|
test("unguardedHttp allow has no effect outside env === test", () => {
|
|
180
|
-
for (const env of ["
|
|
180
|
+
for (const env of ["dev", "prod"] as const) {
|
|
181
181
|
expect(() => assertHttpGatePosture(unguarded, { unguardedHttp: "allow", env })).toThrow(
|
|
182
182
|
GateBootError,
|
|
183
183
|
);
|
package/src/elements/gate.ts
CHANGED
|
@@ -45,11 +45,11 @@ export {
|
|
|
45
45
|
type GateRuntime,
|
|
46
46
|
} from "./gate/runtime.ts";
|
|
47
47
|
|
|
48
|
+
export { ALL_RATE_STRATEGIES, DEFAULT_RATE_STRATEGY } from "./gate/constants.ts";
|
|
49
|
+
|
|
48
50
|
export {
|
|
49
51
|
takeRate,
|
|
50
52
|
luaForStrategy,
|
|
51
|
-
ALL_RATE_STRATEGIES,
|
|
52
|
-
DEFAULT_RATE_STRATEGY,
|
|
53
53
|
RATE_STRATEGIES,
|
|
54
54
|
type RateTakeResult,
|
|
55
55
|
type TakeRateOptions,
|
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { describe, expect, test } from "bun:test";
|
|
6
|
-
import { Database } from "bun:sqlite";
|
|
7
6
|
import { OkeError, OKE_ERRORS } from "../../kernel/errors.ts";
|
|
8
|
-
import {
|
|
7
|
+
import { memorySqlDriver } from "../../drivers/memory.ts";
|
|
9
8
|
import { defineTable } from "./table.ts";
|
|
10
9
|
import { createSqlStoreHandle } from "./sql-session.ts";
|
|
11
10
|
|
|
@@ -13,8 +12,7 @@ const notes = defineTable("notes", { id: true, title: true });
|
|
|
13
12
|
|
|
14
13
|
describe("domain DDL policy", () => {
|
|
15
14
|
test("ensure mode creates tables on first touch", async () => {
|
|
16
|
-
const
|
|
17
|
-
const conn = await sqliteDriver.connect({ client: db, role: "primary" });
|
|
15
|
+
const conn = await memorySqlDriver.connect({ role: "primary" });
|
|
18
16
|
const handle = createSqlStoreHandle("sql:app", {
|
|
19
17
|
connection: conn,
|
|
20
18
|
classifications: new Map(),
|
|
@@ -28,12 +26,11 @@ describe("domain DDL policy", () => {
|
|
|
28
26
|
});
|
|
29
27
|
|
|
30
28
|
test("prod/off mode never CREATE TABLE IF NOT EXISTS", async () => {
|
|
31
|
-
const db = new Database(":memory:");
|
|
32
29
|
const execSql: string[] = [];
|
|
33
|
-
const base = await
|
|
30
|
+
const base = await memorySqlDriver.connect({ role: "primary" });
|
|
34
31
|
const conn = {
|
|
35
32
|
...base,
|
|
36
|
-
driverId: "
|
|
33
|
+
driverId: "memory" as const,
|
|
37
34
|
role: "primary" as const,
|
|
38
35
|
async query(sql: string, params?: readonly unknown[]) {
|
|
39
36
|
execSql.push(sql);
|
|
@@ -61,8 +58,7 @@ describe("domain DDL policy", () => {
|
|
|
61
58
|
});
|
|
62
59
|
|
|
63
60
|
test("off mode remaps missing table to DOMAIN_SCHEMA_MISSING", async () => {
|
|
64
|
-
const
|
|
65
|
-
const conn = await sqliteDriver.connect({ client: db, role: "primary" });
|
|
61
|
+
const conn = await memorySqlDriver.connect({ role: "primary" });
|
|
66
62
|
const handle = createSqlStoreHandle("sql:app", {
|
|
67
63
|
connection: conn,
|
|
68
64
|
classifications: new Map(),
|
|
@@ -78,9 +74,6 @@ describe("domain DDL policy", () => {
|
|
|
78
74
|
expect(err).toBeInstanceOf(OkeError);
|
|
79
75
|
const oke = err as OkeError;
|
|
80
76
|
expect(oke.code).toBe(OKE_ERRORS.DOMAIN_SCHEMA_MISSING.code);
|
|
81
|
-
expect(oke.causeText).toContain("migrations have not been applied");
|
|
82
|
-
expect(oke.fix).toContain("oke db migrate");
|
|
83
|
-
expect(oke.docsUrl).toContain(`/e/${OKE_ERRORS.DOMAIN_SCHEMA_MISSING.code}`);
|
|
84
77
|
await conn.close();
|
|
85
78
|
});
|
|
86
79
|
});
|
|
@@ -24,18 +24,24 @@ import {
|
|
|
24
24
|
type SchemaTableDecl,
|
|
25
25
|
} from "./schema-decl.ts";
|
|
26
26
|
|
|
27
|
-
/** Supported Drizzle dialects for domain schema emit. */
|
|
28
|
-
export type SqlDialect = "
|
|
27
|
+
/** Supported Drizzle dialects for domain schema emit (Postgres-family only). */
|
|
28
|
+
export type SqlDialect = "postgres";
|
|
29
29
|
|
|
30
30
|
/** Header written at the top of generated files. */
|
|
31
31
|
export const GENERATED_SCHEMA_HEADER = "// generated by oke — do not edit";
|
|
32
32
|
|
|
33
33
|
/** Default output path (relative to project root). */
|
|
34
|
-
export const DEFAULT_GENERATED_SCHEMA_REL = "src/db/schema.
|
|
34
|
+
export const DEFAULT_GENERATED_SCHEMA_REL = "src/db/schema.drizzle.ts";
|
|
35
35
|
|
|
36
36
|
/** Default declaration module path (relative to project root). */
|
|
37
37
|
export const DEFAULT_DECLARE_SCHEMA_REL = "src/db/schema.decl.ts";
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Prior `src/db/` emit filename — still resolved when present and the new
|
|
41
|
+
* default does not exist yet.
|
|
42
|
+
*/
|
|
43
|
+
export const PREV_GENERATED_SCHEMA_REL = "src/db/schema.generated.ts";
|
|
44
|
+
|
|
39
45
|
/** Legacy paths kept so pre-layout projects keep resolving. */
|
|
40
46
|
export const LEGACY_GENERATED_SCHEMA_REL = "src/schema.generated.ts";
|
|
41
47
|
|
|
@@ -45,11 +51,15 @@ export const LEGACY_DECLARE_SCHEMA_REL = "src/schema.decl.ts";
|
|
|
45
51
|
/**
|
|
46
52
|
* Map a store.sql driver id to an emit dialect.
|
|
47
53
|
*
|
|
48
|
-
* @param driverId - Protocol driver id (`
|
|
54
|
+
* @param driverId - Protocol driver id (`postgres` · `pglite` · …)
|
|
49
55
|
*/
|
|
50
56
|
export function dialectFromDriverId(driverId: string | undefined): SqlDialect {
|
|
51
|
-
if (driverId === "postgres"
|
|
52
|
-
|
|
57
|
+
if (driverId === undefined || driverId === "postgres" || driverId === "pglite") {
|
|
58
|
+
return "postgres";
|
|
59
|
+
}
|
|
60
|
+
throw new Error(
|
|
61
|
+
`oke schema emit: store.sql driver "${driverId}" is not supported (postgres | pglite)`,
|
|
62
|
+
);
|
|
53
63
|
}
|
|
54
64
|
|
|
55
65
|
/**
|
|
@@ -58,9 +68,14 @@ export function dialectFromDriverId(driverId: string | undefined): SqlDialect {
|
|
|
58
68
|
* @param col - Column decl
|
|
59
69
|
* @param dialect - Target dialect (affects import names only at file level)
|
|
60
70
|
*/
|
|
61
|
-
export function emitColumnSource(col: SchemaColumnDecl, _dialect: SqlDialect): string {
|
|
62
|
-
|
|
63
|
-
|
|
71
|
+
export function emitColumnSource(col: SchemaColumnDecl, _dialect: SqlDialect = "postgres"): string {
|
|
72
|
+
// Postgres INTEGER is 32-bit; abstract `integer` (often ms clocks) → bigint.
|
|
73
|
+
// drizzle-orm's `bigint()` requires `{ mode }` — omitting it throws on
|
|
74
|
+
// `config.mode` when the generated schema is loaded by drizzle-kit push.
|
|
75
|
+
let expr =
|
|
76
|
+
col.sqlType === "integer"
|
|
77
|
+
? `bigint(${JSON.stringify(col.sqlName)}, { mode: "number" })`
|
|
78
|
+
: `text(${JSON.stringify(col.sqlName)})`;
|
|
64
79
|
if (col.primaryKey) expr += ".primaryKey()";
|
|
65
80
|
else if (col.notNull) expr += ".notNull()";
|
|
66
81
|
if (col.unique) expr += ".unique()";
|
|
@@ -157,19 +172,20 @@ export interface EmitDrizzleSourceOptions {
|
|
|
157
172
|
* Emit a full Drizzle schema module from abstract table decls.
|
|
158
173
|
*
|
|
159
174
|
* @param tables - App + plugin tables (already merged)
|
|
160
|
-
* @param dialect -
|
|
175
|
+
* @param dialect - Always `postgres` (`pgTable`); retained for call-site clarity
|
|
161
176
|
* @param options - Optional relations to emit as `defineRelations`
|
|
162
177
|
*/
|
|
163
178
|
export function emitDrizzleSource(
|
|
164
179
|
tables: readonly SchemaTableDecl[],
|
|
165
|
-
dialect: SqlDialect,
|
|
180
|
+
dialect: SqlDialect = "postgres",
|
|
166
181
|
options: EmitDrizzleSourceOptions = {},
|
|
167
182
|
): string {
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
183
|
+
void dialect;
|
|
184
|
+
const needsInteger = tables.some((t) =>
|
|
185
|
+
Object.values(t.columns).some((c) => c.sqlType === "integer"),
|
|
186
|
+
);
|
|
187
|
+
const pgIntImport = needsInteger ? "bigint" : null;
|
|
188
|
+
const coreImport = `import { pgTable, text${pgIntImport ? `, ${pgIntImport}` : ""} } from "drizzle-orm/pg-core";`;
|
|
173
189
|
|
|
174
190
|
const needsId = tables.some((t) =>
|
|
175
191
|
Object.values(t.columns).some((c) => c.defaultFnKind === "id"),
|
|
@@ -191,7 +207,7 @@ export function emitDrizzleSource(
|
|
|
191
207
|
const lines = Object.values(table.columns).map(
|
|
192
208
|
(col) => ` ${col.key}: ${emitColumnSource(col, dialect)},`,
|
|
193
209
|
);
|
|
194
|
-
return `export const ${exportNameForTable(table.name)} =
|
|
210
|
+
return `export const ${exportNameForTable(table.name)} = pgTable(${JSON.stringify(table.name)}, {\n${lines.join("\n")}\n});`;
|
|
195
211
|
});
|
|
196
212
|
|
|
197
213
|
const relationsBlock = emitRelationsSource(relations);
|
|
@@ -380,11 +396,20 @@ export function resolveEmitPaths(
|
|
|
380
396
|
|
|
381
397
|
let outRel = db?.generated;
|
|
382
398
|
if (!outRel) {
|
|
383
|
-
const
|
|
399
|
+
const preferSrcLegacy =
|
|
384
400
|
declareRel === LEGACY_DECLARE_SCHEMA_REL ||
|
|
385
401
|
(!existsSync(join(cwd, DEFAULT_DECLARE_SCHEMA_REL)) &&
|
|
386
402
|
existsSync(join(cwd, LEGACY_GENERATED_SCHEMA_REL)));
|
|
387
|
-
|
|
403
|
+
if (preferSrcLegacy) {
|
|
404
|
+
outRel = LEGACY_GENERATED_SCHEMA_REL;
|
|
405
|
+
} else if (
|
|
406
|
+
!existsSync(join(cwd, DEFAULT_GENERATED_SCHEMA_REL)) &&
|
|
407
|
+
existsSync(join(cwd, PREV_GENERATED_SCHEMA_REL))
|
|
408
|
+
) {
|
|
409
|
+
outRel = PREV_GENERATED_SCHEMA_REL;
|
|
410
|
+
} else {
|
|
411
|
+
outRel = DEFAULT_GENERATED_SCHEMA_REL;
|
|
412
|
+
}
|
|
388
413
|
}
|
|
389
414
|
|
|
390
415
|
return {
|
|
@@ -1,43 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* End-to-end `store.index()` boot wiring — the CONFIGURED driver resolves at
|
|
3
|
-
* real boot for every backend (memory /
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* real boot for every backend (memory / pgvector over pglite / pgvector over
|
|
4
|
+
* real Postgres), SQL-backed indexes share store.sql's already-open connection
|
|
5
|
+
* (never a second, redundant one), and search runs through each engine's
|
|
6
|
+
* native ANN index — no JS-side full scan survives.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { afterEach, describe, expect, test } from "bun:test";
|
|
10
|
-
import {
|
|
11
|
-
connectLibsql,
|
|
12
|
-
libsqlDriver,
|
|
13
|
-
libsqlIndexDriver,
|
|
14
|
-
openLibsqlIndex,
|
|
15
|
-
} from "../../drivers/libsql.ts";
|
|
16
10
|
import { connectPglite } from "../../drivers/pglite.ts";
|
|
17
11
|
import { openPgvectorIndex } from "../../drivers/pgvector.ts";
|
|
18
12
|
import { connectPostgres } from "../../drivers/postgres.ts";
|
|
19
|
-
import type { SqlConnection
|
|
13
|
+
import type { SqlConnection } from "../../drivers/types.ts";
|
|
20
14
|
import { bindStore } from "../../kernel/boot-bind/store.ts";
|
|
21
|
-
import {
|
|
22
|
-
createStoreRuntime,
|
|
23
|
-
index as declareIndex,
|
|
24
|
-
sql as declareSql,
|
|
25
|
-
type SqlStoreHandle,
|
|
26
|
-
type VectorIndexStoreFxHandle,
|
|
27
|
-
} from "../store.ts";
|
|
15
|
+
import { index as declareIndex, type VectorIndexStoreFxHandle } from "../store.ts";
|
|
28
16
|
|
|
29
17
|
const WRITE_CTX = (name: string) => ({
|
|
30
18
|
effects: { reads: [`index:${name}` as const], writes: [`index:${name}` as const] },
|
|
31
19
|
});
|
|
32
20
|
|
|
33
21
|
const prev = {
|
|
34
|
-
libsql: process.env.OKE_LIBSQL_URL,
|
|
35
22
|
pglite: process.env.OKE_PGLITE_URL,
|
|
36
23
|
};
|
|
37
24
|
|
|
38
25
|
afterEach(() => {
|
|
39
|
-
if (prev.libsql === undefined) delete process.env.OKE_LIBSQL_URL;
|
|
40
|
-
else process.env.OKE_LIBSQL_URL = prev.libsql;
|
|
41
26
|
if (prev.pglite === undefined) delete process.env.OKE_PGLITE_URL;
|
|
42
27
|
else process.env.OKE_PGLITE_URL = prev.pglite;
|
|
43
28
|
});
|
|
@@ -66,7 +51,7 @@ function recordingConn(conn: SqlConnection): { conn: SqlConnection; statements:
|
|
|
66
51
|
describe("store.index boot wiring — memory", () => {
|
|
67
52
|
test("boot with no configured index driver resolves memory end to end", async () => {
|
|
68
53
|
const kb = declareIndex("kb", { dims: 3 });
|
|
69
|
-
const runtime = bindStore({ stores: [kb] }, "
|
|
54
|
+
const runtime = bindStore({ stores: [kb] }, "test", () => Date.now());
|
|
70
55
|
const handle = (await runtime.open(kb, WRITE_CTX("kb"))) as VectorIndexStoreFxHandle;
|
|
71
56
|
expect(handle.driverId).toBe("memory");
|
|
72
57
|
await handle.upsert("near", [1, 0, 0]);
|
|
@@ -77,88 +62,16 @@ describe("store.index boot wiring — memory", () => {
|
|
|
77
62
|
});
|
|
78
63
|
});
|
|
79
64
|
|
|
80
|
-
describe("store.index boot wiring — libsql", () => {
|
|
81
|
-
test("boot: sql=libsql + index=libsql resolves native ANN end to end", async () => {
|
|
82
|
-
process.env.OKE_LIBSQL_URL = ":memory:";
|
|
83
|
-
const kb = declareIndex("kb", { dims: 3 });
|
|
84
|
-
const runtime = bindStore(
|
|
85
|
-
{
|
|
86
|
-
config: { drivers: { store: { sql: { local: "libsql" }, index: { local: "libsql" } } } },
|
|
87
|
-
stores: [kb],
|
|
88
|
-
},
|
|
89
|
-
"local",
|
|
90
|
-
() => Date.now(),
|
|
91
|
-
);
|
|
92
|
-
const handle = (await runtime.open(kb, WRITE_CTX("kb"))) as VectorIndexStoreFxHandle;
|
|
93
|
-
expect(handle.driverId).toBe("libsql");
|
|
94
|
-
|
|
95
|
-
await handle.upsert("near", [1, 0.1, 0], { label: "near" });
|
|
96
|
-
await handle.upsert("far", [0, 1, 0]);
|
|
97
|
-
await handle.upsert("farther", [-1, 0, 0]);
|
|
98
|
-
const hits = await handle.search([1, 0, 0], 3);
|
|
99
|
-
expect(hits.map((h: { id: string }) => h.id)).toEqual(["near", "far", "farther"]);
|
|
100
|
-
expect(hits[0]!.score).toBeGreaterThan(hits[1]!.score);
|
|
101
|
-
expect(hits[0]!.meta).toEqual({ label: "near" });
|
|
102
|
-
expect(await handle.delete("farther")).toBe(true);
|
|
103
|
-
await runtime.close();
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test("sql and index facets share one already-open libsql connection", async () => {
|
|
107
|
-
let connects = 0;
|
|
108
|
-
const countingLibsql: SqlDriver = {
|
|
109
|
-
...libsqlDriver,
|
|
110
|
-
connect: async (opts) => {
|
|
111
|
-
connects++;
|
|
112
|
-
return libsqlDriver.connect(opts);
|
|
113
|
-
},
|
|
114
|
-
};
|
|
115
|
-
const docs = declareSql("docs");
|
|
116
|
-
const kb = declareIndex("kb", { dims: 3 });
|
|
117
|
-
const runtime = createStoreRuntime({
|
|
118
|
-
drivers: { sql: countingLibsql, index: libsqlIndexDriver },
|
|
119
|
-
sql: { docs: { name: "docs", primary: { url: ":memory:" } } },
|
|
120
|
-
index: { kb: { url: ":memory:" } },
|
|
121
|
-
});
|
|
122
|
-
// Index first: it must open the connection the sql facet then reuses.
|
|
123
|
-
const idxHandle = (await runtime.open(kb, WRITE_CTX("kb"))) as VectorIndexStoreFxHandle;
|
|
124
|
-
await idxHandle.upsert("a", [1, 0, 0]);
|
|
125
|
-
const sqlHandle = (await runtime.open(docs, {
|
|
126
|
-
effects: { reads: ["sql:docs"], writes: ["sql:docs"] },
|
|
127
|
-
})) as SqlStoreHandle;
|
|
128
|
-
await sqlHandle.raw("CREATE TABLE IF NOT EXISTS t_share (id TEXT)", []);
|
|
129
|
-
expect(connects).toBe(1);
|
|
130
|
-
await runtime.close();
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
test("libsql index searches through vector_top_k — real ANN, not a scan", async () => {
|
|
134
|
-
const real = await connectLibsql({ url: ":memory:" });
|
|
135
|
-
const { conn, statements } = recordingConn(real);
|
|
136
|
-
const idx = await openLibsqlIndex({ name: "ann", dims: 3, sql: conn });
|
|
137
|
-
await idx.upsert("x", [1, 0, 0]);
|
|
138
|
-
await idx.upsert("y", [0, 1, 0]);
|
|
139
|
-
const hits = await idx.search([1, 0, 0], 1);
|
|
140
|
-
expect(hits[0]?.id).toBe("x");
|
|
141
|
-
expect(statements.some((s) => s.includes("libsql_vector_idx"))).toBe(true);
|
|
142
|
-
expect(statements.some((s) => s.includes("vector_top_k"))).toBe(true);
|
|
143
|
-
const plan = await real.query(
|
|
144
|
-
`EXPLAIN QUERY PLAN SELECT * FROM vector_top_k('oke_idx_ann_vec', vector32('[1,0,0]'), 1)`,
|
|
145
|
-
);
|
|
146
|
-
expect(JSON.stringify(plan)).toContain("vector_top_k");
|
|
147
|
-
await idx.close();
|
|
148
|
-
await real.close();
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
65
|
describe("store.index boot wiring — pglite + pgvector", () => {
|
|
153
66
|
test("boot: sql=pglite + index=pgvector resolves end to end", async () => {
|
|
154
67
|
process.env.OKE_PGLITE_URL = "memory://";
|
|
155
68
|
const kb = declareIndex("kb", { dims: 3 });
|
|
156
69
|
const runtime = bindStore(
|
|
157
70
|
{
|
|
158
|
-
config: { drivers: { store: { sql: {
|
|
71
|
+
config: { drivers: { store: { sql: { test: "pglite" }, index: { test: "pgvector" } } } },
|
|
159
72
|
stores: [kb],
|
|
160
73
|
},
|
|
161
|
-
"
|
|
74
|
+
"test",
|
|
162
75
|
() => Date.now(),
|
|
163
76
|
);
|
|
164
77
|
const handle = (await runtime.open(kb, WRITE_CTX("kb"))) as VectorIndexStoreFxHandle;
|
|
@@ -202,24 +115,10 @@ describe("store.index boot wiring — fail loud", () => {
|
|
|
202
115
|
const kb = declareIndex("kb", { dims: 3 });
|
|
203
116
|
const runtime = bindStore(
|
|
204
117
|
{
|
|
205
|
-
config: { drivers: { store: { sql: {
|
|
206
|
-
stores: [kb],
|
|
207
|
-
},
|
|
208
|
-
"local",
|
|
209
|
-
() => Date.now(),
|
|
210
|
-
);
|
|
211
|
-
await expect(runtime.open(kb, WRITE_CTX("kb"))).rejects.toThrow(/cannot share sql driver/);
|
|
212
|
-
await runtime.close();
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
test("libsql index with sqlite sql driver throws", async () => {
|
|
216
|
-
const kb = declareIndex("kb", { dims: 3 });
|
|
217
|
-
const runtime = bindStore(
|
|
218
|
-
{
|
|
219
|
-
config: { drivers: { store: { sql: { local: "sqlite" }, index: { local: "libsql" } } } },
|
|
118
|
+
config: { drivers: { store: { sql: { test: "memory" }, index: { test: "pgvector" } } } },
|
|
220
119
|
stores: [kb],
|
|
221
120
|
},
|
|
222
|
-
"
|
|
121
|
+
"test",
|
|
223
122
|
() => Date.now(),
|
|
224
123
|
);
|
|
225
124
|
await expect(runtime.open(kb, WRITE_CTX("kb"))).rejects.toThrow(/cannot share sql driver/);
|
|
@@ -233,8 +132,8 @@ describe("store.index boot wiring — fail loud", () => {
|
|
|
233
132
|
const kb = declareIndex("kb");
|
|
234
133
|
expect(() =>
|
|
235
134
|
bindStore(
|
|
236
|
-
{ config: { drivers: { store: { index: {
|
|
237
|
-
"
|
|
135
|
+
{ config: { drivers: { store: { index: { test: "meilisearch" } } } }, stores: [kb] },
|
|
136
|
+
"test",
|
|
238
137
|
() => Date.now(),
|
|
239
138
|
),
|
|
240
139
|
).toThrow(/OKE_STORE_INDEX_URL/);
|
|
@@ -252,8 +151,8 @@ describe("store.index boot wiring — fail loud", () => {
|
|
|
252
151
|
try {
|
|
253
152
|
const kb = declareIndex("kb");
|
|
254
153
|
const runtime = bindStore(
|
|
255
|
-
{ config: { drivers: { store: { index: {
|
|
256
|
-
"
|
|
154
|
+
{ config: { drivers: { store: { index: { test: "meilisearch" } } } }, stores: [kb] },
|
|
155
|
+
"test",
|
|
257
156
|
() => Date.now(),
|
|
258
157
|
);
|
|
259
158
|
// No live server in unit tests: opening must fail loud (unreachable),
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Load plugin table contributions from a live `oke()` app entry.
|
|
3
3
|
*
|
|
4
4
|
* Used by `oke db` emit so `.plug()`-registered `field.*` tables merge into
|
|
5
|
-
* `schema.
|
|
5
|
+
* `schema.drizzle.ts` — not only at unit-test merge call sites.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { resolve } from "node:path";
|
|
@@ -11,16 +11,22 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { z } from "zod";
|
|
14
|
-
import { field, id,
|
|
14
|
+
import { field, id, store } from "../store.ts";
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
/** Unique table name — avoids colliding with other suites' `docs_items` DDL. */
|
|
17
|
+
const docsItems = store.schema.table("docs_list_items", {
|
|
17
18
|
id: field.text().primaryKey().defaultFn(id),
|
|
18
19
|
title: field.text().notNull(),
|
|
19
20
|
secret: field.text().notNull(),
|
|
20
|
-
|
|
21
|
+
// Small default — docs fixtures only need a stable integer clock column.
|
|
22
|
+
// (ms `now()` exceeds Postgres INTEGER; ensure DDL maps to BIGINT for apps.)
|
|
23
|
+
createdAt: field.integer().notNull().default(1),
|
|
21
24
|
});
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
/** Shared SQL store — tests must pass this to `oke({ stores })` after registry consume. */
|
|
27
|
+
export const docsListItemsStore = store.sql("docs-list-items", {
|
|
28
|
+
schema: { docs_list_items: docsItems },
|
|
29
|
+
});
|
|
24
30
|
|
|
25
31
|
const inSchema = z.object({ title: z.string().min(1), secret: z.string().min(1) });
|
|
26
32
|
const outSchema = z.object({
|
|
@@ -35,7 +41,7 @@ const outSchema = z.object({
|
|
|
35
41
|
* `count: "exact"` (offset default) and point to `count: "none"` as the escape
|
|
36
42
|
* hatch when COUNT(*) is too expensive.
|
|
37
43
|
*/
|
|
38
|
-
export const docsAdminTableResource = store.resource(
|
|
44
|
+
export const docsAdminTableResource = store.resource(docsListItemsStore, docsItems, {
|
|
39
45
|
in: inSchema,
|
|
40
46
|
out: outSchema,
|
|
41
47
|
list: { mode: "offset", count: "exact", limit: 20 },
|
|
@@ -47,7 +53,7 @@ export const docsAdminTableResource = store.resource(db, docsItems, {
|
|
|
47
53
|
* Public restricted endpoint — filters off entirely. Any `?col=op.value`
|
|
48
54
|
* query key that is not a reserved list param fails validation (422).
|
|
49
55
|
*/
|
|
50
|
-
export const docsRestrictedResource = store.resource(
|
|
56
|
+
export const docsRestrictedResource = store.resource(docsListItemsStore, docsItems, {
|
|
51
57
|
in: inSchema,
|
|
52
58
|
out: outSchema,
|
|
53
59
|
list: { mode: "offset", filter: "none", limit: 20 },
|
|
@@ -15,7 +15,11 @@ import { resetFlowSeq } from "../../kernel/flow.ts";
|
|
|
15
15
|
import { on, resetBindings } from "../../kernel/on.ts";
|
|
16
16
|
import { http } from "../../kernel/triggers.ts";
|
|
17
17
|
import { createTestApp } from "../../test/create-test-app.ts";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
docsAdminTableResource,
|
|
20
|
+
docsListItemsStore,
|
|
21
|
+
docsRestrictedResource,
|
|
22
|
+
} from "./resource-list-docs.fixture.ts";
|
|
19
23
|
|
|
20
24
|
afterEach(() => {
|
|
21
25
|
resetBindings();
|
|
@@ -23,57 +27,73 @@ afterEach(() => {
|
|
|
23
27
|
});
|
|
24
28
|
|
|
25
29
|
describe("resource-list-docs fixtures — documented behaviors stay real", () => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
// PGLite first-boot under full-suite load can exceed the default 5s.
|
|
31
|
+
test(
|
|
32
|
+
"filter: \"none\" — forbidden filter key → 422 ValidationError 'unknown list param'",
|
|
33
|
+
async () => {
|
|
34
|
+
resetBindings();
|
|
35
|
+
resetFlowSeq();
|
|
29
36
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
const mounted = on(http.resource("/docs-restricted", docsRestrictedResource.all()));
|
|
38
|
+
const app = oke({ name: "docs-restricted-test" }).adopt({ docs: mounted });
|
|
39
|
+
// Explicit stores — module-level `store.sql` is drained by the first `oke()`
|
|
40
|
+
// (and cleared by the global afterEach registry reset).
|
|
41
|
+
Object.assign(app.$options, { env: "test", stores: [docsListItemsStore] });
|
|
33
42
|
|
|
34
|
-
|
|
43
|
+
const t = await createTestApp(app);
|
|
35
44
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
const bad = await app.fetch(new Request("http://localhost/docs-restricted?secret=eq.x"));
|
|
46
|
+
expect(bad.status).toBe(422);
|
|
47
|
+
const badBody = (await bad.json()) as {
|
|
48
|
+
data: null;
|
|
49
|
+
error: { code: string; data: { issues: Array<{ message: string; path: string[] }> } };
|
|
50
|
+
};
|
|
51
|
+
expect(badBody.error.code).toBe("ValidationError");
|
|
52
|
+
expect(badBody.error.data.issues[0]!.message).toBe('unknown list param "secret"');
|
|
53
|
+
expect(badBody.error.data.issues[0]!.path).toEqual(["secret"]);
|
|
45
54
|
|
|
46
|
-
|
|
47
|
-
|
|
55
|
+
const ok = await app.fetch(new Request("http://localhost/docs-restricted?limit=20"));
|
|
56
|
+
expect(ok.status).toBe(200);
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
|
|
58
|
+
await t.close();
|
|
59
|
+
},
|
|
60
|
+
{ timeout: 30_000 },
|
|
61
|
+
);
|
|
51
62
|
|
|
52
|
-
test(
|
|
53
|
-
|
|
54
|
-
|
|
63
|
+
test(
|
|
64
|
+
'offset + count: "exact" answers meta.total; "none" drops it',
|
|
65
|
+
async () => {
|
|
66
|
+
resetBindings();
|
|
67
|
+
resetFlowSeq();
|
|
55
68
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
69
|
+
const exactMounted = on(http.resource("/docs-admin", docsAdminTableResource.all()));
|
|
70
|
+
const app = oke({ name: "docs-admin-test" }).adopt({ docs: exactMounted });
|
|
71
|
+
Object.assign(app.$options, { env: "test", stores: [docsListItemsStore] });
|
|
59
72
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
73
|
+
const t = await createTestApp(app);
|
|
74
|
+
const created = await app.fetch(
|
|
75
|
+
new Request("http://localhost/docs-admin", {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: { "content-type": "application/json" },
|
|
78
|
+
body: JSON.stringify({ title: "one", secret: "s" }),
|
|
79
|
+
}),
|
|
80
|
+
);
|
|
81
|
+
expect(created.status).toBe(201);
|
|
68
82
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
83
|
+
const exact = await app.fetch(new Request("http://localhost/docs-admin?limit=20"));
|
|
84
|
+
expect(exact.status).toBe(200);
|
|
85
|
+
const exactBody = (await exact.json()) as {
|
|
86
|
+
data: Array<{ title?: string }>;
|
|
87
|
+
meta: Record<string, unknown>;
|
|
88
|
+
};
|
|
89
|
+
// `count: "exact"` must answer meta.total (COUNT(*)); offset mode keeps meta.offset.
|
|
90
|
+
expect(typeof exactBody.meta.total).toBe("number");
|
|
91
|
+
expect(exactBody.meta.total).toBeGreaterThanOrEqual(1);
|
|
92
|
+
expect(exactBody.meta.offset).toBe(0);
|
|
93
|
+
expect(exactBody.data.some((row) => row.title === "one")).toBe(true);
|
|
76
94
|
|
|
77
|
-
|
|
78
|
-
|
|
95
|
+
await t.close();
|
|
96
|
+
},
|
|
97
|
+
{ timeout: 30_000 },
|
|
98
|
+
);
|
|
79
99
|
});
|