okengine 0.9.1 → 0.10.1
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/manifest.v1.schema.json +9 -1
- package/package.json +3 -3
- package/site/content/docs/console/runs.mdx +10 -0
- package/site/content/docs/console/traces.mdx +4 -0
- package/site/content/docs/elements/ai.mdx +54 -32
- package/site/content/docs/elements/channel.mdx +97 -9
- package/site/content/docs/elements/clock.mdx +1 -0
- package/site/content/docs/elements/flow.mdx +42 -8
- package/site/content/docs/get-started/basic-usage.mdx +5 -1
- package/site/content/docs/get-started/installation.mdx +9 -5
- package/site/content/docs/meta.json +2 -0
- package/site/content/docs/providers/azure-redis.mdx +85 -0
- package/site/content/docs/providers/cockroachdb.mdx +86 -0
- package/site/content/docs/providers/digitalocean-caching.mdx +88 -0
- package/site/content/docs/providers/dragonfly-cloud.mdx +82 -0
- package/site/content/docs/providers/elasticache.mdx +92 -0
- package/site/content/docs/providers/index.mdx +101 -0
- package/site/content/docs/providers/memorystore.mdx +89 -0
- package/site/content/docs/providers/meta.json +20 -0
- package/site/content/docs/providers/neon.mdx +115 -0
- package/site/content/docs/providers/redis-cloud.mdx +93 -0
- package/site/content/docs/providers/supabase.mdx +117 -0
- package/site/content/docs/providers/upstash.mdx +91 -0
- package/site/content/docs/providers/yugabytedb.mdx +95 -0
- package/site/content/docs/recipes/caddy.mdx +128 -0
- package/site/content/docs/recipes/cockroachdb.mdx +127 -0
- package/site/content/docs/recipes/dragonfly.mdx +125 -0
- package/site/content/docs/recipes/index.mdx +146 -0
- package/site/content/docs/recipes/llama-cpp.mdx +153 -0
- package/site/content/docs/recipes/mailpit.mdx +136 -0
- package/site/content/docs/recipes/meilisearch.mdx +139 -0
- package/site/content/docs/recipes/meta.json +31 -0
- package/site/content/docs/recipes/ollama.mdx +142 -0
- package/site/content/docs/recipes/openbao.mdx +141 -0
- package/site/content/docs/recipes/pgdog.mdx +137 -0
- package/site/content/docs/recipes/postgres.mdx +136 -0
- package/site/content/docs/recipes/redis.mdx +130 -0
- package/site/content/docs/recipes/rustfs.mdx +148 -0
- package/site/content/docs/recipes/sglang.mdx +105 -0
- package/site/content/docs/recipes/supabase-docker.mdx +145 -0
- package/site/content/docs/recipes/timescale.mdx +129 -0
- package/site/content/docs/recipes/traefik.mdx +136 -0
- package/site/content/docs/recipes/valkey.mdx +121 -0
- package/site/content/docs/recipes/vllm.mdx +106 -0
- package/site/content/docs/recipes/yugabytedb.mdx +135 -0
- package/site/content/docs/reference/cli.md +42 -6
- package/site/content/docs/reference/configuration.mdx +5 -3
- package/site/content/docs/reference/environment-variables.mdx +12 -12
- package/site/content/docs/reference/fx.mdx +38 -4
- package/src/cli/ai-setup/ai-setup.test.ts +32 -12
- package/src/cli/ai-setup/apply.ts +65 -23
- package/src/cli/ai-setup/catalog.ts +1316 -35
- package/src/cli/ai-setup/detect-ollama.ts +47 -0
- package/src/cli/ai-setup/index.ts +44 -7
- package/src/cli/ai-setup/prompts.ts +430 -486
- package/src/cli/ai-setup/recommend.test.ts +150 -50
- package/src/cli/ai-setup/recommend.ts +118 -101
- package/src/cli/ask-seed.test.ts +68 -0
- package/src/cli/ask-seed.ts +79 -0
- package/src/cli/db-seed-redact.test.ts +25 -0
- package/src/cli/db-seed.ts +36 -7
- package/src/cli/db.test.ts +1 -1
- package/src/cli/db.ts +2 -2
- package/src/cli/dev-app-runner.ts +6 -0
- package/src/cli/dev-controls.test.ts +107 -0
- package/src/cli/dev-controls.ts +354 -0
- package/src/cli/dev-schema-sync.ts +12 -4
- package/src/cli/dev.test.ts +50 -30
- package/src/cli/dev.ts +633 -66
- package/src/cli/docker-cli.test.ts +15 -0
- package/src/cli/drizzle-env.test.ts +40 -6
- package/src/cli/drizzle-env.ts +22 -1
- package/src/cli/ensure-drizzle-config.ts +6 -4
- package/src/cli/hero-meta.test.ts +13 -1
- package/src/cli/hero-meta.ts +28 -6
- package/src/cli/index.ts +5 -0
- package/src/cli/registry.ts +24 -0
- package/src/cli/replay.test.ts +105 -0
- package/src/cli/replay.ts +206 -0
- package/src/cli/start.ts +12 -1
- package/src/compiler/effects-infer.ts +37 -8
- package/src/compiler/extract.test.ts +125 -0
- package/src/compiler/extract.ts +31 -6
- package/src/config/index.ts +3 -3
- package/src/console/server/claim.ts +3 -1
- package/src/console/ui/dist/assets/{index-ClX0ZCe5.js → index-D-9KFce5.js} +2 -2
- package/src/console/ui/dist/assets/panel-overview-CW0CQEc2.js +1 -0
- package/src/console/ui/dist/assets/panel-runs-BxuyDI3x.js +1 -0
- package/src/console/ui/dist/assets/{panel-signals-Dsxdu_AR.js → panel-signals-BXofbj7v.js} +1 -1
- package/src/console/ui/dist/assets/{panel-store-hy7O8HOs.js → panel-store-B8mH-arU.js} +1 -1
- package/src/console/ui/dist/assets/{panel-traces-CBiAe5go.js → panel-traces-DSA-ZWnf.js} +1 -1
- package/src/console/ui/dist/index.html +1 -1
- package/src/console/ui/overview/slo.ts +31 -3
- package/src/console/ui/overview/types.ts +6 -0
- package/src/console/ui/runs/errors.test.ts +53 -0
- package/src/console/ui/runs/errors.ts +88 -0
- package/src/console/ui/runs/index.ts +8 -0
- package/src/console/ui/runs/search.ts +16 -0
- package/src/console/ui/shell/panels/runs/RunsPanel.tsx +72 -1
- package/src/docker/ai-model-status.test.ts +101 -0
- package/src/docker/ai-model-status.ts +276 -0
- package/src/docker/compose-health.test.ts +115 -0
- package/src/docker/compose-health.ts +234 -0
- package/src/docker/compose.ts +24 -8
- package/src/docker/derive.ts +35 -9
- package/src/docker/docker.test.ts +304 -12
- package/src/docker/helpers.ts +56 -0
- package/src/docker/index.ts +52 -0
- package/src/docker/ollama-pull.ts +232 -0
- package/src/docker/recipes/cockroach.ts +24 -0
- package/src/docker/recipes/index.ts +32 -0
- package/src/docker/recipes/llama-cpp.ts +273 -0
- package/src/docker/recipes/ollama.ts +19 -23
- package/src/docker/recipes/pgdog.ts +4 -1
- package/src/docker/recipes/postgres.ts +5 -18
- package/src/docker/recipes/sglang.ts +55 -0
- package/src/docker/recipes/supabase.ts +21 -0
- package/src/docker/recipes/timescale.ts +18 -0
- package/src/docker/recipes/vllm.ts +44 -0
- package/src/docker/recipes/yugabyte.ts +23 -0
- package/src/docker/stack-id.test.ts +22 -0
- package/src/docker/stack-id.ts +38 -4
- package/src/docker/types.ts +15 -0
- package/src/drivers/ai-ollama.integration.test.ts +7 -4
- package/src/drivers/signal-engine.ts +10 -0
- package/src/drivers/signal-postgres.ts +24 -2
- package/src/drivers/signal-types.ts +10 -0
- package/src/elements/channel/fallback.test.ts +91 -0
- package/src/elements/channel/hard-bounce.test.ts +82 -0
- package/src/elements/channel/injection.test.ts +153 -0
- package/src/elements/channel/locale-ar.test.ts +102 -0
- package/src/elements/channel/otp-delivery.ts +5 -2
- package/src/elements/channel/receipts-ledger.test.ts +100 -0
- package/src/elements/channel/runtime.ts +24 -1
- package/src/elements/channel/test-helpers.ts +60 -0
- package/src/elements/channel/whatsapp-compliance.test.ts +124 -0
- package/src/elements/store/cache.test.ts +28 -0
- package/src/elements/store/cache.ts +1 -1
- package/src/elements/store/emit-drizzle.ts +43 -9
- package/src/elements/store/runtime.ts +1 -1
- package/src/elements/store/schema-decl.test.ts +2 -0
- package/src/elements/store/schema-decl.ts +15 -0
- package/src/elements/store/seed.ts +1 -1
- package/src/index.ts +1 -0
- package/src/kernel/app.ts +131 -10
- package/src/kernel/boot-bind/signal.ts +6 -2
- package/src/kernel/boot-bind/store.ts +2 -1
- package/src/kernel/boot.test.ts +6 -5
- package/src/kernel/boot.ts +113 -10
- package/src/kernel/compensate.test.ts +131 -0
- package/src/kernel/correlation.test.ts +108 -0
- package/src/kernel/effect-timing.test.ts +82 -0
- package/src/kernel/effects-stamping.test.ts +207 -0
- package/src/kernel/errors.ts +11 -0
- package/src/kernel/flow.ts +38 -0
- package/src/kernel/fx-runs.test.ts +49 -0
- package/src/kernel/fx.test.ts +14 -0
- package/src/kernel/fx.ts +153 -14
- package/src/manifest/sql-resource.ts +41 -0
- package/src/manifest/types.ts +8 -2
- package/src/runs/collect.ts +3 -0
- package/src/runs/export-otlp.ts +64 -0
- package/src/runs/index.ts +16 -0
- package/src/runs/parquet.ts +9 -0
- package/src/runs/types.ts +5 -0
- package/src/runs/window.test.ts +68 -0
- package/src/runs/window.ts +148 -0
- package/src/runtime/boot-warn.test.ts +45 -0
- package/src/runtime/boot-warn.ts +17 -0
- package/src/runtime/dev-request-log.ts +5 -5
- package/src/term.test.ts +95 -10
- package/src/term.ts +369 -14
- package/src/console/ui/dist/assets/panel-overview-DONDzBd2.js +0 -1
- package/src/console/ui/dist/assets/panel-runs-D5zf-D9c.js +0 -1
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
TemplateRef,
|
|
16
16
|
FlowRef,
|
|
17
17
|
} from "../manifest/types.ts";
|
|
18
|
+
import { sqlTableRef } from "../manifest/sql-resource.ts";
|
|
18
19
|
|
|
19
20
|
/** Minimal ESTree-shaped nodes produced by oxc-parser. */
|
|
20
21
|
export interface AstNode {
|
|
@@ -54,6 +55,7 @@ export interface InferBinding {
|
|
|
54
55
|
| "template"
|
|
55
56
|
| "flow"
|
|
56
57
|
| "embed"
|
|
58
|
+
| "table"
|
|
57
59
|
| "unknown";
|
|
58
60
|
/** Resolved resource / name. */
|
|
59
61
|
readonly ref: string;
|
|
@@ -103,9 +105,20 @@ const READ_METHODS = new Set([
|
|
|
103
105
|
"findMany",
|
|
104
106
|
"findFirst",
|
|
105
107
|
"find",
|
|
108
|
+
"list",
|
|
106
109
|
]);
|
|
107
110
|
|
|
108
|
-
const WRITE_METHODS = new Set([
|
|
111
|
+
const WRITE_METHODS = new Set([
|
|
112
|
+
"insert",
|
|
113
|
+
"set",
|
|
114
|
+
"delete",
|
|
115
|
+
"increment",
|
|
116
|
+
"update",
|
|
117
|
+
"upsert",
|
|
118
|
+
"log",
|
|
119
|
+
"put",
|
|
120
|
+
"putImage",
|
|
121
|
+
]);
|
|
109
122
|
|
|
110
123
|
/** Methods whose first argument is a table / collection identifier. */
|
|
111
124
|
const TABLE_ARG_METHODS = new Set([
|
|
@@ -203,7 +216,7 @@ export function inferEffects(options: InferEffectsOptions): InferredEffects {
|
|
|
203
216
|
// Skip incomplete chains (`select` before `.from`, `insert` before table) —
|
|
204
217
|
// the sibling call that carries the table arg records the real resource.
|
|
205
218
|
const leaf = resolved.methods[resolved.methods.length - 1]!;
|
|
206
|
-
const hasTable = tableFromStoreChain(call) !== undefined;
|
|
219
|
+
const hasTable = tableFromStoreChain(call, options.bindings) !== undefined;
|
|
207
220
|
if (!hasTable && (leaf === "select" || leaf === "insert" || leaf === "update")) {
|
|
208
221
|
continue;
|
|
209
222
|
}
|
|
@@ -352,11 +365,14 @@ export function storeResourceFromCall(
|
|
|
352
365
|
const storeBinding = resolveBinding(storeArg, bindings);
|
|
353
366
|
const facet = storeBinding?.facet ?? "sql";
|
|
354
367
|
|
|
355
|
-
const table = tableFromStoreChain(call);
|
|
368
|
+
const table = tableFromStoreChain(call, bindings);
|
|
356
369
|
|
|
357
370
|
if (table) {
|
|
358
371
|
return {
|
|
359
|
-
|
|
372
|
+
// sql:<table> is the shared naming convention with the kernel's
|
|
373
|
+
// runtime capability gate (see ../manifest/sql-resource.ts) — table
|
|
374
|
+
// args only ever occur on sql-facet methods in practice.
|
|
375
|
+
resource: (facet === "sql" ? sqlTableRef(table) : `${facet}:${table}`) as ResourceRef,
|
|
360
376
|
methods: chain.methods,
|
|
361
377
|
};
|
|
362
378
|
}
|
|
@@ -382,11 +398,21 @@ export function storeResourceFromCall(
|
|
|
382
398
|
}
|
|
383
399
|
|
|
384
400
|
/**
|
|
385
|
-
* Walk an `fx.store(…).a().b(table)` chain and return the table
|
|
401
|
+
* Walk an `fx.store(…).a().b(table)` chain and return the declared table
|
|
402
|
+
* name — resolved through a registered `table` binding (the real string
|
|
403
|
+
* passed to `store.schema.table(name, …)`) when the argument is one, so a
|
|
404
|
+
* JS binding named differently from its declared table (`const notesTable
|
|
405
|
+
* = store.schema.table("notes", …)`) still resolves to `"notes"`, matching
|
|
406
|
+
* what the kernel reads off the live table object at call time. Falls back
|
|
407
|
+
* to the raw identifier text otherwise.
|
|
386
408
|
*
|
|
387
409
|
* @param call - Any call in the chain
|
|
410
|
+
* @param bindings - Scope bindings
|
|
388
411
|
*/
|
|
389
|
-
function tableFromStoreChain(
|
|
412
|
+
function tableFromStoreChain(
|
|
413
|
+
call: CallExpression,
|
|
414
|
+
bindings: ReadonlyMap<string, InferBinding>,
|
|
415
|
+
): string | undefined {
|
|
390
416
|
let current: AstNode | undefined = call;
|
|
391
417
|
while (current && current.type === "CallExpression") {
|
|
392
418
|
const c = current as CallExpression;
|
|
@@ -394,8 +420,11 @@ function tableFromStoreChain(call: CallExpression): string | undefined {
|
|
|
394
420
|
if (!link || link.rootMethod !== "store") break;
|
|
395
421
|
const leaf = link.methods[link.methods.length - 1]!;
|
|
396
422
|
if (TABLE_ARG_METHODS.has(leaf)) {
|
|
397
|
-
const
|
|
398
|
-
if (
|
|
423
|
+
const id = identifierName(c.arguments[0]);
|
|
424
|
+
if (id) {
|
|
425
|
+
const binding = bindings.get(id);
|
|
426
|
+
return binding?.kind === "table" ? binding.ref : id;
|
|
427
|
+
}
|
|
399
428
|
}
|
|
400
429
|
const callee = c.callee;
|
|
401
430
|
if (callee.type === "MemberExpression") {
|
|
@@ -344,3 +344,128 @@ export const remove = mounted.remove;
|
|
|
344
344
|
expect(manifest.flows?.list?.breaking).toBe(true);
|
|
345
345
|
});
|
|
346
346
|
});
|
|
347
|
+
|
|
348
|
+
describe("extractManifest — files-store write methods", () => {
|
|
349
|
+
test("fx.store(files).put(...) infers a write, unannotated", async () => {
|
|
350
|
+
const source = `
|
|
351
|
+
import { on, flow, http, gate, store } from "okengine";
|
|
352
|
+
|
|
353
|
+
export const files = store.files("uploads");
|
|
354
|
+
|
|
355
|
+
export const attach = on(
|
|
356
|
+
http.post("/attach").gate(gate.public),
|
|
357
|
+
flow({
|
|
358
|
+
name: "notes.attach",
|
|
359
|
+
unit: "notes",
|
|
360
|
+
do: async (input, fx) => {
|
|
361
|
+
await fx.store(files).put("key", input.text);
|
|
362
|
+
return { ok: true };
|
|
363
|
+
},
|
|
364
|
+
}),
|
|
365
|
+
);
|
|
366
|
+
`;
|
|
367
|
+
const manifest = await extractFromSources({ "src/flows/attach.ts": source });
|
|
368
|
+
|
|
369
|
+
expect(manifest.flows?.["notes.attach"]?.effects?.writes).toEqual(["files:uploads"]);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
test("fx.store(files).putImage(...) infers a write, unannotated", async () => {
|
|
373
|
+
const source = `
|
|
374
|
+
import { on, flow, http, gate, store } from "okengine";
|
|
375
|
+
|
|
376
|
+
export const files = store.files("uploads");
|
|
377
|
+
|
|
378
|
+
export const attach = on(
|
|
379
|
+
http.post("/attach").gate(gate.public),
|
|
380
|
+
flow({
|
|
381
|
+
name: "notes.attachImage",
|
|
382
|
+
unit: "notes",
|
|
383
|
+
do: async (input, fx) => {
|
|
384
|
+
await fx.store(files).putImage("key", input.bytes);
|
|
385
|
+
return { ok: true };
|
|
386
|
+
},
|
|
387
|
+
}),
|
|
388
|
+
);
|
|
389
|
+
`;
|
|
390
|
+
const manifest = await extractFromSources({ "src/flows/attach.ts": source });
|
|
391
|
+
|
|
392
|
+
expect(manifest.flows?.["notes.attachImage"]?.effects?.writes).toEqual(["files:uploads"]);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test("fx.store(files).list(...) infers a read, unannotated", async () => {
|
|
396
|
+
const source = `
|
|
397
|
+
import { on, flow, every, store } from "okengine";
|
|
398
|
+
|
|
399
|
+
export const files = store.files("uploads");
|
|
400
|
+
|
|
401
|
+
export const sweep = on(
|
|
402
|
+
every("1d"),
|
|
403
|
+
flow({
|
|
404
|
+
name: "notes.sweep",
|
|
405
|
+
unit: "notes",
|
|
406
|
+
do: async (_input, fx) => {
|
|
407
|
+
const keys = await fx.store(files).list();
|
|
408
|
+
return { count: keys.length };
|
|
409
|
+
},
|
|
410
|
+
}),
|
|
411
|
+
);
|
|
412
|
+
`;
|
|
413
|
+
const manifest = await extractFromSources({ "src/flows/sweep.ts": source });
|
|
414
|
+
|
|
415
|
+
expect(manifest.flows?.["notes.sweep"]?.effects?.reads).toEqual(["files:uploads"]);
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
describe("extractManifest — channel medium binder aliasing", () => {
|
|
420
|
+
test("mail.template(...) resolves through `const mail = channel.email(...)`", async () => {
|
|
421
|
+
const source = `
|
|
422
|
+
import { on, flow, http, gate, channel } from "okengine";
|
|
423
|
+
|
|
424
|
+
const mail = channel.email({ from: "Notes <notes@localhost>" });
|
|
425
|
+
|
|
426
|
+
export const noteCreatedMail = mail.template("note-created", {
|
|
427
|
+
locales: ["en", "ar"],
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
export const onCreated = on(
|
|
431
|
+
http.post("/hook").gate(gate.public),
|
|
432
|
+
flow({
|
|
433
|
+
name: "notes.onCreated",
|
|
434
|
+
unit: "notes",
|
|
435
|
+
do: async (payload, fx) => {
|
|
436
|
+
await fx.send(noteCreatedMail, { to: "you@localhost", data: payload });
|
|
437
|
+
},
|
|
438
|
+
}),
|
|
439
|
+
);
|
|
440
|
+
`;
|
|
441
|
+
const manifest = await extractFromSources({ "src/flows/notes.ts": source });
|
|
442
|
+
|
|
443
|
+
expect(manifest.flows?.["notes.onCreated"]?.effects?.sends).toEqual(["note-created"]);
|
|
444
|
+
expect(manifest.channels?.["note-created"]?.medium).toBe("email");
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
test("sms medium binder alias carries its medium (not defaulted to email)", async () => {
|
|
448
|
+
const source = `
|
|
449
|
+
import { on, flow, http, gate, channel } from "okengine";
|
|
450
|
+
|
|
451
|
+
const otp = channel.sms({});
|
|
452
|
+
|
|
453
|
+
export const otpTemplate = otp.template("otp-code", {});
|
|
454
|
+
|
|
455
|
+
export const send = on(
|
|
456
|
+
http.post("/otp").gate(gate.public),
|
|
457
|
+
flow({
|
|
458
|
+
name: "notes.sendOtp",
|
|
459
|
+
unit: "notes",
|
|
460
|
+
do: async (payload, fx) => {
|
|
461
|
+
await fx.send(otpTemplate, { to: "+10000000000", data: payload });
|
|
462
|
+
},
|
|
463
|
+
}),
|
|
464
|
+
);
|
|
465
|
+
`;
|
|
466
|
+
const manifest = await extractFromSources({ "src/flows/otp.ts": source });
|
|
467
|
+
|
|
468
|
+
expect(manifest.flows?.["notes.sendOtp"]?.effects?.sends).toEqual(["otp-code"]);
|
|
469
|
+
expect(manifest.channels?.["otp-code"]?.medium).toBe("sms");
|
|
470
|
+
});
|
|
471
|
+
});
|
package/src/compiler/extract.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
AiModel,
|
|
15
15
|
AiPrompt,
|
|
16
16
|
Channel,
|
|
17
|
+
ChannelMedium,
|
|
17
18
|
Clock,
|
|
18
19
|
DeclaredColumn,
|
|
19
20
|
Effects,
|
|
@@ -22,6 +23,7 @@ import type {
|
|
|
22
23
|
Journey,
|
|
23
24
|
Manifest,
|
|
24
25
|
RateStrategy,
|
|
26
|
+
ResourceRef,
|
|
25
27
|
SecretContract,
|
|
26
28
|
Signal,
|
|
27
29
|
SignalDelivery,
|
|
@@ -93,6 +95,8 @@ interface ProjectScope {
|
|
|
93
95
|
string,
|
|
94
96
|
{ storeName: string; storeRef: string; unit?: string; breaking?: boolean }
|
|
95
97
|
>;
|
|
98
|
+
/** Local binding name → medium, for `const x = channel.email(…)` binders. */
|
|
99
|
+
channelMediumBindings: Map<string, ChannelMedium>;
|
|
96
100
|
}
|
|
97
101
|
|
|
98
102
|
/**
|
|
@@ -122,6 +126,7 @@ export async function extractManifest(options: ExtractManifestOptions = {}): Pro
|
|
|
122
126
|
flows: {},
|
|
123
127
|
flowExports: new Map(),
|
|
124
128
|
resources: new Map(),
|
|
129
|
+
channelMediumBindings: new Map(),
|
|
125
130
|
};
|
|
126
131
|
|
|
127
132
|
const parsed = files.map((file) => {
|
|
@@ -328,7 +333,14 @@ function collectSchemaTable(call: CallExpression, program: AstNode, scope: Proje
|
|
|
328
333
|
const entry = { name: tableName, columns };
|
|
329
334
|
scope.schemaTables.set(tableName, entry);
|
|
330
335
|
const bindingName = enclosingConstName(call, program);
|
|
331
|
-
if (bindingName)
|
|
336
|
+
if (bindingName) {
|
|
337
|
+
scope.schemaTables.set(bindingName, entry);
|
|
338
|
+
// Declared table name, not the JS binding — resolved by
|
|
339
|
+
// tableFromStoreChain so `const notesTable = store.schema.table("notes",
|
|
340
|
+
// …)` still infers "sql:notes", matching what the kernel reads off the
|
|
341
|
+
// live table object (see ../manifest/sql-resource.ts).
|
|
342
|
+
scope.bindings.set(bindingName, { kind: "table", ref: tableName });
|
|
343
|
+
}
|
|
332
344
|
}
|
|
333
345
|
|
|
334
346
|
function attachSchemaOption(
|
|
@@ -583,6 +595,8 @@ function camelToSnakeKey(key: string): string {
|
|
|
583
595
|
.toLowerCase();
|
|
584
596
|
}
|
|
585
597
|
|
|
598
|
+
const CHANNEL_MEDIUM_METHODS = new Set(["email", "sms", "whatsapp", "push"]);
|
|
599
|
+
|
|
586
600
|
function visitDeclarationCall(call: CallExpression, program: AstNode, scope: ProjectScope): void {
|
|
587
601
|
const callee = call.callee;
|
|
588
602
|
|
|
@@ -717,7 +731,17 @@ function visitDeclarationCall(call: CallExpression, program: AstNode, scope: Pro
|
|
|
717
731
|
}
|
|
718
732
|
}
|
|
719
733
|
|
|
720
|
-
|
|
734
|
+
// channel.email(…) / .sms(…) / .whatsapp(…) / .push(…) — medium binder,
|
|
735
|
+
// usually held in a local const and called later as `mail.template(…)`.
|
|
736
|
+
if (obj === "channel" && prop && CHANNEL_MEDIUM_METHODS.has(prop)) {
|
|
737
|
+
const bindingName = enclosingConstName(call, program);
|
|
738
|
+
if (bindingName) {
|
|
739
|
+
scope.channelMediumBindings.set(bindingName, prop as ChannelMedium);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
const boundMedium = obj ? scope.channelMediumBindings.get(obj) : undefined;
|
|
744
|
+
if ((obj === "channel" && prop === "template") || (prop === "template" && boundMedium)) {
|
|
721
745
|
const templateName = stringArg(call.arguments[0]);
|
|
722
746
|
const opts = objectArg(call.arguments[1]);
|
|
723
747
|
if (templateName) {
|
|
@@ -725,7 +749,9 @@ function visitDeclarationCall(call: CallExpression, program: AstNode, scope: Pro
|
|
|
725
749
|
const locales = stringArrayProp(opts, "locales");
|
|
726
750
|
const description = stringProp(opts, "description");
|
|
727
751
|
scope.channels[templateName] = {
|
|
728
|
-
...(medium
|
|
752
|
+
...(medium
|
|
753
|
+
? { medium: medium as Channel["medium"] }
|
|
754
|
+
: { medium: boundMedium ?? "email" }),
|
|
729
755
|
...(locales ? { locales } : {}),
|
|
730
756
|
...(description ? { description } : {}),
|
|
731
757
|
};
|
|
@@ -1333,9 +1359,8 @@ function registerResourceMount(
|
|
|
1333
1359
|
const resource = baseName ? scope.resources.get(baseName) : undefined;
|
|
1334
1360
|
|
|
1335
1361
|
const unit = resource?.unit;
|
|
1336
|
-
const storeRef
|
|
1337
|
-
?
|
|
1338
|
-
: never;
|
|
1362
|
+
const storeRef: ResourceRef | undefined =
|
|
1363
|
+
resource?.storeRef !== undefined ? (resource.storeRef as ResourceRef) : undefined;
|
|
1339
1364
|
const effects: Effects | undefined = storeRef
|
|
1340
1365
|
? { reads: [storeRef], writes: [storeRef] }
|
|
1341
1366
|
: undefined;
|
package/src/config/index.ts
CHANGED
|
@@ -155,13 +155,13 @@ export interface DbConfig {
|
|
|
155
155
|
/**
|
|
156
156
|
* Abstract schema declare module (`store.schema.table` exports).
|
|
157
157
|
* When present, `oke db` emits Drizzle into {@link generated} before sync.
|
|
158
|
-
* Default `"src/schema.decl.ts"` (
|
|
158
|
+
* Default `"src/db/schema.decl.ts"` (legacy `"src/schema.decl.ts"` still resolved).
|
|
159
159
|
*/
|
|
160
160
|
readonly declare?: string;
|
|
161
161
|
/**
|
|
162
162
|
* Generated Drizzle schema path written from abstract decls.
|
|
163
|
-
* Default `"src/schema.generated.ts"
|
|
164
|
-
* at this file when using the abstract path.
|
|
163
|
+
* Default `"src/db/schema.generated.ts"` (legacy `"src/schema.generated.ts"`).
|
|
164
|
+
* Point `drizzle.config.ts` `schema` at this file when using the abstract path.
|
|
165
165
|
*/
|
|
166
166
|
readonly generated?: string;
|
|
167
167
|
/**
|
|
@@ -58,7 +58,9 @@ export function mintClaimCode(now: () => number = Date.now): ClaimCodeState {
|
|
|
58
58
|
*/
|
|
59
59
|
export function printClaimCodeOnce(
|
|
60
60
|
state: ClaimCodeState,
|
|
61
|
-
write: (line: string) => void = (line) =>
|
|
61
|
+
write: (line: string) => void = (line) => {
|
|
62
|
+
process.stdout.write(line);
|
|
63
|
+
},
|
|
62
64
|
): void {
|
|
63
65
|
if (state.printed) return;
|
|
64
66
|
state.printed = true;
|