create-oke 0.9.1 → 0.10.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.
Files changed (56) hide show
  1. package/README.md +7 -5
  2. package/package.json +2 -3
  3. package/src/agents-md.ts +1 -1
  4. package/src/ai-setup/apply.ts +66 -23
  5. package/src/ai-setup/catalog.ts +1316 -35
  6. package/src/ai-setup/detect-ollama.ts +48 -0
  7. package/src/ai-setup/from-pref.ts +32 -0
  8. package/src/ai-setup/prompts.ts +430 -486
  9. package/src/ai-setup/recommend.ts +118 -101
  10. package/src/cli.test.ts +36 -7
  11. package/src/cli.ts +21 -16
  12. package/src/customize-flow.test.ts +43 -13
  13. package/src/customize-flow.ts +136 -61
  14. package/src/drivers-catalog.ts +29 -12
  15. package/src/local-okengine.test.ts +59 -0
  16. package/src/local-okengine.ts +137 -0
  17. package/src/scaffold.ts +1 -1
  18. package/src/transform.test.ts +54 -32
  19. package/src/transform.ts +46 -9
  20. package/src/wizard-select.ts +5 -6
  21. package/templates/advanced/.github/workflows/ci.yml +24 -0
  22. package/templates/advanced/.vscode/settings.json +15 -0
  23. package/templates/advanced/README.md +15 -4
  24. package/templates/advanced/drizzle.config.ts +6 -4
  25. package/templates/advanced/oke.config.ts +6 -2
  26. package/templates/advanced/package.json +5 -2
  27. package/templates/advanced/src/app.ts +2 -14
  28. package/templates/advanced/src/core/index.ts +12 -0
  29. package/templates/advanced/src/{core.ts → core/store.ts} +1 -1
  30. package/templates/advanced/src/db/migrations/.gitkeep +0 -0
  31. package/templates/{standard/src → advanced/src/db}/schema.decl.ts +1 -1
  32. package/templates/advanced/src/{seed → db/seed}/index.ts +2 -2
  33. package/templates/advanced/src/flows/notes/index.ts +2 -4
  34. package/templates/advanced/tests/advanced.test.ts +10 -9
  35. package/templates/advanced/tsconfig.json +23 -0
  36. package/templates/standard/.github/workflows/ci.yml +24 -0
  37. package/templates/standard/.vscode/settings.json +15 -0
  38. package/templates/standard/README.md +20 -9
  39. package/templates/standard/drizzle.config.ts +6 -4
  40. package/templates/standard/oke.config.ts +4 -0
  41. package/templates/standard/package.json +5 -2
  42. package/templates/standard/src/app.ts +2 -14
  43. package/templates/standard/src/core/index.ts +12 -0
  44. package/templates/standard/src/{core.ts → core/store.ts} +1 -1
  45. package/templates/standard/src/db/migrations/.gitkeep +0 -0
  46. package/templates/{advanced/src → standard/src/db}/schema.decl.ts +1 -1
  47. package/templates/standard/src/{seed → db/seed}/index.ts +2 -2
  48. package/templates/standard/src/flows/notes/index.ts +2 -4
  49. package/templates/standard/tests/standard.test.ts +13 -10
  50. package/templates/standard/tsconfig.json +23 -0
  51. /package/templates/advanced/src/{channels.ts → core/channels.ts} +0 -0
  52. /package/templates/advanced/src/{gates.ts → core/gates.ts} +0 -0
  53. /package/templates/advanced/src/{vault.ts → core/vault.ts} +0 -0
  54. /package/templates/standard/src/{channels.ts → core/channels.ts} +0 -0
  55. /package/templates/standard/src/{gates.ts → core/gates.ts} +0 -0
  56. /package/templates/standard/src/{vault.ts → core/vault.ts} +0 -0
@@ -11,8 +11,23 @@ import { resolveTemplateDir } from "./templates.ts";
11
11
  import { applyCreateAnswers, upsertAiDrivers } from "./transform.ts";
12
12
  import type { EnvDriverPins } from "./create-defaults.ts";
13
13
 
14
- function templateConfig(): string {
15
- return readFileSync(join(resolveTemplateDir("standard"), "oke.config.ts"), "utf8");
14
+ function templateConfig(id: "standard" | "advanced" = "advanced"): string {
15
+ return readFileSync(join(resolveTemplateDir(id), "oke.config.ts"), "utf8");
16
+ }
17
+
18
+ /** Evaluate `oke.config.ts` source via identity `defineConfig` (no okengine import). */
19
+ function evalConfig(source: string): {
20
+ drivers?: {
21
+ ai?: unknown;
22
+ channel?: { ai?: unknown; email?: unknown };
23
+ };
24
+ images?: Record<string, string>;
25
+ } {
26
+ const body = source
27
+ .replace(/^import\s+[\s\S]*?from\s+["'][^"']+["'];?\s*/m, "")
28
+ .replace(/export\s+default\s+/, "return ");
29
+ const defineConfig = <T>(c: T): T => c;
30
+ return new Function("defineConfig", body)(defineConfig) as ReturnType<typeof evalConfig>;
16
31
  }
17
32
 
18
33
  function defaultsWithIndex(indexLocal: string, indexDocker: string) {
@@ -67,39 +82,46 @@ describe("upsertAiDrivers", () => {
67
82
  };
68
83
 
69
84
  test("inserts drivers.ai as sibling of channel (not inside email)", () => {
70
- const next = upsertAiDrivers(templateConfig(), ollamaPins);
71
- expect(next).toMatch(/^ {4}ai:\s*\{/m);
72
- expect(next).toContain('local: "ollama"');
73
- const channel = next.match(/^ {4}channel:\s*\{[\s\S]*?\n {4}\},?\n/m)?.[0] ?? "";
74
- expect(channel).toContain("email:");
75
- expect(channel).not.toContain("ai:");
85
+ const next = upsertAiDrivers(templateConfig("advanced"), ollamaPins);
86
+ const config = evalConfig(next);
87
+ expect(config.drivers?.ai).toEqual(ollamaPins);
88
+ expect(config.drivers?.channel?.ai).toBeUndefined();
89
+ expect(config.drivers?.channel?.email).toBeDefined();
76
90
  });
77
91
 
78
92
  test("applyCreateAnswers with ai pins keeps top-level drivers.ai", () => {
79
- const next = applyCreateAnswers(
80
- templateConfig(),
81
- toCreateDefaults({
82
- template: "advanced",
83
- profile: "docker-ready",
84
- drivers: {
85
- store: {
86
- sql: pinsDockerReady("libsql", "postgres", "memory"),
87
- kv: pinsLocalOnly("memory", "redis", "memory"),
88
- files: pinsLocalOnly("fs", "s3", "memory"),
89
- index: null,
93
+ const llamaPins = pinsDockerReady("openai-compatible", "openai-compatible", "mock");
94
+ for (const id of ["standard", "advanced"] as const) {
95
+ const next = applyCreateAnswers(
96
+ templateConfig(id),
97
+ toCreateDefaults({
98
+ template: "advanced",
99
+ profile: "docker-ready",
100
+ drivers: {
101
+ store: {
102
+ sql: pinsDockerReady("libsql", "postgres", "memory"),
103
+ kv: pinsLocalOnly("memory", "redis", "memory"),
104
+ files: pinsLocalOnly("fs", "s3", "memory"),
105
+ index: null,
106
+ },
107
+ signal: pinsLocalOnly("memory", "redis", "memory"),
108
+ clock: pinsLocalOnly("memory", "file", "frozen"),
109
+ vault: pinsLocalOnly("env", "openbao", "memory"),
110
+ channel: { email: pinsLocalOnly("console", "smtp", "console") },
111
+ ai: llamaPins,
90
112
  },
91
- signal: pinsLocalOnly("memory", "redis", "memory"),
92
- clock: pinsLocalOnly("memory", "file", "frozen"),
93
- vault: pinsLocalOnly("env", "openbao", "memory"),
94
- channel: { email: pinsLocalOnly("console", "smtp", "console") },
95
- ai: ollamaPins,
96
- },
97
- ai: { enabled: true, provider: "ollama", driver: "ollama" },
98
- }),
99
- );
100
- expect(next).toMatch(/^ {4}ai:\s*\{/m);
101
- expect(next).toContain('ai: "ollama/ollama:latest"');
102
- const channel = next.match(/^ {4}channel:\s*\{[\s\S]*?\n {4}\},?\n/m)?.[0] ?? "";
103
- expect(channel).not.toContain("ai:");
113
+ ai: { enabled: true, provider: "llama-cpp", driver: "openai-compatible" },
114
+ }),
115
+ );
116
+ const config = evalConfig(next);
117
+ expect(config.drivers?.ai, id).toEqual({
118
+ local: "openai-compatible",
119
+ docker: "openai-compatible",
120
+ test: "mock",
121
+ prod: "openai-compatible",
122
+ });
123
+ expect(config.drivers?.channel?.ai, id).toBeUndefined();
124
+ expect(config.images?.ai, id).toBe("ghcr.io/ggml-org/llama.cpp:server-b10290");
125
+ }
104
126
  });
105
127
  });
package/src/transform.ts CHANGED
@@ -5,7 +5,8 @@
5
5
  * Exactly:
6
6
  * 1. `package.json` `"name"` → the user-provided project name
7
7
  * 2. `package.json` `"okengine": "file:../.."` → an installable reference
8
- * (absolute `file:<okengine-root>` in the monorepo; registry version otherwise)
8
+ * (staged `file:~/.oke/create-oke/okengine` in the monorepo not the
9
+ * workspace root; registry version otherwise)
9
10
  * 3. Drop monorepo-only files that import paths outside the source tree
10
11
  * (today: `tests/docker.test.ts`)
11
12
  * 4. Optional `--sql` / wizard choice → Drizzle dialect + `store.sql` pins
@@ -14,7 +15,14 @@
14
15
  import { readFileSync } from "node:fs";
15
16
  import { join } from "node:path";
16
17
  import type { CreateDefaults, EnvDriverPins } from "./create-defaults.ts";
17
- import { DEFAULT_IMAGES } from "./drivers-catalog.ts";
18
+ import {
19
+ DEFAULT_IMAGES,
20
+ LLAMA_CPP_IMAGE,
21
+ OLLAMA_IMAGE,
22
+ SGLANG_IMAGE,
23
+ VLLM_IMAGE,
24
+ } from "./drivers-catalog.ts";
25
+ import { materializeLocalOkengineDependency } from "./local-okengine.ts";
18
26
  import { packageRoot } from "./templates.ts";
19
27
 
20
28
  /** SQL store drivers selectable at scaffold time. */
@@ -49,13 +57,15 @@ export type ScaffoldPackageJson = {
49
57
  /**
50
58
  * Resolve the `okengine` dependency string written into the scaffolded package.json.
51
59
  *
52
- * - Monorepo / local: `file:<absolute-okengine-root>` (installable; not `file:../..`)
60
+ * - Monorepo / local: staged publish-shaped `file:` package (no workspaces /
61
+ * no monorepo `devDependencies` — those pull drizzle-zod and trip Bun’s RC
62
+ * peer check)
53
63
  * - Published create-oke: the version of this package (kept in lockstep with okengine)
54
64
  *
55
65
  * @param localOkengineRoot - Absolute path when available
56
66
  */
57
67
  export function resolveOkengineDependency(localOkengineRoot: string | null): string {
58
- if (localOkengineRoot) return `file:${localOkengineRoot}`;
68
+ if (localOkengineRoot) return materializeLocalOkengineDependency(localOkengineRoot);
59
69
  const pkgPath = join(packageRoot(), "package.json");
60
70
  const pkg = JSON.parse(readFileSync(pkgPath, "utf8")) as { version: string };
61
71
  return pkg.version;
@@ -269,11 +279,14 @@ export function upsertAiDrivers(source: string, pins: EnvDriverPins): string {
269
279
  * Format an env driver map literal.
270
280
  *
271
281
  * @param pins - Pins
272
- * @param indentLevel - Indent depth (2 spaces each)
282
+ * @param indentLevel - Indent of the map key (`signal` → 2, `sql`/`email` → 3;
283
+ * two spaces each). Inner fields use `indentLevel + 1`; the closing `}`
284
+ * aligns with the key. An off-by-one here made `email`'s `},` look like
285
+ * `channel`'s close, so {@link upsertAiDrivers} nested `ai` under `channel`.
273
286
  */
274
287
  function formatEnvMap(pins: EnvDriverPins, indentLevel: number): string {
275
- const pad = " ".repeat(indentLevel);
276
- const close = " ".repeat(indentLevel - 1);
288
+ const pad = " ".repeat(indentLevel + 1);
289
+ const close = " ".repeat(indentLevel);
277
290
  return `{
278
291
  ${pad}local: "${pins.local}",
279
292
  ${pad}docker: "${pins.docker}",
@@ -329,13 +342,37 @@ function syncImages(source: string, defaults: CreateDefaults): string {
329
342
  if (d.store.index?.docker === "meilisearch") {
330
343
  pin("store.index", DEFAULT_IMAGES["store.index"]!);
331
344
  }
332
- if (d.ai && (d.ai.docker === "ollama" || d.ai.local === "ollama")) {
333
- pin("ai", DEFAULT_IMAGES.ai!);
345
+ if (
346
+ d.ai &&
347
+ (d.ai.docker === "ollama" ||
348
+ d.ai.local === "ollama" ||
349
+ d.ai.docker === "openai-compatible" ||
350
+ d.ai.local === "openai-compatible")
351
+ ) {
352
+ pin("ai", aiImageForDefaults(defaults));
334
353
  }
335
354
 
336
355
  return replaceImagesBlock(source, images);
337
356
  }
338
357
 
358
+ /**
359
+ * Resolve `images.ai` from create-defaults provider / driver pins.
360
+ *
361
+ * @param defaults - Create answers
362
+ */
363
+ function aiImageForDefaults(defaults: CreateDefaults): string {
364
+ const provider = defaults.ai.provider;
365
+ if (provider === "ollama" || defaults.drivers.ai?.local === "ollama") {
366
+ return OLLAMA_IMAGE;
367
+ }
368
+ if (provider === "vllm") return VLLM_IMAGE;
369
+ if (provider === "sglang") return SGLANG_IMAGE;
370
+ if (provider === "llama-cpp" || defaults.drivers.ai?.local === "openai-compatible") {
371
+ return LLAMA_CPP_IMAGE;
372
+ }
373
+ return DEFAULT_IMAGES.ai!;
374
+ }
375
+
339
376
  /**
340
377
  * Parse role→image pins from an `images` block.
341
378
  *
@@ -1,16 +1,15 @@
1
1
  /**
2
- * Shared Clack select helpers with optional Back.
2
+ * Shared Clack select helpers with optional Back.
3
3
  */
4
4
 
5
5
  import { isCancel, select } from "@clack/prompts";
6
6
 
7
- /** Sentinel — go back one wizard step (shown as "Back" in selects). */
7
+ /** Sentinel — go back one wizard step (shown as "Back" in selects). */
8
8
  export const WIZARD_BACK = "__back__" as const;
9
- /** {@link WIZARD_BACK} type alias. */
10
9
  export type WizardBack = typeof WIZARD_BACK;
11
10
 
12
11
  /**
13
- * Append "Back" when allowed — pure helper for tests + {@link selectWithBack}.
12
+ * Append "Back" when allowed — pure helper for tests + {@link selectWithBack}.
14
13
  *
15
14
  * @param options - Choices
16
15
  * @param allowBack - Whether to append Back
@@ -20,11 +19,11 @@ export function withBackOption(
20
19
  allowBack: boolean,
21
20
  ): readonly { value: string; label: string; hint?: string }[] {
22
21
  if (!allowBack) return options;
23
- return [...options, { value: WIZARD_BACK, label: "Back" }];
22
+ return [...options, { value: WIZARD_BACK, label: "Back" }];
24
23
  }
25
24
 
26
25
  /**
27
- * Select with an optional trailing "Back" option.
26
+ * Select with an optional trailing "Back" option.
28
27
  *
29
28
  * @param message - Prompt
30
29
  * @param options - Choices (without back)
@@ -0,0 +1,24 @@
1
+ # Minimal CI for an okengine app — typecheck + test on every push/PR.
2
+ # Expand later (lint, docker, deploy); keep this green from day one.
3
+
4
+ name: CI
5
+
6
+ on:
7
+ push:
8
+ pull_request:
9
+
10
+ jobs:
11
+ check:
12
+ name: typecheck · test
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v6
16
+ - uses: oven-sh/setup-bun@v2.2.0
17
+ with:
18
+ bun-version: latest
19
+ - name: Install
20
+ run: bun install
21
+ - name: Typecheck
22
+ run: bun run typecheck
23
+ - name: Test
24
+ run: bun test
@@ -0,0 +1,15 @@
1
+ {
2
+ "files.exclude": {
3
+ "**/dist": true,
4
+ "**/.next": true,
5
+ "**/.turbo": true,
6
+ "**/bun.lock": true,
7
+ "**/node_modules": true,
8
+ "**/next-env.d.ts": true,
9
+ "**/worker-configuration.d.ts": true
10
+ },
11
+ "editor.formatOnSave": true,
12
+ "files.associations": {
13
+ "*.css": "tailwindcss"
14
+ }
15
+ }
@@ -2,12 +2,12 @@
2
2
 
3
3
  Docker-ready [okengine](https://oke.omqkhafi.dev) **Notes** starter — same domain as
4
4
  `standard`, plus files attach, a daily digest clock, and an AI summarize stub.
5
+ Scaffold, not a finished product: keep the Flows you want, replace the rest.
5
6
 
6
7
  ```bash
7
8
  bun install
8
9
  oke mode docker # seeded by create-oke recommended path
9
- oke dev
10
- oke db seed # essential welcome note + local/docker sample notes
10
+ oke dev # auto db push; asks once whether to seed
11
11
  ```
12
12
 
13
13
  | Surface | URL |
@@ -24,14 +24,25 @@ oke db seed # essential welcome note + local/docker sample notes
24
24
  | POST | `/notes/:id/summarize` | `notes.summarize` — `fx.ask` when AI is configured |
25
25
  | clock | `every("1d")` | `notes.digest` — active note count |
26
26
 
27
- Configure AI with `oke ai setup` or `create-oke --ai` (writes `src/ai.ts` + `drivers.ai`).
27
+ Configure AI with `oke ai setup` or `create-oke --ai` (writes `src/core/ai.ts` + `drivers.ai`).
28
28
  Without AI, summarize returns a local excerpt (`via: "fallback"`).
29
29
 
30
+ ## Included vs you build
31
+
32
+ | Ships ready | You still own |
33
+ | ----------- | ------------- |
34
+ | standard Notes surface + attach / digest / summarize | your domain beyond notes |
35
+ | files Store pin + digest Clock | production file policy and schedules |
36
+ | AI summarize stub (fallback without AI) | real prompts / models via `oke ai setup` |
37
+ | `.github/workflows/ci.yml` (typecheck + test) | lint, docker, deploy when you need them |
38
+
30
39
  ## Layout extras
31
40
 
32
41
  | Path | Role |
33
42
  | ---- | ---- |
34
- | `src/seed/` | `oke db seed` essential + dev sample notes |
43
+ | `src/core/` | `store` (db + files) · gates · vault · channels |
44
+ | `src/db/` | schema · seed · migrations |
45
+ | `.github/workflows/ci.yml` | `bun run typecheck` + `bun test` on push/PR |
35
46
 
36
47
  ## Drivers
37
48
 
@@ -5,17 +5,19 @@ import { defineConfig } from "drizzle-kit";
5
5
  *
6
6
  * Dialect comes from OKE's `store.sql` driver map (resolved by the CLI and
7
7
  * injected as `OKE_DRIZZLE_DIALECT`) — never inferred from `DATABASE_URL`
8
- * presence. `src/schema.generated.ts` is emitted from `src/schema.decl.ts`
8
+ * presence. `src/db/schema.generated.ts` is emitted from `src/db/schema.decl.ts`
9
9
  * for the active dialect as a pre-step of `oke db` / `oke dev`.
10
10
  *
11
- * Versioned prod migrations land in `./drizzle` — never mix with `.oke/`.
11
+ * Versioned prod migrations land in `./src/db/migrations` — never mix with `.oke/`.
12
12
  */
13
13
  const dialect = (process.env.OKE_DRIZZLE_DIALECT ?? "sqlite") as "sqlite" | "postgresql";
14
14
 
15
15
  export default defineConfig({
16
16
  dialect,
17
- schema: "./src/schema.generated.ts",
18
- out: "./drizzle",
17
+ schema: "./src/db/schema.generated.ts",
18
+ out: "./src/db/migrations",
19
+ // Core runtime tables (oke_crons, …) are created by drivers — not domain schema.
20
+ tablesFilter: ["!oke_*"],
19
21
  dbCredentials: {
20
22
  url:
21
23
  dialect === "postgresql"
@@ -5,6 +5,10 @@ import { defineConfig } from "okengine/config";
5
5
  * Recommended create path seeds `.oke/mode` as docker.
6
6
  */
7
7
  export default defineConfig({
8
+ db: {
9
+ declare: "src/db/schema.decl.ts",
10
+ generated: "src/db/schema.generated.ts",
11
+ },
8
12
  drivers: {
9
13
  store: {
10
14
  sql: {
@@ -66,7 +70,7 @@ export default defineConfig({
66
70
  prod: "smtp",
67
71
  },
68
72
  },
69
- // Opt in: create-oke --ai / oke ai setup writes drivers.ai + src/ai.ts
73
+ // Opt in: create-oke --ai / oke ai setup writes drivers.ai + src/core/ai.ts
70
74
  },
71
75
  images: {
72
76
  "store.sql": "postgres:18-alpine",
@@ -76,7 +80,7 @@ export default defineConfig({
76
80
  "channel.email": "axllent/mailpit:v1.22.3",
77
81
  vault: "openbao/openbao:2.6.1",
78
82
  // "store.index": "getmeili/meilisearch:v1.37",
79
- // ai: "ollama/ollama:latest",
83
+ // ai: "ghcr.io/ggml-org/llama.cpp:server-b10290", // or ollama/ollama:0.32.6
80
84
  },
81
85
  i18n: { locales: ["en", "ar"], default: "en", dir: { ar: "rtl" } },
82
86
  });
@@ -5,13 +5,16 @@
5
5
  "type": "module",
6
6
  "dependencies": {
7
7
  "okengine": "file:../../../..",
8
- "drizzle-orm": "^1.0.0-rc.4",
8
+ "drizzle-orm": "1.0.0-rc.4",
9
9
  "zod": "^4.4.3"
10
10
  },
11
11
  "devDependencies": {
12
- "drizzle-kit": "^1.0.0-rc.4"
12
+ "@types/bun": "latest",
13
+ "drizzle-kit": "1.0.0-rc.4",
14
+ "typescript": "^7.0.2"
13
15
  },
14
16
  "scripts": {
17
+ "typecheck": "tsc --noEmit",
15
18
  "test": "bun test",
16
19
  "dev": "oke dev"
17
20
  }
@@ -1,7 +1,4 @@
1
- import { db, files } from "./core";
2
- import "./gates";
3
- import "./vault";
4
- import "./channels";
1
+ import { db, files, noteCreatedMail, webhookSecret } from "./core";
5
2
  import "./locales/en";
6
3
  import "./locales/ar";
7
4
 
@@ -9,22 +6,13 @@ import { oke } from "okengine";
9
6
  import * as main from "./flows/main";
10
7
  import * as notes from "./flows/notes";
11
8
  import { noteCreated } from "./flows/notes/signals";
12
- import { noteCreatedMail } from "./channels";
13
- import { webhookSecret } from "./vault";
14
9
 
15
10
  export const app = oke({
16
11
  name: "notes",
12
+ stores: [db, files],
17
13
  secrets: [webhookSecret],
18
14
  signals: [noteCreated],
19
15
  channel: { templates: [noteCreatedMail] },
20
16
  }).adopt({ main, notes });
21
17
 
22
18
  export type App = typeof app;
23
-
24
- Object.assign(app.$options, {
25
- env: "test",
26
- stores: [db, files],
27
- secrets: [webhookSecret],
28
- signals: [noteCreated],
29
- channel: { templates: [noteCreatedMail] },
30
- });
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Core wiring — stores, gates, vault, channels (and AI when configured).
3
+ */
4
+
5
+ export { db, files } from "./store";
6
+ export { notesWrite } from "./gates";
7
+ export { webhookSecret } from "./vault";
8
+ export { noteCreatedMail } from "./channels";
9
+
10
+ import "./gates";
11
+ import "./vault";
12
+ import "./channels";
@@ -1,5 +1,5 @@
1
1
  import { store } from "okengine";
2
- import * as schema from "./schema.decl";
2
+ import * as schema from "../db/schema.decl";
3
3
 
4
4
  /** App SQL store — tables from {@link schema}. */
5
5
  export const db = store.sql("app", { schema });
File without changes
@@ -2,7 +2,7 @@ import { store, field, id, now } from "okengine";
2
2
 
3
3
  /**
4
4
  * Notes domain — abstract declarations, emitted to
5
- * `src/schema.generated.ts` for the active dialect by `oke db` / `oke dev`.
5
+ * `src/db/schema.generated.ts` for the active dialect by `oke db` / `oke dev`.
6
6
  */
7
7
  export const notes = store.schema.table("notes", {
8
8
  id: field.text().primaryKey().defaultFn(id),
@@ -1,12 +1,12 @@
1
1
  import { defineSeed, type Fx } from "okengine";
2
- import { db } from "../core";
2
+ import { db } from "../../core";
3
3
  import { notes } from "../schema.decl";
4
4
 
5
5
  /**
6
6
  * Seed data — run explicitly with `oke db seed` (never at boot).
7
7
  *
8
8
  * Categories: `essential` (every env) · `dev` (local|docker) · `prod` (prod only).
9
- * For multi-file composition (`src/seed/essential/*.ts` + arrays here), see
9
+ * For multi-file composition (`src/db/seed/essential/*.ts` + arrays here), see
10
10
  * Store docs → Seeding.
11
11
  */
12
12
 
@@ -1,10 +1,8 @@
1
1
  import { on, flow, http, every, gate, fail } from "okengine";
2
2
  import { eq, isNull } from "drizzle-orm";
3
3
 
4
- import { db, files } from "../../core";
5
- import { notes } from "../../schema.decl";
6
- import { webhookSecret } from "../../vault";
7
- import { noteCreatedMail } from "../../channels";
4
+ import { db, files, noteCreatedMail, webhookSecret } from "../../core";
5
+ import { notes } from "../../db/schema.decl";
8
6
  import {
9
7
  NoteAttachIn,
10
8
  NoteAttachOut,
@@ -13,30 +13,31 @@ afterAll(async () => {
13
13
  });
14
14
 
15
15
  test("boots — health flow is named main.health", async () => {
16
- const { data, error } = await t.api.main.health({});
16
+ const { data, error } = await t.api.main!.health!({});
17
17
  expect(error).toBeNull();
18
18
  expect(data).toEqual({ ok: true });
19
19
  });
20
20
 
21
21
  test("notes create → attach → summarize fallback → archive", async () => {
22
- const created = await t.api.notes.create({
22
+ const created = await t.api.notes!.create!({
23
23
  title: "Advanced",
24
24
  body: "Body long enough to exercise attach and summarize paths in the advanced starter.",
25
25
  });
26
26
  expect(created.error).toBeNull();
27
- const id = created.data!.id;
27
+ const id = (created.data as { id: string }).id;
28
28
 
29
29
  await t.signals.drain();
30
30
 
31
- const attached = await t.api.notes.attach({ id, text: "attachment body" });
31
+ const attached = await t.api.notes!.attach!({ id, text: "attachment body" });
32
32
  expect(attached.error).toBeNull();
33
- expect(attached.data?.key).toBe(`notes/${id}/attachment.txt`);
33
+ expect((attached.data as { key: string }).key).toBe(`notes/${id}/attachment.txt`);
34
34
 
35
- const summary = await t.api.notes.summarize({ id });
35
+ const summary = await t.api.notes!.summarize!({ id });
36
36
  expect(summary.error).toBeNull();
37
- expect(summary.data?.via).toBe("fallback");
38
- expect(summary.data?.summary.length).toBeGreaterThan(0);
37
+ const out = summary.data as { via: string; summary: string };
38
+ expect(out.via).toBe("fallback");
39
+ expect(out.summary.length).toBeGreaterThan(0);
39
40
 
40
- const archived = await t.api.notes.archive({ id });
41
+ const archived = await t.api.notes!.archive!({ id });
41
42
  expect(archived.error).toBeNull();
42
43
  });
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["ESNext"],
4
+ "target": "ESNext",
5
+ "module": "ESNext",
6
+ "moduleDetection": "force",
7
+ "types": ["bun"],
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "verbatimModuleSyntax": true,
11
+ "noEmit": true,
12
+ "rewriteRelativeImportExtensions": true,
13
+ "strict": true,
14
+ "skipLibCheck": true,
15
+ "noFallthroughCasesInSwitch": true,
16
+ "noUncheckedIndexedAccess": true,
17
+ "noImplicitOverride": true,
18
+ "noUnusedLocals": true,
19
+ "noUnusedParameters": true,
20
+ "erasableSyntaxOnly": true
21
+ },
22
+ "include": ["src/**/*.ts", "tests/**/*.ts", "oke.config.ts", "drizzle.config.ts"]
23
+ }
@@ -0,0 +1,24 @@
1
+ # Minimal CI for an okengine app — typecheck + test on every push/PR.
2
+ # Expand later (lint, docker, deploy); keep this green from day one.
3
+
4
+ name: CI
5
+
6
+ on:
7
+ push:
8
+ pull_request:
9
+
10
+ jobs:
11
+ check:
12
+ name: typecheck · test
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v6
16
+ - uses: oven-sh/setup-bun@v2.2.0
17
+ with:
18
+ bun-version: latest
19
+ - name: Install
20
+ run: bun install
21
+ - name: Typecheck
22
+ run: bun run typecheck
23
+ - name: Test
24
+ run: bun test
@@ -0,0 +1,15 @@
1
+ {
2
+ "files.exclude": {
3
+ "**/dist": true,
4
+ "**/.next": true,
5
+ "**/.turbo": true,
6
+ "**/bun.lock": true,
7
+ "**/node_modules": true,
8
+ "**/next-env.d.ts": true,
9
+ "**/worker-configuration.d.ts": true
10
+ },
11
+ "editor.formatOnSave": true,
12
+ "files.associations": {
13
+ "*.css": "tailwindcss"
14
+ }
15
+ }