okengine 0.19.9 → 0.21.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/AGENTS.md +1 -1
- package/package.json +2 -1
- package/site/content/docs/client/auth.mdx +1 -2
- package/site/content/docs/client/calling.mdx +125 -37
- package/site/content/docs/client/index.mdx +7 -7
- package/site/content/docs/client/react.mdx +5 -0
- package/site/content/docs/elements/channel/email.mdx +25 -36
- package/site/content/docs/elements/channel/index.mdx +88 -46
- package/site/content/docs/elements/channel/push.mdx +7 -9
- package/site/content/docs/elements/channel/sms.mdx +6 -4
- package/site/content/docs/elements/channel/whatsapp.mdx +11 -9
- package/site/content/docs/elements/clock/index.mdx +14 -25
- package/site/content/docs/elements/flow/http.mdx +42 -23
- package/site/content/docs/elements/flow/index.mdx +41 -30
- package/site/content/docs/elements/flow/routing.mdx +166 -122
- package/site/content/docs/elements/gate/rls.mdx +2 -2
- package/site/content/docs/elements/gate/tenancy.mdx +1 -1
- package/site/content/docs/elements/store/files.mdx +11 -5
- package/site/content/docs/elements/store/index.mdx +13 -6
- package/site/content/docs/elements/store/kv.mdx +8 -2
- package/site/content/docs/elements/store/search.mdx +5 -5
- package/site/content/docs/elements/store/sql.mdx +100 -39
- package/site/content/docs/elements/vault/index.mdx +14 -13
- package/site/content/docs/elements/vault/secrets.mdx +6 -3
- package/site/content/docs/index.mdx +1 -1
- package/site/content/docs/plugins/magic-link.mdx +4 -3
- package/site/content/docs/plugins/otp.mdx +4 -3
- package/site/content/docs/plugins/two-factor.mdx +4 -0
- package/site/content/docs/providers/index.mdx +1 -1
- package/site/content/docs/recipes/index.mdx +1 -1
- package/site/content/docs/recipes/rustfs.mdx +1 -1
- package/site/content/docs/reference/cli.mdx +7 -4
- package/site/content/docs/reference/configuration.mdx +8 -8
- package/site/content/docs/reference/errors.mdx +229 -55
- package/site/content/docs/reference/fx.mdx +22 -9
- package/site/content/docs/reference/i18n.mdx +4 -4
- package/site/content/docs/reference/plugins.mdx +4 -4
- package/site/content/docs/understand/the-architecture.mdx +2 -2
- package/site/content/docs/understand/try-it.mdx +759 -24
- package/src/cli/ai-setup/ai-setup.test.ts +40 -0
- package/src/cli/ai-setup/apply.ts +28 -46
- package/src/cli/build.test.ts +3 -3
- package/src/cli/build.ts +5 -5
- package/src/cli/db-auto-push.test.ts +11 -0
- package/src/cli/db-auto-push.ts +6 -2
- package/src/cli/db.test.ts +1 -1
- package/src/cli/db.ts +6 -6
- package/src/cli/dev-app-runner.ts +2 -1
- package/src/cli/dev-db-push.test.ts +6 -2
- package/src/cli/dev-schema-sync.ts +1 -1
- package/src/cli/dev.test.ts +10 -7
- package/src/cli/dev.ts +13 -11
- package/src/cli/ensure-drizzle-config.ts +4 -3
- package/src/cli/start.ts +2 -1
- package/src/client/create.ts +3 -3
- package/src/client/explain.test.ts +252 -0
- package/src/client/explain.ts +272 -0
- package/src/client/live.test.ts +44 -0
- package/src/client/notes-contract.test.ts +10 -0
- package/src/client/sse.ts +6 -2
- package/src/client/transport.test.ts +67 -0
- package/src/client/transport.ts +24 -40
- package/src/client/types.ts +17 -6
- package/src/client-react/browser.test.ts +23 -0
- package/src/client-react/live-resource.ts +6 -2
- package/src/client-react/use-live-query.ts +1 -1
- package/src/compiler/flow-path.test.ts +1 -0
- package/src/compiler/flow-path.ts +1 -1
- package/src/compiler/generate-adopt.test.ts +55 -1
- package/src/compiler/generate-adopt.ts +111 -21
- package/src/compiler/response.ts +17 -27
- package/src/config/index.ts +6 -4
- package/src/console/server/invoke-user-flow.test.ts +8 -2
- package/src/console/server/invoke-user-flow.ts +12 -18
- package/src/console/server/security.gate.test.ts +1 -1
- package/src/console/ui-next/dist/assets/{access-page-BoC83Ubl.js → access-page-tIbsiphz.js} +1 -1
- package/src/console/ui-next/dist/assets/{agent-disclosure-CKjAEOqA.js → agent-disclosure-CSKumwS2.js} +1 -1
- package/src/console/ui-next/dist/assets/{cache-glyph-Ceaq9pYh.js → cache-glyph-BCC-DxKT.js} +1 -1
- package/src/console/ui-next/dist/assets/{call-pii-button-C4lmY7ck.js → call-pii-button-jOmYISdc.js} +1 -1
- package/src/console/ui-next/dist/assets/{collapsible-82y257sL.js → collapsible-DC2xNaAb.js} +1 -1
- package/src/console/ui-next/dist/assets/{duration-tone-W63jaMZ8.js → duration-tone-CwoV56jn.js} +1 -1
- package/src/console/ui-next/dist/assets/{flows-page-KFTFj2rK.js → flows-page-gT1lsWQK.js} +1 -1
- package/src/console/ui-next/dist/assets/{highlighted-json-yE9zNTqC.js → highlighted-json-C2GZEJNI.js} +1 -1
- package/src/console/ui-next/dist/assets/{http-method-CwFeFroN.js → http-method-BdYjcIrD.js} +1 -1
- package/src/console/ui-next/dist/assets/{index-r7xXt_VV.js → index-Cul17AcV.js} +3 -3
- package/src/console/ui-next/dist/assets/{observability-page-BDiliMiC.js → observability-page-oY9vdYBk.js} +1 -1
- package/src/console/ui-next/dist/assets/{replica-lag-DYDzWUFT.js → replica-lag-C4QdAF7J.js} +1 -1
- package/src/console/ui-next/dist/assets/{request-meta-CzrOfgiz.js → request-meta-C43DHyld.js} +1 -1
- package/src/console/ui-next/dist/assets/{store-page-CZC2cwaw.js → store-page-CL0D9dOq.js} +1 -1
- package/src/console/ui-next/dist/assets/{trace-detail-sheet-DgeeejW7.js → trace-detail-sheet-Dcpo4Us_.js} +1 -1
- package/src/console/ui-next/dist/assets/{tree-expand-toggle-CzGIyOPY.js → tree-expand-toggle-CKJTuv43.js} +1 -1
- package/src/console/ui-next/dist/assets/{units-page-DBiDCLIB.js → units-page-1PlT19ft.js} +1 -1
- package/src/console/ui-next/dist/assets/{vault-page-CcHsthPe.js → vault-page-BwZ9YjTW.js} +1 -1
- package/src/console/ui-next/dist/index.html +1 -1
- package/src/docker/docker.test.ts +3 -3
- package/src/docker/images-config.test.ts +4 -4
- package/src/docker/stack-id.test.ts +1 -1
- package/src/drivers/clock-postgres.test.ts +10 -2
- package/src/drivers/clock-postgres.ts +18 -2
- package/src/drivers/vault-driver-removal.test.ts +2 -2
- package/src/elements/channel/declare.ts +66 -3
- package/src/elements/channel/runtime.ts +9 -11
- package/src/elements/channel.test.ts +42 -0
- package/src/elements/channel.ts +4 -2
- package/src/elements/clock/reconcile.ts +45 -24
- package/src/elements/clock.test.ts +33 -0
- package/src/elements/store/emit-drizzle.ts +285 -65
- package/src/elements/store/files-errors.test.ts +149 -0
- package/src/elements/store/files-errors.ts +189 -0
- package/src/elements/store/kv-errors.test.ts +98 -0
- package/src/elements/store/kv-errors.ts +139 -0
- package/src/elements/store/load-plugin-tables.ts +1 -1
- package/src/elements/store/prepare-row.test.ts +57 -4
- package/src/elements/store/resource.ts +11 -7
- package/src/elements/store/runtime.ts +18 -13
- package/src/elements/store/schema-decl.test.ts +178 -0
- package/src/elements/store/sql-errors.test.ts +197 -0
- package/src/elements/store/sql-errors.ts +294 -0
- package/src/elements/store/sql-session.test.ts +52 -0
- package/src/elements/store/sql-session.ts +50 -2
- package/src/elements/store/store-errors.ts +47 -0
- package/src/elements/store/table.ts +8 -6
- package/src/http.ts +9 -1
- package/src/i18n/catalogs/ar.ts +18 -0
- package/src/i18n/catalogs/en.ts +18 -0
- package/src/index.ts +9 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
- package/src/kernel/app.ts +40 -34
- package/src/kernel/auto-registry.test.ts +26 -1
- package/src/kernel/boot.ts +2 -2
- package/src/kernel/boundary-contract.ts +6 -1
- package/src/kernel/builtin-errors.test.ts +117 -0
- package/src/kernel/builtin-errors.ts +129 -0
- package/src/kernel/call.test.ts +182 -0
- package/src/kernel/errors-vault.ts +16 -0
- package/src/kernel/errors.registry.test.ts +7 -0
- package/src/kernel/errors.ts +97 -24
- package/src/kernel/fail-helpers.ts +34 -0
- package/src/kernel/flow-units.ts +3 -3
- package/src/kernel/fx.test.ts +8 -0
- package/src/kernel/fx.ts +24 -8
- package/src/kernel/index.ts +12 -1
- package/src/kernel/mutation-id.ts +8 -0
- package/src/kernel/plugin.ts +4 -3
- package/src/kernel/project-out.test.ts +176 -0
- package/src/kernel/project-out.ts +91 -0
- package/src/kernel/realtime-bind.ts +2 -3
- package/src/kernel/router/linear.ts +12 -6
- package/src/kernel/router.test.ts +13 -0
- package/src/plugins/magic-link.ts +25 -24
- package/src/plugins/otp.ts +35 -24
- package/src/plugins/two-factor.ts +15 -0
- package/src/runs/duckdb.test.ts +2 -2
- package/src/runtime/dev-request-log.ts +29 -11
- package/src/term.test.ts +76 -0
- package/src/term.ts +166 -3
|
@@ -464,6 +464,46 @@ export default defineConfig({
|
|
|
464
464
|
}
|
|
465
465
|
});
|
|
466
466
|
|
|
467
|
+
test("applyAiSetup writes src/core/ai.ts when the barrel is src/core/index.ts", () => {
|
|
468
|
+
const dir = mkdtempSync(join(tmpdir(), "oke-ai-setup-index-"));
|
|
469
|
+
try {
|
|
470
|
+
writeFileSync(
|
|
471
|
+
join(dir, "oke.config.ts"),
|
|
472
|
+
`import { defineConfig } from "okengine/config";
|
|
473
|
+
export default defineConfig({
|
|
474
|
+
drivers: {
|
|
475
|
+
channel: { email: { dev: "console", test: "console", prod: "console" } },
|
|
476
|
+
},
|
|
477
|
+
});
|
|
478
|
+
`,
|
|
479
|
+
"utf8",
|
|
480
|
+
);
|
|
481
|
+
mkdirSync(join(dir, "src", "core"), { recursive: true });
|
|
482
|
+
writeFileSync(join(dir, "src", "core", "index.ts"), `export * from "./store.ts";\n`, "utf8");
|
|
483
|
+
writeFileSync(
|
|
484
|
+
join(dir, "src", "app.ts"),
|
|
485
|
+
`import "@/core";\nexport const app = {};\n`,
|
|
486
|
+
"utf8",
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
applyAiSetup(dir, {
|
|
490
|
+
driver: "openai-compatible",
|
|
491
|
+
provider: "openrouter",
|
|
492
|
+
chatModel: "openrouter/free",
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
const index = readFileSync(join(dir, "src", "core", "index.ts"), "utf8");
|
|
496
|
+
expect(index).not.toContain("ai.model");
|
|
497
|
+
expect(index).toContain('export * from "./ai.ts"');
|
|
498
|
+
const aiTs = readFileSync(join(dir, "src", "core", "ai.ts"), "utf8");
|
|
499
|
+
expect(aiTs).toContain("smart");
|
|
500
|
+
const app = readFileSync(join(dir, "src", "app.ts"), "utf8");
|
|
501
|
+
expect(app).not.toContain("./core/ai");
|
|
502
|
+
} finally {
|
|
503
|
+
rmSync(dir, { recursive: true, force: true });
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
|
|
467
507
|
test("applyAiSetup writes API token to .env.local and vault.secret into core", () => {
|
|
468
508
|
const dir = mkdtempSync(join(tmpdir(), "oke-ai-setup-key-"));
|
|
469
509
|
try {
|
|
@@ -434,8 +434,8 @@ export function ensureAiApiKeyVaultSecret(cwd: string, apiKeyEnv: string): void
|
|
|
434
434
|
|
|
435
435
|
/**
|
|
436
436
|
* Write AI model declarations into `src/core/ai.ts` when that split exists
|
|
437
|
-
* (so a thin `src/core.ts` barrel stays a re-export),
|
|
438
|
-
* else
|
|
437
|
+
* (so a thin `src/core.ts` / `src/core/index.ts` barrel stays a re-export),
|
|
438
|
+
* else `src/core.ts`.
|
|
439
439
|
*
|
|
440
440
|
* @param cwd - Project root
|
|
441
441
|
* @param input - Setup choices
|
|
@@ -445,25 +445,23 @@ function writeAiModels(cwd: string, input: AiSetupApplyInput): string {
|
|
|
445
445
|
const rendered = renderAiTs(input);
|
|
446
446
|
const coreAiPath = join(cwd, "src", "core", "ai.ts");
|
|
447
447
|
const coreTsPath = join(cwd, "src", "core.ts");
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
448
|
+
const coreIndexPath = join(cwd, "src", "core", "index.ts");
|
|
449
|
+
const splitLayout =
|
|
450
|
+
existsSync(coreAiPath) || (!existsSync(coreTsPath) && existsSync(coreIndexPath));
|
|
451
|
+
|
|
452
|
+
if (splitLayout) {
|
|
453
|
+
mkdirSync(dirname(coreAiPath), { recursive: true });
|
|
454
|
+
const existing = existsSync(coreAiPath) ? readFileSync(coreAiPath, "utf8") : "";
|
|
455
|
+
writeFileSync(
|
|
456
|
+
coreAiPath,
|
|
457
|
+
existing ? resolveAiCoreSource(existing, rendered) : rendered,
|
|
458
|
+
"utf8",
|
|
459
|
+
);
|
|
453
460
|
ensureCoreBarrelExportsAi(cwd);
|
|
454
461
|
ensureCoreImported(cwd);
|
|
455
462
|
return coreAiPath;
|
|
456
463
|
}
|
|
457
464
|
|
|
458
|
-
// Folder layout still in the wild — keep writing a sidecar.
|
|
459
|
-
if (!existsSync(coreTsPath) && existsSync(legacyIndex)) {
|
|
460
|
-
const aiTsPath = join(cwd, "src", "core", "ai.ts");
|
|
461
|
-
mkdirSync(dirname(aiTsPath), { recursive: true });
|
|
462
|
-
writeFileSync(aiTsPath, rendered, "utf8");
|
|
463
|
-
ensureLegacyAiImported(cwd);
|
|
464
|
-
return aiTsPath;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
465
|
mkdirSync(dirname(coreTsPath), { recursive: true });
|
|
468
466
|
if (existsSync(coreTsPath)) {
|
|
469
467
|
const existing = readFileSync(coreTsPath, "utf8");
|
|
@@ -625,16 +623,25 @@ export function ensureNamedOkengineImport(source: string, name: string): string
|
|
|
625
623
|
}
|
|
626
624
|
|
|
627
625
|
/**
|
|
628
|
-
* Keep a
|
|
626
|
+
* Keep a core barrel re-exporting the AI sidecar after a split write.
|
|
627
|
+
*
|
|
628
|
+
* `src/core.ts` → `./core/ai.ts`; `src/core/index.ts` → `./ai.ts`.
|
|
629
629
|
*
|
|
630
630
|
* @param cwd - Project root
|
|
631
631
|
*/
|
|
632
632
|
function ensureCoreBarrelExportsAi(cwd: string): void {
|
|
633
633
|
const coreTsPath = join(cwd, "src", "core.ts");
|
|
634
|
-
if (
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
634
|
+
if (existsSync(coreTsPath)) {
|
|
635
|
+
const src = readFileSync(coreTsPath, "utf8");
|
|
636
|
+
if (/from\s+["']\.\/core\/ai(?:\.ts)?["']/.test(src)) return;
|
|
637
|
+
writeFileSync(coreTsPath, `${src.trimEnd()}\nexport * from "./core/ai.ts";\n`, "utf8");
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
const coreIndexPath = join(cwd, "src", "core", "index.ts");
|
|
641
|
+
if (!existsSync(coreIndexPath)) return;
|
|
642
|
+
const src = readFileSync(coreIndexPath, "utf8");
|
|
643
|
+
if (/from\s+["']\.\/ai(?:\.ts)?["']/.test(src)) return;
|
|
644
|
+
writeFileSync(coreIndexPath, `${src.trimEnd()}\nexport * from "./ai.ts";\n`, "utf8");
|
|
638
645
|
}
|
|
639
646
|
|
|
640
647
|
/**
|
|
@@ -656,28 +663,3 @@ function ensureCoreImported(cwd: string): void {
|
|
|
656
663
|
}
|
|
657
664
|
writeFileSync(appPath, `import "@/core";\n${src}`, "utf8");
|
|
658
665
|
}
|
|
659
|
-
|
|
660
|
-
/**
|
|
661
|
-
* Legacy `src/core/*` layout — side-effect import the AI sidecar.
|
|
662
|
-
*
|
|
663
|
-
* @param cwd - Project root
|
|
664
|
-
*/
|
|
665
|
-
function ensureLegacyAiImported(cwd: string): void {
|
|
666
|
-
const candidates: ReadonlyArray<{ readonly rel: string; readonly importLine: string }> = [
|
|
667
|
-
{ rel: "src/app.ts", importLine: `import "./core/ai";\n` },
|
|
668
|
-
{ rel: "src/core/index.ts", importLine: `import "./ai";\n` },
|
|
669
|
-
];
|
|
670
|
-
for (const { rel, importLine } of candidates) {
|
|
671
|
-
const path = join(cwd, rel);
|
|
672
|
-
if (!existsSync(path)) continue;
|
|
673
|
-
const src = readFileSync(path, "utf8");
|
|
674
|
-
if (
|
|
675
|
-
/from\s+["']\.\/(?:core\/)?ai["']/.test(src) ||
|
|
676
|
-
/import\s+["']\.\/(?:core\/)?ai["']/.test(src)
|
|
677
|
-
) {
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
|
-
writeFileSync(path, `${importLine}${src}`, "utf8");
|
|
681
|
-
return;
|
|
682
|
-
}
|
|
683
|
-
}
|
package/src/cli/build.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `oke build` regenerates `src/flows/
|
|
2
|
+
* `oke build` regenerates `src/flows/index.ts` before bundling — real
|
|
3
3
|
* before/after proof, not an assumption about the wiring.
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -16,13 +16,13 @@ async function makeUnit(root: string, name: string): Promise<void> {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
describe("oke build — .adopt() barrel regeneration", () => {
|
|
19
|
-
test("real before/after: no
|
|
19
|
+
test("real before/after: no index.ts before build, real barrel content after", async () => {
|
|
20
20
|
const root = await mkdtemp(join(tmpdir(), "oke-build-adopt-"));
|
|
21
21
|
try {
|
|
22
22
|
await makeUnit(root, "main");
|
|
23
23
|
await makeUnit(root, "notes");
|
|
24
24
|
|
|
25
|
-
const generatedPath = join(root, "src/flows/
|
|
25
|
+
const generatedPath = join(root, "src/flows/index.ts");
|
|
26
26
|
await expect(readFile(generatedPath, "utf8")).rejects.toThrow();
|
|
27
27
|
|
|
28
28
|
const code = await runBuild({
|
package/src/cli/build.ts
CHANGED
|
@@ -18,16 +18,16 @@ export interface BuildOptions {
|
|
|
18
18
|
}) => Promise<{ success: boolean; logs: string }>;
|
|
19
19
|
/**
|
|
20
20
|
* Inject `.adopt()` barrel regeneration (tests). Default: real
|
|
21
|
-
* `generateAdoptBarrel` + atomic write to `<rootDir>/src/flows/
|
|
22
|
-
* (`
|
|
21
|
+
* `generateAdoptBarrel` + atomic write to `<rootDir>/src/flows/index.ts`
|
|
22
|
+
* (`index.ts.tmp` → rename). Returns the unit names written, for the
|
|
23
23
|
* status line.
|
|
24
24
|
*/
|
|
25
25
|
readonly syncAdoptBarrel?: (rootDir: string) => Promise<readonly string[]>;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Regenerate `src/flows/
|
|
30
|
-
*
|
|
29
|
+
* Regenerate `src/flows/index.ts` from every `src/flows/<unit>/` folder —
|
|
30
|
+
* pre-step for `oke build` (mirrors `oke db`'s `drizzle/`
|
|
31
31
|
* pre-step). A real file on disk, not a virtual module: identical resolution
|
|
32
32
|
* under `oke dev`'s runtime `import()` and `oke build`'s `Bun.build()`
|
|
33
33
|
* (investigated — a virtual-module Bun plugin does not resolve consistently
|
|
@@ -76,7 +76,7 @@ export async function runBuild(options: BuildOptions = {}): Promise<number> {
|
|
|
76
76
|
try {
|
|
77
77
|
const units = await syncAdoptBarrel(rootDir);
|
|
78
78
|
write(
|
|
79
|
-
`oke build: .adopt() barrel → src/flows/
|
|
79
|
+
`oke build: .adopt() barrel → src/flows/index.ts (${units.length} unit${units.length === 1 ? "" : "s"})\n`,
|
|
80
80
|
);
|
|
81
81
|
const result = await bundle({ entry, outdir, target: bunTarget });
|
|
82
82
|
if (!result.success) {
|
|
@@ -17,7 +17,18 @@ describe("isDomainSchemaWatchPath", () => {
|
|
|
17
17
|
expect(isDomainSchemaWatchPath("src/schema.decl.ts")).toBe(true);
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
+
test("matches split declare modules under schema/", () => {
|
|
21
|
+
expect(isDomainSchemaWatchPath("db/schema/links.ts")).toBe(true);
|
|
22
|
+
expect(isDomainSchemaWatchPath("src/db/schema/daily.ts")).toBe(true);
|
|
23
|
+
expect(isDomainSchemaWatchPath("src/db/schema/index.ts")).toBe(true);
|
|
24
|
+
expect(isDomainSchemaWatchPath("src/db/schema/relations.ts")).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
20
27
|
test("ignores emit output (avoids push ↔ generated feedback loop)", () => {
|
|
28
|
+
expect(isDomainSchemaWatchPath("src/db/drizzle.ts")).toBe(false);
|
|
29
|
+
expect(isDomainSchemaWatchPath("drizzle.ts")).toBe(false);
|
|
30
|
+
expect(isDomainSchemaWatchPath("src/db/drizzle/index.ts")).toBe(false);
|
|
31
|
+
expect(isDomainSchemaWatchPath("src/db/drizzle/links.ts")).toBe(false);
|
|
21
32
|
expect(isDomainSchemaWatchPath("src/db/schema.drizzle.ts")).toBe(false);
|
|
22
33
|
expect(isDomainSchemaWatchPath("schema.drizzle.ts")).toBe(false);
|
|
23
34
|
expect(isDomainSchemaWatchPath("flows/notes/schema.drizzle.tsx")).toBe(false);
|
package/src/cli/db-auto-push.ts
CHANGED
|
@@ -11,8 +11,8 @@ export const DB_AUTO_PUSH_DEBOUNCE_MS = 300;
|
|
|
11
11
|
* Whether a watched path should trigger `oke db push`.
|
|
12
12
|
*
|
|
13
13
|
* Inputs only: declaration / drizzle config / app entry. Never the emit
|
|
14
|
-
* output (`
|
|
15
|
-
* watching
|
|
14
|
+
* output (`src/db/drizzle/`) — push rewrites those files every run, so
|
|
15
|
+
* watching them loops `oke db push` forever under `oke dev`.
|
|
16
16
|
*
|
|
17
17
|
* @param filename - Relative path from the watcher (may be undefined)
|
|
18
18
|
*/
|
|
@@ -21,8 +21,10 @@ export function isDomainSchemaWatchPath(filename: string | null | undefined): bo
|
|
|
21
21
|
const normalized = filename.replace(/\\/g, "/");
|
|
22
22
|
const base = basename(normalized);
|
|
23
23
|
// Emit output — never a watch trigger (feedback loop with `oke db push`).
|
|
24
|
+
if (base === "drizzle.ts" || base === "drizzle.tsx") return false;
|
|
24
25
|
if (base === "schema.drizzle.ts" || base === "schema.drizzle.tsx") return false;
|
|
25
26
|
if (base === "schema.generated.ts" || base === "schema.generated.tsx") return false;
|
|
27
|
+
if (/(^|\/)drizzle\/.+\.tsx?$/.test(normalized)) return false;
|
|
26
28
|
if (base === "schema.ts" || base === "schema.tsx") return true;
|
|
27
29
|
if (base === "schema.decl.ts" || base === "schema.decl.tsx") return true;
|
|
28
30
|
if (base === "drizzle.config.ts" || base === "drizzle.config.js") return true;
|
|
@@ -30,6 +32,8 @@ export function isDomainSchemaWatchPath(filename: string | null | undefined): bo
|
|
|
30
32
|
if (base === "app.ts" || base === "app.tsx") return true;
|
|
31
33
|
// Nested domain schemas commonly live under src/**/schema.ts (not emit output).
|
|
32
34
|
if (/(^|\/)schema(\.decl)?\.tsx?$/.test(normalized)) return true;
|
|
35
|
+
// Split declare modules: `src/db/schema/links.ts` next to `schema/index.ts`.
|
|
36
|
+
if (/(^|\/)schema\/.+\.tsx?$/.test(normalized)) return true;
|
|
33
37
|
return false;
|
|
34
38
|
}
|
|
35
39
|
|
package/src/cli/db.test.ts
CHANGED
|
@@ -327,7 +327,7 @@ export const app = oke({ name: "plugin-only" }).plug(
|
|
|
327
327
|
const emitted = await emitAbstractSchemaPrestep(dir, loaded.config, () => {}, "test");
|
|
328
328
|
expect(emitted).toBe(true);
|
|
329
329
|
|
|
330
|
-
const generated = await readFile(join(dir, "src", "db", "
|
|
330
|
+
const generated = await readFile(join(dir, "src", "db", "drizzle", "metrics_daily.ts"), "utf8");
|
|
331
331
|
expect(generated).toContain('pgTable("metrics_daily"');
|
|
332
332
|
// Postgres dialect maps abstract integer → bigint (32-bit INTEGER is too narrow).
|
|
333
333
|
expect(generated).toContain('bigint("hits", { mode: "number" })');
|
package/src/cli/db.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* plus explicit seed runs.
|
|
4
4
|
*
|
|
5
5
|
* Distinct from `oke schema generate` (core/plugin stub tables).
|
|
6
|
-
* When abstract decls exist (`src/db/schema.
|
|
7
|
-
* not a third schema CLI.
|
|
6
|
+
* When abstract decls exist (`src/db/schema.ts` or `src/db/schema/index.ts`),
|
|
7
|
+
* emits Drizzle first — not a third schema CLI.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { resolve } from "node:path";
|
|
@@ -279,7 +279,7 @@ export async function runDb(sub: DbSubcommand, options: DbOptions = {}): Promise
|
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
/**
|
|
282
|
-
* Emit `
|
|
282
|
+
* Emit `drizzle/` from abstract decls + live plugged plugin tables.
|
|
283
283
|
*
|
|
284
284
|
* @param cwd - Project root
|
|
285
285
|
* @param config - Loaded oke config
|
|
@@ -596,9 +596,9 @@ export async function dbCli(args: readonly string[]): Promise<number> {
|
|
|
596
596
|
console.log(`oke db push|generate|migrate|seed|studio|search-backfill [--config|-c] [--env name] [--force]
|
|
597
597
|
|
|
598
598
|
Domain schema sync via drizzle-kit, plus explicit seed and hybrid-search backfill.
|
|
599
|
-
When src/db/schema.
|
|
600
|
-
|
|
601
|
-
contributions — then runs drizzle-kit.
|
|
599
|
+
When src/db/schema.ts or src/db/schema/index.ts and/or a plugged app
|
|
600
|
+
entry exists, emits src/db/drizzle/ from store.schema.table + live plugin
|
|
601
|
+
.table() contributions — then runs drizzle-kit.
|
|
602
602
|
Hand-written src/schema.ts remains supported (emit skipped if nothing to emit).
|
|
603
603
|
Not the same as \`oke schema generate\` (core/plugin stub tables).
|
|
604
604
|
|
|
@@ -90,7 +90,8 @@ try {
|
|
|
90
90
|
// `bun --hot` keeps the watcher alive on an uncaught throw, so `oke dev`
|
|
91
91
|
// would wait the full ready timeout after VaultBootError. Exit so the
|
|
92
92
|
// parent sees a dead child immediately.
|
|
93
|
-
|
|
93
|
+
const { formatFatalError } = await import("../term.ts");
|
|
94
|
+
console.error(formatFatalError(err));
|
|
94
95
|
process.exit(1);
|
|
95
96
|
}
|
|
96
97
|
const handle = createBunRuntime().serve(mod.app, {
|
|
@@ -169,7 +169,7 @@ export const entries = pgTable("entries", {
|
|
|
169
169
|
expect(pushes.length).toBeGreaterThan(before);
|
|
170
170
|
});
|
|
171
171
|
|
|
172
|
-
test("
|
|
172
|
+
test("generated drizzle.ts change does not re-trigger dbPush", async () => {
|
|
173
173
|
const cwd = await scaffoldProject();
|
|
174
174
|
const pushes: string[] = [];
|
|
175
175
|
const watcher = controlledWatcher();
|
|
@@ -221,7 +221,11 @@ export const entries = pgTable("entries", {
|
|
|
221
221
|
const before = pushes.length;
|
|
222
222
|
expect(before).toBeGreaterThanOrEqual(1);
|
|
223
223
|
|
|
224
|
-
// Simulate emit writing
|
|
224
|
+
// Simulate emit writing drizzle.ts after push — must not loop.
|
|
225
|
+
watcher.change("drizzle.ts");
|
|
226
|
+
watcher.change("src/db/drizzle.ts");
|
|
227
|
+
watcher.change("src/db/drizzle/index.ts");
|
|
228
|
+
watcher.change("src/db/drizzle/links.ts");
|
|
225
229
|
watcher.change("schema.drizzle.ts");
|
|
226
230
|
watcher.change("src/db/schema.drizzle.ts");
|
|
227
231
|
watcher.change("schema.generated.ts");
|
|
@@ -25,7 +25,7 @@ import { loadOkeConfig, resolveImages } from "./load-config.ts";
|
|
|
25
25
|
export interface DevSchemaSyncOptions {
|
|
26
26
|
/** Write output (default: stdout). */
|
|
27
27
|
readonly write?: (text: string) => void;
|
|
28
|
-
/** Skip emitting `
|
|
28
|
+
/** Skip emitting `drizzle/` (tests). */
|
|
29
29
|
readonly skipEmit?: boolean;
|
|
30
30
|
/** Injectable push (tests). Default: `runDb("push", ...)`. */
|
|
31
31
|
readonly pushFn?: (cwd: string, env: ConfigEnv) => Promise<number>;
|
package/src/cli/dev.test.ts
CHANGED
|
@@ -706,7 +706,7 @@ describe("oke dev hot reload", () => {
|
|
|
706
706
|
});
|
|
707
707
|
|
|
708
708
|
describe("oke dev syncAdoptBarrel atomic write", () => {
|
|
709
|
-
test("default path regenerates
|
|
709
|
+
test("default path regenerates index.ts and leaves no .tmp behind", async () => {
|
|
710
710
|
const dir = await mkdtemp(join(tmpdir(), "oke-dev-adopt-atomic-"));
|
|
711
711
|
await mkdir(join(dir, "src/flows/notes"), { recursive: true });
|
|
712
712
|
await Bun.write(join(dir, "src/flows/notes/index.ts"), "export {};\n");
|
|
@@ -791,9 +791,12 @@ describe("oke dev syncAdoptBarrel atomic write", () => {
|
|
|
791
791
|
});
|
|
792
792
|
|
|
793
793
|
describe("isFlowsTreeWatchPath", () => {
|
|
794
|
-
test("flows tree files trigger regen;
|
|
794
|
+
test("flows tree files trigger regen; adopt barrel does not", () => {
|
|
795
795
|
expect(isFlowsTreeWatchPath("flows/notes/[id]/get.ts")).toBe(true);
|
|
796
796
|
expect(isFlowsTreeWatchPath("flows\\notes\\list.ts")).toBe(true);
|
|
797
|
+
expect(isFlowsTreeWatchPath("flows/notes/index.ts")).toBe(true);
|
|
798
|
+
expect(isFlowsTreeWatchPath("flows/index.ts")).toBe(false);
|
|
799
|
+
expect(isFlowsTreeWatchPath("flows/index.ts.tmp")).toBe(false);
|
|
797
800
|
expect(isFlowsTreeWatchPath("flows/generated.ts")).toBe(false);
|
|
798
801
|
expect(isFlowsTreeWatchPath("flows/generated.ts.tmp")).toBe(false);
|
|
799
802
|
expect(isFlowsTreeWatchPath("core.ts")).toBe(false);
|
|
@@ -953,7 +956,7 @@ describe("oke dev Docker-first", () => {
|
|
|
953
956
|
});
|
|
954
957
|
});
|
|
955
958
|
|
|
956
|
-
describe("oke dev schema
|
|
959
|
+
describe("oke dev schema declare sync framing", () => {
|
|
957
960
|
let session: DevSession | undefined;
|
|
958
961
|
|
|
959
962
|
afterEach(() => {
|
|
@@ -981,7 +984,7 @@ describe("oke dev schema.decl sync framing", () => {
|
|
|
981
984
|
await Bun.write(join(dir, "src/app.ts"), "export {}\n");
|
|
982
985
|
// Genuine definition error: `.references()` target is undefined → emit throws.
|
|
983
986
|
await Bun.write(
|
|
984
|
-
join(dir, "src/db/schema.
|
|
987
|
+
join(dir, "src/db/schema.ts"),
|
|
985
988
|
`import { store, field } from ${JSON.stringify(OKE_INDEX)};
|
|
986
989
|
|
|
987
990
|
export const authors = store.schema.table("authors", {
|
|
@@ -1024,8 +1027,8 @@ export const posts = store.schema.table("posts", {
|
|
|
1024
1027
|
session = result.session;
|
|
1025
1028
|
|
|
1026
1029
|
const plain = writes.join("").replace(/\x1b\[[0-9;]*m/g, "");
|
|
1027
|
-
expect(plain).toContain("schema.
|
|
1028
|
-
expect(plain).toMatch(/●\s*schema\.
|
|
1030
|
+
expect(plain).toContain("schema.ts has an error");
|
|
1031
|
+
expect(plain).toMatch(/●\s*schema\.ts has an error/);
|
|
1029
1032
|
expect(plain).not.toContain("oke db push (dev) skipped");
|
|
1030
1033
|
}, 60_000);
|
|
1031
1034
|
|
|
@@ -1065,7 +1068,7 @@ export const posts = store.schema.table("posts", {
|
|
|
1065
1068
|
|
|
1066
1069
|
const plain = writes.join("").replace(/\x1b\[[0-9;]*m/g, "");
|
|
1067
1070
|
expect(plain).toContain("oke db push (dev) skipped — docker mode: oke.config.ts not found");
|
|
1068
|
-
expect(plain).not.toContain("schema.
|
|
1071
|
+
expect(plain).not.toContain("schema.ts has an error");
|
|
1069
1072
|
}, 60_000);
|
|
1070
1073
|
});
|
|
1071
1074
|
|
package/src/cli/dev.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
writeStackCredentialsCache,
|
|
28
28
|
type DeriveOptions,
|
|
29
29
|
} from "../docker/index.ts";
|
|
30
|
+
import { isAdoptBarrelRelPath } from "../compiler/generate-adopt.ts";
|
|
30
31
|
import type { Manifest } from "../manifest/types.ts";
|
|
31
32
|
import type { McpContext } from "../mcp/tools.ts";
|
|
32
33
|
import { APP_PORT, CONSOLE_PORT, DOCS_MCP_PORT, MCP_PORT } from "../runtime/types.ts";
|
|
@@ -183,9 +184,9 @@ export interface DevOptions {
|
|
|
183
184
|
readonly onDbAutoPush?: (filename: string) => void;
|
|
184
185
|
/**
|
|
185
186
|
* Injectable `.adopt()` barrel regeneration (tests). Default: real
|
|
186
|
-
* `generateAdoptBarrel` + atomic write to `<cwd>/src/flows/
|
|
187
|
-
* (`
|
|
188
|
-
* `src/flows/**` changes except `
|
|
187
|
+
* `generateAdoptBarrel` + atomic write to `<cwd>/src/flows/index.ts`
|
|
188
|
+
* (`index.ts.tmp` → rename). Runs at session start and again on
|
|
189
|
+
* `src/flows/**` changes except `index.ts` itself. Write-if-changed
|
|
189
190
|
* so the `src/` watcher + bun `--hot` do not loop.
|
|
190
191
|
*
|
|
191
192
|
* @param cwd - Project root
|
|
@@ -370,7 +371,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
|
|
|
370
371
|
const cwd = options.cwd ?? process.cwd();
|
|
371
372
|
|
|
372
373
|
// One-shot `.adopt()` barrel regen for the session — real file on disk
|
|
373
|
-
// (`src/flows/
|
|
374
|
+
// (`src/flows/index.ts`), written atomically (tmp → rename) before the
|
|
374
375
|
// entry is resolved/imported below so a freshly-added flows unit is
|
|
375
376
|
// adoptable without a hand edit. Best-effort like `syncDevSchema` below:
|
|
376
377
|
// a project with no `src/flows` yet (or a synthetic test tree) never
|
|
@@ -1036,7 +1037,7 @@ export async function runDev(options: DevOptions = {}): Promise<DevResult> {
|
|
|
1036
1037
|
}
|
|
1037
1038
|
|
|
1038
1039
|
// One-shot schema sync for the compose (`dev`) profile: emits
|
|
1039
|
-
//
|
|
1040
|
+
// drizzle/ for the active dialect, then pushes via drizzle-kit.
|
|
1040
1041
|
let schemaPushOk = options.noDbPush === true;
|
|
1041
1042
|
if (!options.noDbPush) {
|
|
1042
1043
|
const { syncDevSchema } = await import("./dev-schema-sync.ts");
|
|
@@ -1576,7 +1577,7 @@ function isBenignDevSchemaEnvSkip(message: string): boolean {
|
|
|
1576
1577
|
}
|
|
1577
1578
|
|
|
1578
1579
|
/**
|
|
1579
|
-
* Whether a caught sync/push error is a schema
|
|
1580
|
+
* Whether a caught sync/push error is a schema declare definition failure
|
|
1580
1581
|
* (import evaluation, emit, duplicate tables, bad `.references()`, …).
|
|
1581
1582
|
*
|
|
1582
1583
|
* Message text alone is insufficient — emit-time bugs often look like bare
|
|
@@ -1584,7 +1585,7 @@ function isBenignDevSchemaEnvSkip(message: string): boolean {
|
|
|
1584
1585
|
* emit frames.
|
|
1585
1586
|
*
|
|
1586
1587
|
* @param err - Caught value
|
|
1587
|
-
* @param declarePath - Resolved schema
|
|
1588
|
+
* @param declarePath - Resolved schema declare path for this project
|
|
1588
1589
|
*/
|
|
1589
1590
|
function isSchemaDeclDefinitionError(err: unknown, declarePath: string): boolean {
|
|
1590
1591
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -1600,15 +1601,15 @@ function isSchemaDeclDefinitionError(err: unknown, declarePath: string): boolean
|
|
|
1600
1601
|
|
|
1601
1602
|
/**
|
|
1602
1603
|
* True when a `src/` watcher event is a flows tree change that should
|
|
1603
|
-
* regenerate `
|
|
1604
|
+
* regenerate `src/flows/index.ts` (never the barrel itself, nor a leftover
|
|
1605
|
+
* `generated.ts`). Unit `index.ts` files still trigger regen.
|
|
1604
1606
|
*
|
|
1605
1607
|
* @param rel - Watcher filename (POSIX or Windows)
|
|
1606
1608
|
*/
|
|
1607
1609
|
export function isFlowsTreeWatchPath(rel: string): boolean {
|
|
1608
1610
|
const posix = rel.replace(/\\/g, "/");
|
|
1609
1611
|
if (!posix.includes("flows/")) return false;
|
|
1610
|
-
|
|
1611
|
-
if (base === "generated.ts" || base === "generated.ts.tmp") return false;
|
|
1612
|
+
if (isAdoptBarrelRelPath(posix)) return false;
|
|
1612
1613
|
return true;
|
|
1613
1614
|
}
|
|
1614
1615
|
|
|
@@ -1638,7 +1639,8 @@ async function formatDevSchemaSyncFailure(
|
|
|
1638
1639
|
const { resolveEmitPaths } = await import("../elements/store/emit-drizzle.ts");
|
|
1639
1640
|
const { declarePath } = resolveEmitPaths(cwd);
|
|
1640
1641
|
if (isSchemaDeclDefinitionError(err, declarePath)) {
|
|
1641
|
-
|
|
1642
|
+
const declareName = declarePath.split(/[/\\]/).pop() ?? "schema.ts";
|
|
1643
|
+
return formatStatusLine(`${declareName} has an error — ${msg}`, color, "error");
|
|
1642
1644
|
}
|
|
1643
1645
|
|
|
1644
1646
|
const label = context === "watch" ? "oke db push (schema change)" : "oke db push (dev)";
|
|
@@ -12,8 +12,9 @@ const DRIZZLE_CONFIG_TEMPLATE = `import { defineConfig } from "drizzle-kit";
|
|
|
12
12
|
* Domain schema sync for \`oke db push|generate|migrate\`.
|
|
13
13
|
*
|
|
14
14
|
* SQL is PostgreSQL-only (postgres / pglite). Domain tables come from
|
|
15
|
-
* \`src/db/schema.
|
|
16
|
-
* pre-step of \`oke db\` /
|
|
15
|
+
* \`src/db/schema.ts\` (single file) or \`src/db/schema/index.ts\`
|
|
16
|
+
* (folder) → \`src/db/drizzle/\` (emitted as a pre-step of \`oke db\` /
|
|
17
|
+
* \`oke dev\`). \`schema.ts\` wins when both exist.
|
|
17
18
|
*
|
|
18
19
|
* System / auth / plugin stubs are separate: \`oke schema generate\` →
|
|
19
20
|
* \`.oke/schema/oke.ts\` (gitignored with \`.oke/\`). Re-run after adding plugins.
|
|
@@ -22,7 +23,7 @@ const DRIZZLE_CONFIG_TEMPLATE = `import { defineConfig } from "drizzle-kit";
|
|
|
22
23
|
*/
|
|
23
24
|
export default defineConfig({
|
|
24
25
|
dialect: "postgresql",
|
|
25
|
-
schema: "./src/db/
|
|
26
|
+
schema: "./src/db/drizzle/index.ts",
|
|
26
27
|
out: "./src/db/migrations",
|
|
27
28
|
// Domain tables live in public. Schema \`oke\` (RLS) and \`oke_console\` are engine-owned.
|
|
28
29
|
schemaFilter: ["public"],
|
package/src/cli/start.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { resolve } from "node:path";
|
|
6
6
|
import { APP_PORT } from "../runtime/types.ts";
|
|
7
|
+
import { formatFatalError } from "../term.ts";
|
|
7
8
|
|
|
8
9
|
/** Options for {@link runStart}. */
|
|
9
10
|
export interface StartOptions {
|
|
@@ -77,7 +78,7 @@ export async function runStart(options: StartOptions = {}): Promise<number> {
|
|
|
77
78
|
await import(entry);
|
|
78
79
|
return 0;
|
|
79
80
|
} catch (err) {
|
|
80
|
-
console.error(
|
|
81
|
+
console.error(formatFatalError(err));
|
|
81
82
|
return 1;
|
|
82
83
|
}
|
|
83
84
|
}
|
package/src/client/create.ts
CHANGED
|
@@ -194,14 +194,14 @@ export function flattenRoutes(
|
|
|
194
194
|
function proxy(transport: Transport, path: readonly string[], ctx: ProxyCtx): unknown {
|
|
195
195
|
const invoke = async (input?: unknown, callOpts?: CallOpts): Promise<ClientResult> => {
|
|
196
196
|
if (path.length < 2) {
|
|
197
|
+
const message = `Incomplete path: api.${path.join(".") || "?"}(…)`;
|
|
197
198
|
return attachPager(
|
|
198
199
|
{
|
|
199
200
|
data: null,
|
|
200
201
|
error: {
|
|
201
202
|
code: "TransportError" as const,
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
},
|
|
203
|
+
message,
|
|
204
|
+
data: { message },
|
|
205
205
|
},
|
|
206
206
|
},
|
|
207
207
|
(nextInput) => invoke(nextInput, callOpts),
|