create-oke 0.2.8 → 0.3.2

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 (142) hide show
  1. package/README.md +11 -32
  2. package/package.json +2 -4
  3. package/src/cli.test.ts +29 -117
  4. package/src/cli.ts +16 -139
  5. package/src/docs-origin.ts +2 -3
  6. package/src/index.ts +1 -2
  7. package/src/scaffold.ts +13 -31
  8. package/src/templates.ts +12 -90
  9. package/src/transform.ts +1 -1
  10. package/template/.env.example +56 -0
  11. package/{templates/standard → template}/README.md +25 -12
  12. package/template/bun.lock +845 -0
  13. package/template/drizzle.config.ts +25 -0
  14. package/{templates/full → template}/oke.config.ts +11 -7
  15. package/{templates/minimal → template}/src/core.ts +1 -1
  16. package/template/src/schema.decl.ts +12 -0
  17. package/examples/linkly/.env.example +0 -15
  18. package/examples/linkly/drizzle.config.ts +0 -11
  19. package/examples/linkly/oke.config.ts +0 -12
  20. package/examples/linkly/package.json +0 -18
  21. package/examples/linkly/src/app.ts +0 -21
  22. package/examples/linkly/src/core.ts +0 -4
  23. package/examples/linkly/src/flows/analytics/index.ts +0 -39
  24. package/examples/linkly/src/flows/links/index.ts +0 -82
  25. package/examples/linkly/src/flows/links/shapes.ts +0 -22
  26. package/examples/linkly/src/flows/links/signals.ts +0 -14
  27. package/examples/linkly/src/gates.ts +0 -10
  28. package/examples/linkly/src/schema.decl.ts +0 -39
  29. package/examples/linkly/src/schema.generated.ts +0 -30
  30. package/examples/linkly/src/schema.ts +0 -2
  31. package/examples/linkly/tests/linkly.test.ts +0 -19
  32. package/examples/notes/.env.example +0 -12
  33. package/examples/notes/drizzle.config.ts +0 -11
  34. package/examples/notes/oke.config.ts +0 -14
  35. package/examples/notes/package.json +0 -19
  36. package/examples/notes/src/app.ts +0 -13
  37. package/examples/notes/src/core.ts +0 -4
  38. package/examples/notes/src/flows/notes/index.ts +0 -58
  39. package/examples/notes/src/schema.ts +0 -9
  40. package/examples/notes/tests/notes.test.ts +0 -10
  41. package/examples/provisions/.env.example +0 -19
  42. package/examples/provisions/drizzle.config.ts +0 -11
  43. package/examples/provisions/oke.config.ts +0 -19
  44. package/examples/provisions/package.json +0 -18
  45. package/examples/provisions/src/app.ts +0 -60
  46. package/examples/provisions/src/channels.ts +0 -15
  47. package/examples/provisions/src/core.ts +0 -4
  48. package/examples/provisions/src/flows/notifications/index.ts +0 -25
  49. package/examples/provisions/src/flows/orders/index.ts +0 -97
  50. package/examples/provisions/src/flows/orders/shapes.ts +0 -21
  51. package/examples/provisions/src/flows/orders/signals.ts +0 -19
  52. package/examples/provisions/src/flows/payments/index.ts +0 -43
  53. package/examples/provisions/src/flows/payments/shapes.ts +0 -3
  54. package/examples/provisions/src/flows/payments/stripe.ts +0 -18
  55. package/examples/provisions/src/gates.ts +0 -4
  56. package/examples/provisions/src/locales/ar.ts +0 -1
  57. package/examples/provisions/src/locales/en.ts +0 -1
  58. package/examples/provisions/src/plugins/audit-schema.ts +0 -2
  59. package/examples/provisions/src/plugins/audit.ts +0 -14
  60. package/examples/provisions/src/schema.ts +0 -16
  61. package/examples/provisions/src/vault.ts +0 -16
  62. package/examples/provisions/tests/orders.test.ts +0 -19
  63. package/examples/skyport/.env.example +0 -34
  64. package/examples/skyport/drizzle.config.ts +0 -11
  65. package/examples/skyport/evals/triage.jsonl +0 -1
  66. package/examples/skyport/oke.config.ts +0 -47
  67. package/examples/skyport/oke.images.lock +0 -4
  68. package/examples/skyport/package.json +0 -19
  69. package/examples/skyport/src/ai.ts +0 -25
  70. package/examples/skyport/src/app.ts +0 -46
  71. package/examples/skyport/src/channels.ts +0 -12
  72. package/examples/skyport/src/core.ts +0 -4
  73. package/examples/skyport/src/flows/bookings/index.ts +0 -73
  74. package/examples/skyport/src/flows/bookings/shapes.ts +0 -6
  75. package/examples/skyport/src/flows/bookings/signals.ts +0 -13
  76. package/examples/skyport/src/flows/notifications/index.ts +0 -16
  77. package/examples/skyport/src/flows/payments/index.ts +0 -14
  78. package/examples/skyport/src/flows/payments/shapes.ts +0 -5
  79. package/examples/skyport/src/flows/support/index.ts +0 -50
  80. package/examples/skyport/src/flows/users/elements.ts +0 -2
  81. package/examples/skyport/src/flows/users/index.ts +0 -12
  82. package/examples/skyport/src/flows/users/shapes.ts +0 -6
  83. package/examples/skyport/src/gates.ts +0 -10
  84. package/examples/skyport/src/journeys.ts +0 -9
  85. package/examples/skyport/src/locales/ar.ts +0 -4
  86. package/examples/skyport/src/locales/en.ts +0 -4
  87. package/examples/skyport/src/plugins/audit-schema.ts +0 -7
  88. package/examples/skyport/src/plugins/audit.ts +0 -14
  89. package/examples/skyport/src/plugins/panel.tsx +0 -4
  90. package/examples/skyport/src/schema.ts +0 -25
  91. package/examples/skyport/src/vault.ts +0 -24
  92. package/examples/skyport/tests/support.test.ts +0 -16
  93. package/src/sync-templates.ts +0 -77
  94. package/templates/full/.env.example +0 -24
  95. package/templates/full/README.md +0 -65
  96. package/templates/full/drizzle.config.ts +0 -14
  97. package/templates/full/package.json +0 -18
  98. package/templates/full/src/ai.ts +0 -11
  99. package/templates/full/src/app.ts +0 -34
  100. package/templates/full/src/channels.ts +0 -9
  101. package/templates/full/src/core.ts +0 -4
  102. package/templates/full/src/flows/main/index.ts +0 -45
  103. package/templates/full/src/flows/main/shapes.ts +0 -3
  104. package/templates/full/src/flows/main/signals.ts +0 -8
  105. package/templates/full/src/gates.ts +0 -4
  106. package/templates/full/src/locales/ar.ts +0 -3
  107. package/templates/full/src/locales/en.ts +0 -3
  108. package/templates/full/src/schema.ts +0 -8
  109. package/templates/full/src/vault.ts +0 -9
  110. package/templates/full/tests/full.test.ts +0 -11
  111. package/templates/hello/.env.example +0 -9
  112. package/templates/hello/README.md +0 -40
  113. package/templates/hello/oke.config.ts +0 -3
  114. package/templates/hello/package.json +0 -14
  115. package/templates/hello/src/app.ts +0 -10
  116. package/templates/hello/src/flows/hello/index.ts +0 -27
  117. package/templates/hello/tests/hello.test.ts +0 -10
  118. package/templates/minimal/.env.example +0 -13
  119. package/templates/minimal/README.md +0 -49
  120. package/templates/minimal/drizzle.config.ts +0 -14
  121. package/templates/minimal/oke.config.ts +0 -14
  122. package/templates/minimal/package.json +0 -18
  123. package/templates/minimal/src/app.ts +0 -13
  124. package/templates/minimal/src/flows/main/index.ts +0 -49
  125. package/templates/minimal/src/schema.ts +0 -8
  126. package/templates/minimal/tests/minimal.test.ts +0 -13
  127. package/templates/standard/.env.example +0 -21
  128. package/templates/standard/drizzle.config.ts +0 -14
  129. package/templates/standard/oke.config.ts +0 -51
  130. package/templates/standard/src/core.ts +0 -4
  131. package/templates/standard/src/schema.ts +0 -9
  132. /package/{templates/standard → template}/package.json +0 -0
  133. /package/{templates/standard → template}/src/app.ts +0 -0
  134. /package/{templates/standard → template}/src/channels.ts +0 -0
  135. /package/{templates/standard → template}/src/flows/main/index.ts +0 -0
  136. /package/{templates/standard → template}/src/flows/main/shapes.ts +0 -0
  137. /package/{templates/standard → template}/src/flows/main/signals.ts +0 -0
  138. /package/{templates/standard → template}/src/gates.ts +0 -0
  139. /package/{templates/standard → template}/src/locales/ar.ts +0 -0
  140. /package/{templates/standard → template}/src/locales/en.ts +0 -0
  141. /package/{templates/standard → template}/src/vault.ts +0 -0
  142. /package/{templates/standard → template}/tests/standard.test.ts +0 -0
@@ -0,0 +1,25 @@
1
+ import { defineConfig } from "drizzle-kit";
2
+
3
+ /**
4
+ * Domain schema sync for `oke db push|generate|migrate`.
5
+ *
6
+ * Dialect comes from OKE's `store.sql` driver map (resolved by the CLI and
7
+ * injected as `OKE_DRIZZLE_DIALECT`) — never inferred from `DATABASE_URL`
8
+ * presence. `src/schema.generated.ts` is emitted from `src/schema.decl.ts`
9
+ * for the active dialect as a pre-step of `oke db` / `oke dev`.
10
+ *
11
+ * Versioned prod migrations land in `./drizzle` — never mix with `.oke/`.
12
+ */
13
+ const dialect = (process.env.OKE_DRIZZLE_DIALECT ?? "sqlite") as "sqlite" | "postgresql";
14
+
15
+ export default defineConfig({
16
+ dialect,
17
+ schema: "./src/schema.generated.ts",
18
+ out: "./drizzle",
19
+ dbCredentials: {
20
+ url:
21
+ dialect === "postgresql"
22
+ ? (process.env.DATABASE_URL ?? process.env.OKE_STORE_SQL_URL!)
23
+ : (process.env.OKE_SQLITE_URL ?? "file:.oke/app.sqlite"),
24
+ },
25
+ });
@@ -15,6 +15,12 @@ export default defineConfig({
15
15
  test: "memory",
16
16
  prod: "redis",
17
17
  },
18
+ files: {
19
+ local: "fs",
20
+ docker: "s3",
21
+ test: "memory",
22
+ prod: "s3",
23
+ },
18
24
  },
19
25
  signal: {
20
26
  local: "memory",
@@ -30,9 +36,9 @@ export default defineConfig({
30
36
  },
31
37
  vault: {
32
38
  local: "dotenv",
33
- docker: "dotenv",
39
+ docker: "openbao",
34
40
  test: "memory",
35
- prod: "sops",
41
+ prod: "openbao",
36
42
  },
37
43
  channel: {
38
44
  email: {
@@ -42,15 +48,13 @@ export default defineConfig({
42
48
  prod: "smtp",
43
49
  },
44
50
  },
45
- ai: {
46
- local: "mock",
47
- docker: "mock",
48
- test: "mock",
49
- },
50
51
  },
51
52
  images: {
52
53
  "store.sql": "postgres:18-alpine",
53
54
  "store.kv": "redis:8-alpine",
55
+ "store.files": "rustfs/rustfs:1.0.0-beta.11",
56
+ "channel.email": "axllent/mailpit:v1.22.3",
57
+ vault: "openbao/openbao:2.6.1",
54
58
  },
55
59
  i18n: { locales: ["en", "ar"], default: "en", dir: { ar: "rtl" } },
56
60
  });
@@ -1,4 +1,4 @@
1
1
  import { store } from "okengine";
2
- import * as schema from "./schema";
2
+ import * as schema from "./schema.decl";
3
3
 
4
4
  export const db = store.sql("app", { schema });
@@ -0,0 +1,12 @@
1
+ import { store, field, id, now } from "okengine";
2
+
3
+ /**
4
+ * Domain schema — abstract declarations, emitted to
5
+ * `src/schema.generated.ts` for the active dialect (sqlite in local mode,
6
+ * postgres in docker) by `oke db` / `oke dev`.
7
+ */
8
+ export const entries = store.schema.table("entries", {
9
+ id: field.text().primaryKey().defaultFn(id),
10
+ body: field.text().notNull(),
11
+ createdAt: field.integer().notNull().defaultFn(now),
12
+ });
@@ -1,15 +0,0 @@
1
- # Environment template — copy to `.env.local` for local overrides.
2
- # create-oke copies this file to `.env.local` when you scaffold a new project.
3
- #
4
- # Resolution order (first hit wins):
5
- # process.env → .env.local → docker/.env.docker → vault driver → dev fallback
6
- #
7
- # --- Infrastructure (optional) ---
8
- # This app pins postgres (store.sql) and redis (store.kv) for prod in oke.config.ts.
9
- # Local dev uses sqlite + in-memory kv; uncomment only for prod overrides.
10
-
11
- # DATABASE_URL — primary Postgres connection; from your host (Neon, RDS, …)
12
- # DATABASE_URL=postgres://user:password@host:5432/linkly
13
-
14
- # REDIS_URL — Redis/Valkey connection for store.kv; from your host (Upstash, ElastiCache, …)
15
- # REDIS_URL=redis://:password@host:6379
@@ -1,11 +0,0 @@
1
- import { defineConfig } from "drizzle-kit";
2
-
3
- /** Domain schema sync for `oke db push|generate|migrate`. */
4
- export default defineConfig({
5
- dialect: "sqlite",
6
- schema: "./src/schema.generated.ts",
7
- out: "./drizzle",
8
- dbCredentials: {
9
- url: process.env.OKE_SQLITE_URL ?? "file:.oke/app.sqlite",
10
- },
11
- });
@@ -1,12 +0,0 @@
1
- import { defineConfig } from "okengine/config";
2
-
3
- export default defineConfig({
4
- drivers: {
5
- store: {
6
- sql: { local: "sqlite", test: "memory", prod: "postgres" },
7
- kv: { local: "memory", test: "memory", prod: "redis" },
8
- },
9
- signal: { local: "memory", test: "memory", prod: "postgres" },
10
- clock: { local: "memory", test: "frozen", prod: "postgres" },
11
- },
12
- });
@@ -1,18 +0,0 @@
1
- {
2
- "name": "@oke/example-linkly",
3
- "version": "0.0.1",
4
- "private": true,
5
- "type": "module",
6
- "scripts": {
7
- "test": "bun test",
8
- "dev": "oke dev"
9
- },
10
- "dependencies": {
11
- "drizzle-orm": "^1.0.0-rc.4",
12
- "okengine": "file:../..",
13
- "zod": "^4.4.3"
14
- },
15
- "devDependencies": {
16
- "drizzle-kit": "^1.0.0-rc.4"
17
- }
18
- }
@@ -1,21 +0,0 @@
1
- import { db } from "./core";
2
- import { member, fair } from "./gates";
3
- import { linkClicked, linkStats } from "./flows/links/signals";
4
-
5
- import { oke } from "okengine";
6
- import * as links from "./flows/links";
7
- import * as analytics from "./flows/analytics";
8
-
9
- export const app = oke({ name: "linkly" }).adopt({ links, analytics });
10
-
11
- export type App = typeof app;
12
-
13
- // Spec test calls `t.api.links.report` though `report` lives in analytics.
14
- app.adopt({ links: { report: analytics.report } });
15
-
16
- Object.assign(app.$options, {
17
- env: "test",
18
- gates: [member, fair],
19
- signals: [linkClicked, linkStats],
20
- stores: [db],
21
- });
@@ -1,4 +0,0 @@
1
- import { store } from "okengine";
2
- import * as schema from "./schema";
3
-
4
- export const db = store.sql("linkly", { schema });
@@ -1,39 +0,0 @@
1
- import { on, flow, http } from "okengine";
2
- import { z } from "zod";
3
- import { eq, and } from "drizzle-orm";
4
- import { linkClicked } from "../links/signals";
5
- import { db } from "../../core";
6
- import { member } from "../../gates";
7
- import { daily } from "../../schema";
8
-
9
- on(
10
- linkClicked,
11
- flow({
12
- // a second consumer of the same signal
13
- do: async ({ code, at }, fx) => {
14
- const day = new Date(at).toISOString().slice(0, 10);
15
- const [row] = await fx
16
- .store(db)
17
- .select()
18
- .from(daily)
19
- .where(and(eq(daily.code, code), eq(daily.day, day)))
20
- .limit(1);
21
-
22
- if (row) await fx.store(db).increment(daily, row.id, "clicks");
23
- else await fx.store(db).insert(daily).values({ id: fx.id(), code, day, clicks: 1 });
24
- },
25
- }),
26
- );
27
-
28
- export const report = on(
29
- http.get("/links/:code/report").gate(member),
30
- flow({
31
- out: z.array(z.object({ day: z.string(), clicks: z.number() })),
32
- do: ({ code }, fx) =>
33
- fx
34
- .store(db)
35
- .select({ day: daily.day, clicks: daily.clicks })
36
- .from(daily)
37
- .where(eq(daily.code, code)),
38
- }),
39
- );
@@ -1,82 +0,0 @@
1
- import { z } from "zod";
2
- import { on, flow, http, every } from "okengine";
3
- import { eq, lt } from "drizzle-orm";
4
- import { db } from "../../core";
5
- import { member, fair } from "../../gates";
6
- import { linkClicked, linkStats } from "./signals";
7
- import { NewLink, LinkCode, Link, NotFound, Taken } from "./shapes";
8
- import { links } from "../../schema";
9
-
10
- // ① HTTP — "an endpoint"
11
- export const shorten = on(
12
- http.post("/links").gate(member, fair),
13
- flow({
14
- in: NewLink,
15
- out: LinkCode,
16
- errors: { Taken },
17
- do: async ({ url, code }, fx) => {
18
- if (await fx.store(db).exists(links, { code })) return fx.fail("Taken", {});
19
- const id = fx.id();
20
- await fx
21
- .store(db)
22
- .insert(links)
23
- .values({ id, code, url, userId: fx.auth.userId, clicks: 0, createdAt: Date.now() });
24
- return { code };
25
- },
26
- }),
27
- );
28
-
29
- // ② HTTP — the hot path
30
- export const redirect = on(
31
- http.get("/:code").gate(fair),
32
- flow({
33
- in: LinkCode,
34
- out: Link,
35
- errors: { NotFound },
36
- do: async ({ code }, fx) => {
37
- const [link] = await fx.store(db).select().from(links).where(eq(links.code, code)).limit(1);
38
- if (!link) return fx.fail("NotFound", {});
39
-
40
- await fx.emit(linkClicked, { code, at: Date.now() }); // same transaction as any write
41
- return link;
42
- },
43
- }),
44
- );
45
-
46
- // ③ SIGNAL — "a queue consumer", and the same species as ① and ②
47
- on(
48
- linkClicked,
49
- flow({
50
- do: async ({ code }, fx) => {
51
- const [link] = await fx.store(db).select().from(links).where(eq(links.code, code)).limit(1);
52
- const clicks = await fx.store(db).increment(links, link.id, "clicks");
53
- await fx.emit(linkStats, { code, clicks }); // live: pushed to subscribers
54
- },
55
- }),
56
- );
57
-
58
- // ④ CLOCK — "a cron job", and still the same species
59
- on(
60
- every("1h"),
61
- flow({
62
- do: (_, fx) => {
63
- const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000; // 30 days
64
- return fx.store(db).delete(links).where(lt(links.createdAt, cutoff));
65
- },
66
- }),
67
- );
68
-
69
- // ⑤ A plain flow with no trigger — callable, not "private"
70
- export const stats = flow({
71
- in: LinkCode,
72
- out: z.object({ clicks: z.number() }),
73
- do: async ({ code }, fx) => {
74
- const [link] = await fx
75
- .store(db)
76
- .select({ clicks: links.clicks })
77
- .from(links)
78
- .where(eq(links.code, code))
79
- .limit(1);
80
- return link ?? { clicks: 0 };
81
- },
82
- });
@@ -1,22 +0,0 @@
1
- import { z } from "zod";
2
-
3
- export const NewLink = z.object({
4
- url: z.string().url(),
5
- code: z.string().min(1).max(32),
6
- });
7
-
8
- export const LinkCode = z.object({
9
- code: z.string(),
10
- });
11
-
12
- export const Link = z.object({
13
- id: z.string(),
14
- code: z.string(),
15
- url: z.string(),
16
- userId: z.string(),
17
- clicks: z.number(),
18
- createdAt: z.number(),
19
- });
20
-
21
- export const Taken = z.object({});
22
- export const NotFound = z.object({});
@@ -1,14 +0,0 @@
1
- import { signal } from "okengine";
2
- import { z } from "zod";
3
-
4
- export const linkClicked = signal("link-clicked", {
5
- schema: z.object({ code: z.string(), at: z.number(), referrer: z.string().optional() }),
6
- delivery: "once", // queue physics: one consumer, retries, DLQ
7
- retries: 3,
8
- deadLetter: true,
9
- });
10
-
11
- export const linkStats = signal("link-stats", {
12
- schema: z.object({ code: z.string(), clicks: z.number() }),
13
- delivery: "live", // stream physics: clients subscribe
14
- });
@@ -1,10 +0,0 @@
1
- import { gate } from "okengine";
2
-
3
- export const member = gate.policy("member", ({ auth }) => !!auth?.verified);
4
-
5
- export const fair = gate.rate({
6
- strategy: "sliding-window-counter", // near-exact, two keys, no boundary bursts
7
- max: 60,
8
- per: "1m",
9
- keyBy: "ip",
10
- });
@@ -1,39 +0,0 @@
1
- import { store, field } from "okengine";
2
-
3
- /** Short links — `code` is the human-facing unique key. */
4
- export const links = store.schema.table("links", {
5
- id: field.text().primaryKey(), // `increment` targets this column
6
- code: field.text().notNull().unique(), // the short, human-facing key
7
- url: field.text().notNull(),
8
- userId: field.text().notNull(),
9
- clicks: field.integer().notNull().default(0),
10
- createdAt: field.integer().notNull(),
11
- });
12
-
13
- /** Per-link daily click counts — FK to `links.code`. */
14
- export const daily = store.schema.table("daily", {
15
- id: field.text().primaryKey(),
16
- code: field
17
- .text()
18
- .notNull()
19
- .references(() => links.code),
20
- day: field.text().notNull(), // "YYYY-MM-DD"
21
- clicks: field.integer().notNull().default(0),
22
- });
23
-
24
- /** links ↔ daily — one link has many daily rows. */
25
- export const relations = store.schema.relations({ links, daily }, (r) => ({
26
- links: {
27
- daily: r.many.daily({
28
- from: r.links.code,
29
- to: r.daily.code,
30
- }),
31
- },
32
- daily: {
33
- link: r.one.links({
34
- from: r.daily.code,
35
- to: r.links.code,
36
- optional: false,
37
- }),
38
- },
39
- }));
@@ -1,30 +0,0 @@
1
- // generated by oke — do not edit
2
- import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
3
- import { defineRelations } from "drizzle-orm";
4
-
5
- export const links = sqliteTable("links", {
6
- id: text("id").primaryKey(),
7
- code: text("code").notNull().unique(),
8
- url: text("url").notNull(),
9
- userId: text("user_id").notNull(),
10
- clicks: integer("clicks").notNull().default(0),
11
- createdAt: integer("created_at").notNull(),
12
- });
13
-
14
- export const daily = sqliteTable("daily", {
15
- id: text("id").primaryKey(),
16
- code: text("code")
17
- .notNull()
18
- .references(() => links.code),
19
- day: text("day").notNull(),
20
- clicks: integer("clicks").notNull().default(0),
21
- });
22
-
23
- export const relations = defineRelations({ links, daily }, (r) => ({
24
- links: {
25
- daily: r.many.daily({ from: r.links.code, to: r.daily.code }),
26
- },
27
- daily: {
28
- link: r.one.links({ from: r.daily.code, to: r.links.code, optional: false }),
29
- },
30
- }));
@@ -1,2 +0,0 @@
1
- /** Stable import surface — re-exports generated Drizzle from abstract decls. */
2
- export { links, daily, relations } from "./schema.generated";
@@ -1,19 +0,0 @@
1
- import { test, expect } from "bun:test";
2
- import { createTestApp } from "okengine/test";
3
- import { app } from "../src/app";
4
-
5
- test("shorten → redirect → report · time travel", async () => {
6
- const t = await createTestApp(app); // memory drivers, frozen clock
7
- const u = await t.auth.loginAs({});
8
-
9
- await t.api.links.shorten({ url: "https://example.com", code: "sa" }, { as: u });
10
- await t.api.links.redirect({ code: "sa" });
11
- await t.signals.drain(); // run queued work deterministically
12
-
13
- const { data } = await t.api.links.report({ code: "sa" }, { as: u });
14
- expect(data![0].clicks).toBe(1);
15
-
16
- await t.clock.advance("31d");
17
- await t.cron.run("1h"); // time travel
18
- await t.close();
19
- });
@@ -1,12 +0,0 @@
1
- # Environment template — copy to `.env.local` for local overrides.
2
- # create-oke copies this file to `.env.local` when you scaffold a new project.
3
- #
4
- # Resolution order (first hit wins):
5
- # process.env → .env.local → docker/.env.docker → vault driver → dev fallback
6
- #
7
- # --- Infrastructure (optional) ---
8
- # This app pins postgres for prod SQL in oke.config.ts (local dev uses sqlite).
9
- # Uncomment only when overriding the prod connection URL.
10
-
11
- # DATABASE_URL — primary Postgres connection; from your host (Neon, RDS, …)
12
- # DATABASE_URL=postgres://user:password@host:5432/notes
@@ -1,11 +0,0 @@
1
- import { defineConfig } from "drizzle-kit";
2
-
3
- /** Domain schema sync for `oke db push|generate|migrate`. */
4
- export default defineConfig({
5
- dialect: "sqlite",
6
- schema: "./src/schema.ts",
7
- out: "./drizzle",
8
- dbCredentials: {
9
- url: process.env.OKE_SQLITE_URL ?? "file:.oke/app.sqlite",
10
- },
11
- });
@@ -1,14 +0,0 @@
1
- import { defineConfig } from "okengine/config";
2
-
3
- export default defineConfig({
4
- drivers: {
5
- store: {
6
- sql: {
7
- local: "sqlite",
8
- docker: "postgres",
9
- test: "memory",
10
- prod: "postgres",
11
- },
12
- },
13
- },
14
- });
@@ -1,19 +0,0 @@
1
- {
2
- "name": "@oke/example-notes",
3
- "version": "0.0.1",
4
- "private": true,
5
- "type": "module",
6
- "scripts": {
7
- "test": "bun test",
8
- "dev": "oke dev"
9
- },
10
- "dependencies": {
11
- "drizzle-orm": "^1.0.0-rc.4",
12
- "drizzle-zod": "^0.8.3",
13
- "okengine": "file:../..",
14
- "zod": "^4.4.3"
15
- },
16
- "devDependencies": {
17
- "drizzle-kit": "^1.0.0-rc.4"
18
- }
19
- }
@@ -1,13 +0,0 @@
1
- import { db } from "./core";
2
-
3
- import { oke } from "okengine";
4
- import * as notes from "./flows/notes";
5
-
6
- export const app = oke({ name: "notes" }).adopt({ notes });
7
-
8
- export type App = typeof app; // ← the client needs nothing else
9
-
10
- Object.assign(app.$options, {
11
- env: "test",
12
- stores: [db],
13
- });
@@ -1,4 +0,0 @@
1
- import { store } from "okengine";
2
- import * as schema from "./schema";
3
-
4
- export const db = store.sql("notes", { schema });
@@ -1,58 +0,0 @@
1
- import { on, flow, http } from "okengine";
2
- import { createInsertSchema, createSelectSchema } from "drizzle-zod";
3
- import { z } from "zod";
4
- import { db } from "../../core";
5
- import { notes } from "../../schema";
6
-
7
- // Contracts derived from the schema — one source of truth, refined where the API is stricter
8
- const NewNote = createInsertSchema(notes, { title: (s) => s.min(1).max(120) }).omit({
9
- id: true,
10
- createdAt: true,
11
- });
12
- const Note = createSelectSchema(notes);
13
- const NoteId = z.object({ id: z.string() });
14
- const NotFound = z.object({});
15
-
16
- export const create = on(
17
- http.post("/notes"),
18
- flow({
19
- in: NewNote,
20
- out: NoteId,
21
- do: async (input, fx) => {
22
- const [note] = await fx.store(db).insert(notes).values(input).returning();
23
- return { id: note.id };
24
- },
25
- }),
26
- );
27
- // effects → writes[sql:notes]
28
-
29
- export const list = on(
30
- http.get("/notes"),
31
- flow({
32
- out: Note.array(),
33
- do: (_, fx) => fx.store(db).select().from(notes),
34
- }),
35
- );
36
- // effects → reads[sql:notes]
37
-
38
- export const get = on(
39
- http.get("/notes/:id"),
40
- flow({
41
- in: NoteId,
42
- out: Note,
43
- errors: { NotFound },
44
- do: async ({ id }, fx) => (await fx.store(db).findById(notes, id)) ?? fx.fail("NotFound", {}),
45
- }),
46
- );
47
-
48
- export const remove = on(
49
- http.delete("/notes/:id"),
50
- flow({
51
- in: NoteId,
52
- errors: { NotFound },
53
- do: async ({ id }, fx) => {
54
- const deleted = await fx.store(db).delete(notes, id);
55
- if (!deleted) return fx.fail("NotFound", {});
56
- },
57
- }),
58
- );
@@ -1,9 +0,0 @@
1
- import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
2
- import { id, now } from "okengine/store";
3
-
4
- export const notes = sqliteTable("notes", {
5
- id: text("id").primaryKey().$defaultFn(id),
6
- title: text("title").notNull(),
7
- body: text("body").notNull(),
8
- createdAt: integer("created_at").notNull().$defaultFn(now),
9
- });
@@ -1,10 +0,0 @@
1
- import { test, expect } from "bun:test";
2
- import { createTestApp } from "okengine/test";
3
- import { app } from "../src/app";
4
-
5
- test("create then read", async () => {
6
- const t = await createTestApp(app); // memory driver, automatic
7
- const { data } = await t.api.notes.create({ title: "First", body: "Hello" });
8
- const { data: note } = await t.api.notes.get({ id: data!.id });
9
- expect(note!.title).toBe("First");
10
- });
@@ -1,19 +0,0 @@
1
- # Environment template — copy to `.env.local` for local overrides.
2
- # create-oke copies this file to `.env.local` when you scaffold a new project.
3
- #
4
- # Resolution order (first hit wins):
5
- # process.env → .env.local → docker/.env.docker → vault driver → dev fallback
6
- #
7
- # --- Vault (app secrets) ---
8
-
9
- # STRIPE_KEY — Stripe secret API key for payments; from https://dashboard.stripe.com/apikeys (test keys start with sk_test_)
10
- STRIPE_KEY=
11
-
12
- # DATABASE_URL — primary Postgres URL (vault contract in src/vault.ts); from your host or `docker/.env.docker` after `oke dev --docker`
13
- DATABASE_URL=
14
-
15
- # --- Infrastructure (optional) ---
16
- # This app pins redis for prod kv in oke.config.ts (local dev uses in-memory kv).
17
-
18
- # REDIS_URL — Redis/Valkey connection for store.kv; from your host (Upstash, ElastiCache, …)
19
- # REDIS_URL=redis://:password@host:6379
@@ -1,11 +0,0 @@
1
- import { defineConfig } from "drizzle-kit";
2
-
3
- /** Domain schema sync for `oke db push|generate|migrate`. */
4
- export default defineConfig({
5
- dialect: "sqlite",
6
- schema: "./src/schema.ts",
7
- out: "./drizzle",
8
- dbCredentials: {
9
- url: process.env.OKE_SQLITE_URL ?? "file:.oke/app.sqlite",
10
- },
11
- });
@@ -1,19 +0,0 @@
1
- import { defineConfig } from "okengine/config";
2
-
3
- export default defineConfig({
4
- drivers: {
5
- store: {
6
- sql: { local: "sqlite", test: "memory", prod: "postgres" },
7
- kv: { local: "memory", test: "memory", prod: "redis" },
8
- },
9
- signal: { local: "memory", test: "memory", prod: "postgres" },
10
- clock: { local: "memory", test: "frozen", prod: "postgres" },
11
- vault: { local: "dotenv", test: "memory", prod: "sops" },
12
- channel: {
13
- email: { local: "console", test: "console", prod: "smtp" },
14
- sms: { local: "console", test: "console", prod: "unifonic" },
15
- whatsapp: { local: "console", test: "console", prod: "wa-cloud" },
16
- },
17
- },
18
- i18n: { locales: ["en", "ar"], default: "ar", dir: { ar: "rtl" } },
19
- });