create-oke 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.
Files changed (59) hide show
  1. package/README.md +2 -2
  2. package/package.json +2 -2
  3. package/src/ai-setup/apply.ts +123 -31
  4. package/src/ai-setup/catalog.ts +3 -3
  5. package/src/ai-setup/from-pref.ts +1 -1
  6. package/src/ai-setup/prompts.ts +1 -1
  7. package/src/cli.test.ts +183 -142
  8. package/src/cli.ts +190 -32
  9. package/src/create-defaults.test.ts +21 -15
  10. package/src/create-defaults.ts +33 -13
  11. package/src/customize-flow.test.ts +30 -35
  12. package/src/customize-flow.ts +68 -229
  13. package/src/drivers-catalog.ts +42 -48
  14. package/src/local-okengine.test.ts +50 -41
  15. package/src/local-okengine.ts +28 -14
  16. package/src/locales.test.ts +128 -0
  17. package/src/locales.ts +321 -0
  18. package/src/scaffold.ts +277 -33
  19. package/src/templates.ts +2 -8
  20. package/src/transform.test.ts +102 -38
  21. package/src/transform.ts +378 -132
  22. package/templates/advanced/.env.example +23 -3
  23. package/templates/advanced/README.md +1 -1
  24. package/templates/advanced/drizzle.config.ts +10 -13
  25. package/templates/advanced/oke.config.ts +21 -62
  26. package/templates/advanced/package.json +3 -1
  27. package/templates/advanced/src/app.ts +3 -10
  28. package/templates/advanced/src/core.ts +58 -0
  29. package/templates/advanced/src/db/schema.decl.ts +1 -1
  30. package/templates/advanced/src/db/seed/index.ts +2 -2
  31. package/templates/advanced/src/flows/notes/index.ts +3 -15
  32. package/templates/advanced/src/locales/index.ts +6 -0
  33. package/templates/advanced/tests/advanced.test.ts +1 -1
  34. package/templates/advanced/tsconfig.json +3 -0
  35. package/templates/standard/.env.example +23 -3
  36. package/templates/standard/README.md +1 -1
  37. package/templates/standard/drizzle.config.ts +10 -13
  38. package/templates/standard/oke.config.ts +18 -60
  39. package/templates/standard/package.json +3 -1
  40. package/templates/standard/src/app.ts +3 -10
  41. package/templates/standard/src/core.ts +55 -0
  42. package/templates/standard/src/db/schema.decl.ts +1 -1
  43. package/templates/standard/src/db/seed/index.ts +2 -2
  44. package/templates/standard/src/flows/notes/index.ts +3 -12
  45. package/templates/standard/src/locales/index.ts +6 -0
  46. package/templates/standard/tests/standard.test.ts +1 -1
  47. package/templates/standard/tsconfig.json +3 -0
  48. package/templates/advanced/src/core/channels.ts +0 -13
  49. package/templates/advanced/src/core/gates.ts +0 -12
  50. package/templates/advanced/src/core/index.ts +0 -12
  51. package/templates/advanced/src/core/store.ts +0 -8
  52. package/templates/advanced/src/core/vault.ts +0 -7
  53. package/templates/advanced/src/locales/ar.ts +0 -18
  54. package/templates/standard/src/core/channels.ts +0 -13
  55. package/templates/standard/src/core/gates.ts +0 -12
  56. package/templates/standard/src/core/index.ts +0 -12
  57. package/templates/standard/src/core/store.ts +0 -5
  58. package/templates/standard/src/core/vault.ts +0 -7
  59. package/templates/standard/src/locales/ar.ts +0 -18
@@ -0,0 +1,55 @@
1
+ /**
2
+ * App core — element wiring loaded by `import "@/core"` from `app.ts`.
3
+ *
4
+ * Order matches how you usually extend the starter:
5
+ * locales → store → gate → vault → channel → (AI via `oke ai setup`).
6
+ * Keep this file until a section outgrows it, then split by element.
7
+ */
8
+
9
+ import "@/locales";
10
+
11
+ import { channel, gate, store, vault } from "okengine";
12
+ import { z } from "zod";
13
+ import * as schema from "@/db/schema.decl";
14
+
15
+ // --- Store -------------------------------------------------------------------
16
+
17
+ /** SQL store for Notes (`schema.decl`). Drivers: pglite locally · postgres in docker. */
18
+ export const db = store.sql("app", { schema });
19
+
20
+ // --- Gate --------------------------------------------------------------------
21
+
22
+ /**
23
+ * Write policy for notes. Starter allows everyone — swap in real auth scopes:
24
+ *
25
+ * ```ts
26
+ * export const notesWrite = gate.policy("notes:write", ({ auth }) =>
27
+ * auth.scopes.has("notes:write"),
28
+ * );
29
+ * ```
30
+ */
31
+ export const notesWrite = gate.policy("notes:write", () => true);
32
+
33
+ // --- Vault -------------------------------------------------------------------
34
+
35
+ /** HMAC secret for outbound note webhooks (`fx.vault.get` on create). */
36
+ export const webhookSecret = vault.secret("APP_WEBHOOK_SECRET", {
37
+ description: "HMAC secret for outbound note webhooks",
38
+ dev: "dev-webhook-secret-change-me",
39
+ });
40
+
41
+ // --- Channel -----------------------------------------------------------------
42
+
43
+ const mail = channel.email({ from: "Notes <notes@localhost>" });
44
+
45
+ /** Fired when a note is created (console driver locally · SMTP in docker). */
46
+ export const noteCreatedMail = mail.template("note-created", {
47
+ locales: ["en"],
48
+ schema: z.object({
49
+ id: z.string(),
50
+ title: z.string(),
51
+ }),
52
+ });
53
+
54
+ // --- AI ----------------------------------------------------------------------
55
+ // `oke ai setup` / create-oke --ai appends models here.
@@ -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/db/schema.generated.ts` for the active dialect by `oke db` / `oke dev`.
5
+ * `src/db/schema.drizzle.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,6 +1,6 @@
1
1
  import { defineSeed, type Fx } from "okengine";
2
- import { db } from "../../core";
3
- import { notes } from "../schema.decl";
2
+ import { db } from "@/core";
3
+ import { notes } from "@/db/schema.decl";
4
4
 
5
5
  /**
6
6
  * Seed data — run explicitly with `oke db seed` (never at boot).
@@ -1,8 +1,8 @@
1
1
  import { on, flow, http, gate, fail } from "okengine";
2
2
  import { eq, isNull } from "drizzle-orm";
3
3
 
4
- import { db, noteCreatedMail, webhookSecret } from "../../core";
5
- import { notes } from "../../db/schema.decl";
4
+ import { db, noteCreatedMail, webhookSecret } from "@/core";
5
+ import { notes } from "@/db/schema.decl";
6
6
  import { NoteCreateIn, NoteIdIn, NoteListOut, NoteOut, NotFound } from "./shapes";
7
7
  import { noteCreated } from "./signals";
8
8
 
@@ -14,7 +14,6 @@ export const list = on(
14
14
  http.get("/notes").gate(gate.public),
15
15
  flow("notes.list", {
16
16
  out: NoteListOut,
17
- effects: { reads: ["sql:app"] },
18
17
  do: async (_input, fx) => {
19
18
  const rows = await fx.store(db).select().from(notes).where(isNull(notes.archivedAt));
20
19
  const sorted = [...rows].sort((a, b) => Number(b.createdAt) - Number(a.createdAt));
@@ -37,14 +36,9 @@ export const create = on(
37
36
  flow("notes.create", {
38
37
  in: NoteCreateIn,
39
38
  out: NoteOut,
40
- effects: {
41
- writes: ["sql:app"],
42
- emits: ["note-created"],
43
- secrets: ["APP_WEBHOOK_SECRET"],
44
- },
45
39
  do: async (input, fx) => {
46
40
  // Prove Vault is wired (local: env/dev fallback).
47
- fx.vault(webhookSecret);
41
+ await fx.vault.get(webhookSecret);
48
42
  const id = fx.id();
49
43
  const createdAt = fx.clock.now();
50
44
  await fx.store(db).insert(notes).values({
@@ -73,7 +67,6 @@ export const get = on(
73
67
  in: NoteIdIn,
74
68
  out: NoteOut,
75
69
  errors: { NotFound },
76
- effects: { reads: ["sql:app"] },
77
70
  do: async (input, fx) => {
78
71
  const row = await fx.store(db).findById(notes, input.id);
79
72
  if (!row) return fail("NotFound", { id: input.id });
@@ -95,7 +88,6 @@ export const archive = on(
95
88
  in: NoteIdIn,
96
89
  out: NoteOut,
97
90
  errors: { NotFound },
98
- effects: { reads: ["sql:app"], writes: ["sql:app"] },
99
91
  do: async (input, fx) => {
100
92
  const row = await fx.store(db).findById(notes, input.id);
101
93
  if (!row) return fail("NotFound", { id: input.id });
@@ -120,7 +112,6 @@ export const archive = on(
120
112
  export const onCreated = on(
121
113
  noteCreated,
122
114
  flow("notes.onCreated", {
123
- effects: { sends: ["note-created"] },
124
115
  do: async (payload, fx) => {
125
116
  await fx.send(noteCreatedMail, {
126
117
  to: "you@localhost",
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Message catalogs — imported once from `src/core.ts`.
3
+ * Add a sibling import when you add a locale file.
4
+ */
5
+
6
+ import "./en";
@@ -1,6 +1,6 @@
1
1
  import { afterAll, beforeAll, expect, test } from "bun:test";
2
2
  import { createTestApp, type TestApp } from "okengine/test";
3
- import { app, type App } from "../src/app";
3
+ import { app, type App } from "@/app";
4
4
 
5
5
  let t: TestApp<App>;
6
6
 
@@ -6,6 +6,9 @@
6
6
  "moduleDetection": "force",
7
7
  "types": ["bun"],
8
8
  "moduleResolution": "bundler",
9
+ "paths": {
10
+ "@/*": ["./src/*"]
11
+ },
9
12
  "allowImportingTsExtensions": true,
10
13
  "verbatimModuleSyntax": true,
11
14
  "noEmit": true,
@@ -1,13 +0,0 @@
1
- import { channel } from "okengine";
2
- import { z } from "zod";
3
-
4
- const mail = channel.email({ from: "Notes <notes@localhost>" });
5
-
6
- /** Email when a note is created (console driver locally · SMTP in docker). */
7
- export const noteCreatedMail = mail.template("note-created", {
8
- locales: ["en", "ar"],
9
- schema: z.object({
10
- id: z.string(),
11
- title: z.string(),
12
- }),
13
- });
@@ -1,12 +0,0 @@
1
- import { gate } from "okengine";
2
-
3
- /**
4
- * Starter keeps HTTP public. Replace with real policies when you add auth:
5
- *
6
- * ```ts
7
- * export const canWriteNotes = gate.policy("notes:write", ({ auth }) =>
8
- * auth.scopes.has("notes:write"),
9
- * );
10
- * ```
11
- */
12
- export const notesWrite = gate.policy("notes:write", () => true);
@@ -1,12 +0,0 @@
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,8 +0,0 @@
1
- import { store } from "okengine";
2
- import * as schema from "../db/schema.decl";
3
-
4
- /** App SQL store — tables from {@link schema}. */
5
- export const db = store.sql("app", { schema });
6
-
7
- /** Object storage for note attachments (fs locally · s3 in docker). */
8
- export const files = store.files("uploads");
@@ -1,7 +0,0 @@
1
- import { vault } from "okengine";
2
-
3
- /** Shared webhook HMAC secret — prove Vault wiring on note create. */
4
- export const webhookSecret = vault.secret("APP_WEBHOOK_SECRET", {
5
- description: "HMAC secret for outbound note webhooks",
6
- dev: "dev-webhook-secret-change-me",
7
- });
@@ -1,18 +0,0 @@
1
- import { defineLocale } from "okengine";
2
- import type { MessagesFor } from "okengine";
3
- import type { en } from "./en";
4
-
5
- const ar = {
6
- errors: {
7
- notFound: "غير موجود",
8
- unauthorized: "غير مصرح",
9
- },
10
- notes: {
11
- created: "تم إنشاء الملاحظة «{title}».",
12
- archived: "تم أرشفة الملاحظة.",
13
- empty: "لا توجد ملاحظات نشطة بعد.",
14
- count: "{count, plural, zero {لا ملاحظات} one {ملاحظة واحدة} two {ملاحظتان} few {# ملاحظات} many {# ملاحظة} other {# ملاحظة}}",
15
- },
16
- } satisfies MessagesFor<typeof en>;
17
-
18
- defineLocale("ar", ar);
@@ -1,13 +0,0 @@
1
- import { channel } from "okengine";
2
- import { z } from "zod";
3
-
4
- const mail = channel.email({ from: "Notes <notes@localhost>" });
5
-
6
- /** Email when a note is created (console driver locally · SMTP in docker). */
7
- export const noteCreatedMail = mail.template("note-created", {
8
- locales: ["en", "ar"],
9
- schema: z.object({
10
- id: z.string(),
11
- title: z.string(),
12
- }),
13
- });
@@ -1,12 +0,0 @@
1
- import { gate } from "okengine";
2
-
3
- /**
4
- * Starter keeps HTTP public. Replace with real policies when you add auth:
5
- *
6
- * ```ts
7
- * export const canWriteNotes = gate.policy("notes:write", ({ auth }) =>
8
- * auth.scopes.has("notes:write"),
9
- * );
10
- * ```
11
- */
12
- export const notesWrite = gate.policy("notes:write", () => true);
@@ -1,12 +0,0 @@
1
- /**
2
- * Core wiring — stores, gates, vault, channels (and AI when configured).
3
- */
4
-
5
- export { db } 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 +0,0 @@
1
- import { store } from "okengine";
2
- import * as schema from "../db/schema.decl";
3
-
4
- /** App SQL store — tables from {@link schema}. */
5
- export const db = store.sql("app", { schema });
@@ -1,7 +0,0 @@
1
- import { vault } from "okengine";
2
-
3
- /** Shared webhook HMAC secret — prove Vault wiring on note create. */
4
- export const webhookSecret = vault.secret("APP_WEBHOOK_SECRET", {
5
- description: "HMAC secret for outbound note webhooks",
6
- dev: "dev-webhook-secret-change-me",
7
- });
@@ -1,18 +0,0 @@
1
- import { defineLocale } from "okengine";
2
- import type { MessagesFor } from "okengine";
3
- import type { en } from "./en";
4
-
5
- const ar = {
6
- errors: {
7
- notFound: "غير موجود",
8
- unauthorized: "غير مصرح",
9
- },
10
- notes: {
11
- created: "تم إنشاء الملاحظة «{title}».",
12
- archived: "تم أرشفة الملاحظة.",
13
- empty: "لا توجد ملاحظات نشطة بعد.",
14
- count: "{count, plural, zero {لا ملاحظات} one {ملاحظة واحدة} two {ملاحظتان} few {# ملاحظات} many {# ملاحظة} other {# ملاحظة}}",
15
- },
16
- } satisfies MessagesFor<typeof en>;
17
-
18
- defineLocale("ar", ar);