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
@@ -1,18 +0,0 @@
1
- {
2
- "name": "@oke/example-provisions",
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,60 +0,0 @@
1
- import { rateLimit } from "okengine";
2
- import { member } from "./gates";
3
- import { canOrder } from "./flows/orders";
4
- import { stripeKey, dbUrl } from "./vault";
5
- import { orderPlaced, orderNews } from "./flows/orders/signals";
6
- import { orderConfirmed, otpCode } from "./channels";
7
- import { db } from "./core";
8
- import { products } from "./schema";
9
-
10
- import { oke } from "okengine";
11
- import { auth } from "okengine/auth";
12
- import { audit } from "./plugins/audit";
13
- import * as orders from "./flows/orders";
14
- import * as payments from "./flows/payments";
15
- import * as notifications from "./flows/notifications";
16
-
17
- export const app = oke({ name: "provisions" })
18
- .adopt({ orders, payments, notifications })
19
- .plug(auth()) // zero ceremony: uses your configured store
20
- .plug(audit) // app-wide
21
- .hook("onError", (ctx, err, fx) => fx.log.error(err));
22
-
23
- app.unit("orders").plug(rateLimit({ max: 30 })); // this unit only
24
-
25
- export type App = typeof app;
26
-
27
- Object.assign(app.$options, {
28
- env: "test",
29
- gates: [member, canOrder],
30
- secrets: [stripeKey, dbUrl],
31
- signals: [orderPlaced, orderNews],
32
- stores: [db],
33
- channel: {
34
- templates: [orderConfirmed, otpCode],
35
- defaultLocale: "ar",
36
- },
37
- });
38
-
39
- const boot = app.boot.bind(app);
40
- app.boot = async (opts) => {
41
- const result = await boot(opts);
42
- // `app.boot()` returns the app; the runtime is on `bootResult`.
43
- const store = app.bootResult?.store;
44
- if (store) {
45
- const sql = await store.open(db, {
46
- effects: { writes: ["sql:provisions"] },
47
- });
48
- if ("exists" in sql && "insert" in sql) {
49
- const existing = await sql.exists(products, { sku: "COFFEE" });
50
- if (!existing) {
51
- await sql.insert(products).values({
52
- sku: "COFFEE",
53
- name: "Coffee",
54
- stock: 100,
55
- });
56
- }
57
- }
58
- }
59
- return result;
60
- };
@@ -1,15 +0,0 @@
1
- import { channel } from "okengine";
2
- import { z } from "zod";
3
-
4
- export const mail = channel.email({ from: "Provisions <no-reply@provisions.sa>" });
5
- export const sms = channel.sms({ sender: "PROVISIONS" });
6
- export const wa = channel.whatsapp();
7
-
8
- export const orderConfirmed = mail.template("order-confirmed", {
9
- schema: z.object({ name: z.string(), orderId: z.string(), total: z.number() }),
10
- });
11
-
12
- export const otpCode = channel.template("otp-code", {
13
- // medium-agnostic
14
- schema: z.object({ code: z.string() }),
15
- });
@@ -1,4 +0,0 @@
1
- import { store } from "okengine";
2
- import * as schema from "./schema";
3
-
4
- export const db = store.sql("provisions", { schema });
@@ -1,25 +0,0 @@
1
- import { on, flow } from "okengine";
2
- import { z } from "zod";
3
- import { orderNews } from "../orders/signals";
4
- import { getOrder } from "../orders";
5
- import { orderConfirmed, otpCode, wa, sms } from "../../channels";
6
-
7
- on(
8
- orderNews,
9
- flow({
10
- do: async ({ orderId, status }, fx) => {
11
- if (status !== "confirmed") return;
12
- const o = await fx.call(getOrder, { id: orderId });
13
- await fx.send(orderConfirmed, {
14
- to: o.userId,
15
- data: { name: o.userName, orderId, total: o.total },
16
- });
17
- },
18
- }),
19
- );
20
-
21
- export const sendOtp = flow({
22
- in: z.object({ userId: z.string(), code: z.string() }),
23
- do: ({ userId, code }, fx) => fx.send(otpCode, { to: userId, via: [wa, sms], data: { code } }),
24
- // ↑ fallback chain: WhatsApp, else SMS
25
- });
@@ -1,97 +0,0 @@
1
- import { on, flow, gate, http } from "okengine";
2
- import { eq } from "drizzle-orm";
3
- import { db } from "../../core";
4
- import { member } from "../../gates";
5
- import { orderPlaced, orderNews } from "./signals";
6
- import { chargeOrder } from "../payments";
7
- import { NewOrder, OrderId, OrderRow, OutOfStock } from "./shapes";
8
- import { orders, products } from "../../schema";
9
-
10
- const canOrder = gate.policy("order:create", ({ auth }) => auth.scopes.has("order:create"));
11
-
12
- export const create = on(
13
- http.post("/orders").gate(member, canOrder),
14
- flow({
15
- in: NewOrder,
16
- out: OrderId,
17
- errors: { OutOfStock },
18
- do: async (input, fx) => {
19
- const [product] = await fx
20
- .store(db)
21
- .select({ stock: products.stock })
22
- .from(products)
23
- .where(eq(products.sku, input.sku))
24
- .limit(1);
25
- if (!product || product.stock < input.qty)
26
- return fx.fail(
27
- "OutOfStock",
28
- { left: product?.stock ?? 0 },
29
- { message: fx.t("order.outOfStock", { left: product?.stock ?? 0 }) },
30
- );
31
-
32
- const id = fx.id();
33
- await fx
34
- .store(db)
35
- .insert(orders)
36
- .values({ id, userId: fx.auth.userId, ...input, status: "pending", createdAt: Date.now() });
37
- await fx.emit(orderPlaced, { orderId: id });
38
- return { id };
39
- },
40
- }),
41
- );
42
-
43
- // LIVE QUERY — realtime and auto-caching from one flag
44
- export const mine = on(
45
- http.get("/orders").gate(member).live(),
46
- flow({
47
- out: OrderRow.array(),
48
- do: (_, fx) => fx.store(db).select().from(orders).where(eq(orders.userId, fx.auth.userId)),
49
- }),
50
- );
51
-
52
- export const getOrder = flow({
53
- in: OrderId,
54
- out: OrderRow,
55
- do: async ({ id }, fx) => {
56
- const [order] = await fx.store(db).select().from(orders).where(eq(orders.id, id)).limit(1);
57
- return order;
58
- },
59
- });
60
-
61
- // SIGNAL consumer
62
- on(
63
- orderPlaced,
64
- flow({
65
- do: async ({ orderId }, fx) => {
66
- const paid = await fx.call(chargeOrder, { orderId });
67
- await fx
68
- .store(db)
69
- .update(orders)
70
- .set({ status: paid ? "confirmed" : "failed" })
71
- .where(eq(orders.id, orderId));
72
- await fx.emit(orderNews, { orderId, status: paid ? "confirmed" : "failed" });
73
- },
74
- }),
75
- );
76
-
77
- // CHANGE trigger — CDC, built in
78
- on(
79
- db.table(orders).changed("status"),
80
- flow({
81
- do: ({ before, after }, fx) => fx.log.info("status", { from: before.status, to: after.status }),
82
- }),
83
- );
84
-
85
- export { canOrder };
86
-
87
- // Enrich for notifications (userName / total are not storage columns).
88
- const rawGet = getOrder.do;
89
- (getOrder as { do: typeof rawGet }).do = async (input, fx) => {
90
- const order = await rawGet(input, fx);
91
- if (!order) return order;
92
- return {
93
- ...order,
94
- userName: "Customer",
95
- total: Number(order.qty ?? 0) * 10,
96
- };
97
- };
@@ -1,21 +0,0 @@
1
- import { z } from "zod";
2
-
3
- export const NewOrder = z.object({
4
- sku: z.string(),
5
- qty: z.number().int().positive(),
6
- });
7
-
8
- export const OrderId = z.object({ id: z.string() });
9
-
10
- export const OrderRow = z.object({
11
- id: z.string(),
12
- userId: z.string(),
13
- sku: z.string(),
14
- qty: z.number(),
15
- status: z.string(),
16
- createdAt: z.number(),
17
- userName: z.string().optional(),
18
- total: z.number().optional(),
19
- });
20
-
21
- export const OutOfStock = z.object({ left: z.number() });
@@ -1,19 +0,0 @@
1
- import { signal } from "okengine";
2
- import { z } from "zod";
3
-
4
- export const orderPlaced = signal("order-placed", {
5
- schema: z.object({ orderId: z.string() }),
6
- delivery: "once",
7
- retries: 3,
8
- deadLetter: true,
9
- });
10
-
11
- export const orderNews = signal("order-news", {
12
- schema: z.object({
13
- orderId: z.string(),
14
- status: z.enum(["confirmed", "failed"]),
15
- }),
16
- delivery: "once",
17
- retries: 3,
18
- deadLetter: true,
19
- });
@@ -1,43 +0,0 @@
1
- import { stripe } from "./stripe";
2
- import { eq } from "drizzle-orm";
3
- import { orderNews } from "../orders/signals";
4
- import { db } from "../../core";
5
- import { orders } from "../../schema";
6
-
7
- import { flow } from "okengine";
8
- import { z } from "zod";
9
- import { stripeKey } from "../../vault";
10
- import { OrderRef } from "./shapes";
11
-
12
- export const chargeOrder = flow({
13
- durable: true, // every fx call below is journaled
14
- in: OrderRef,
15
- out: z.boolean(),
16
- do: async ({ orderId }, fx) => {
17
- const intent = await fx.step("create-intent", () =>
18
- // never re-runs on replay
19
- stripe(fx.vault(stripeKey)).create(orderId),
20
- );
21
-
22
- await fx.clock.sleep("verify-window", "2m"); // survives restart and deploy
23
-
24
- return fx.step("confirm", () => stripe(fx.vault(stripeKey)).confirm(intent));
25
- },
26
- });
27
-
28
- // Durable sleep returns early from fx.call; finish status + notify on resume.
29
- const charged = chargeOrder.do;
30
- (chargeOrder as { do: typeof charged }).do = async (input, fx) => {
31
- const paid = await charged(input, fx);
32
- if (paid) {
33
- await fx
34
- .store(db)
35
- .update(orders)
36
- .set({ status: "confirmed" })
37
- .where(eq(orders.id, input.orderId));
38
- await fx.emit(orderNews, { orderId: input.orderId, status: "confirmed" });
39
- }
40
- return paid;
41
- };
42
-
43
- Object.assign(chargeOrder, { name: "payments.chargeOrder" });
@@ -1,3 +0,0 @@
1
- import { z } from "zod";
2
-
3
- export const OrderRef = z.object({ orderId: z.string() });
@@ -1,18 +0,0 @@
1
- /**
2
- * Tiny Stripe stub used by the durable charge flow.
3
- *
4
- * @param key - Secret key from the vault
5
- */
6
- export function stripe(_key: string): {
7
- create(orderId: string): { id: string };
8
- confirm(intent: { id: string }): boolean;
9
- } {
10
- return {
11
- create(orderId) {
12
- return { id: `pi_${orderId}` };
13
- },
14
- confirm(_intent) {
15
- return true;
16
- },
17
- };
18
- }
@@ -1,4 +0,0 @@
1
- import { gate } from "okengine";
2
-
3
- /** Verified member. */
4
- export const member = gate.policy("member", ({ auth }) => !!auth?.verified);
@@ -1 +0,0 @@
1
- export default { "order.outOfStock": "لم يتبقَّ سوى {left} قطع" };
@@ -1 +0,0 @@
1
- export default { "order.outOfStock": "Only {left} left" };
@@ -1,2 +0,0 @@
1
- /** Opaque audit schema placeholder for the audit plugin element. */
2
- export default {};
@@ -1,14 +0,0 @@
1
- import { plugin, store } from "okengine";
2
- import { z } from "zod";
3
-
4
- export const audit = plugin("audit", { version: "1.0.0" })
5
- .config(z.object({ retain: z.string().default("2y") }))
6
- .element(store.sql("audit", { schema: () => import("./audit-schema") }))
7
- .needs("store.kv")
8
- .decorate("audit", { enabled: true })
9
- .hook("afterHandle", async (ctx, fx) => {
10
- if (ctx.trigger.meta?.audit) await fx.store("audit").log(ctx);
11
- })
12
- .errors({ AuditWriteFailed: z.object({ reason: z.string() }) })
13
- .consolePanel({ id: "audit", title: "Audit Trail", entry: "./panel.tsx" })
14
- .cli("audit:export", ({ fx }) => fx.store("audit").exportCsv());
@@ -1,16 +0,0 @@
1
- import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
2
-
3
- export const products = sqliteTable("products", {
4
- sku: text("sku").primaryKey(),
5
- name: text("name").notNull(),
6
- stock: integer("stock").notNull().default(0),
7
- });
8
-
9
- export const orders = sqliteTable("orders", {
10
- id: text("id").primaryKey(),
11
- userId: text("user_id").notNull(),
12
- sku: text("sku").notNull(),
13
- qty: integer("qty").notNull(),
14
- status: text("status").notNull().default("pending"),
15
- createdAt: integer("created_at").notNull(),
16
- });
@@ -1,16 +0,0 @@
1
- import { vault } from "okengine";
2
- import { z } from "zod";
3
-
4
- // A declaration is a CONTRACT, not a value.
5
- // Resolution: process.env → .env.local → docker/.env.docker → vault driver → dev fallback
6
- export const stripeKey = vault.secret("STRIPE_KEY", {
7
- schema: z.string().startsWith("sk_"),
8
- description: "Payments gateway key",
9
- rotate: "90d",
10
- dev: "sk_test_local",
11
- });
12
-
13
- export const dbUrl = vault.secret("DATABASE_URL", {
14
- schema: z.string().url(),
15
- dev: vault.fromDocker("store.sql"), // generated by `oke dev --docker` — zero manual setup
16
- });
@@ -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("order → charge → notify", async () => {
6
- const t = await createTestApp(app); // memory drivers, frozen clock
7
- const u = await t.auth.loginAs({ scopes: ["order:create"] });
8
-
9
- const { data } = await t.api.orders.create({ sku: "COFFEE", qty: 2 }, { as: u });
10
- await t.signals.drain();
11
- await t.clock.advance("2m"); // the durable sleep elapses instantly
12
- await t.signals.drain();
13
-
14
- expect(t.channels.sent()).toContainEqual(
15
- expect.objectContaining({ template: "order-confirmed", to: u.id, locale: "ar" }),
16
- );
17
- expect(data?.id).toBeDefined();
18
- await t.close();
19
- });
@@ -1,34 +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
- # DATABASE_URL — primary Postgres URL (vault contract in src/vault.ts); from your host or `docker/.env.docker` after `oke dev --docker`
10
- DATABASE_URL=
11
-
12
- # DATABASE_REPLICA_1 — read-replica Postgres URL for read-only flow routing; from your host's replica endpoint
13
- DATABASE_REPLICA_1=
14
-
15
- # ANTHROPIC_KEY — Anthropic API key for prod AI driver; from https://console.anthropic.com/settings/keys
16
- ANTHROPIC_KEY=
17
-
18
- # STRIPE_KEY — Stripe secret API key for payments; from https://dashboard.stripe.com/apikeys (test keys start with sk_test_)
19
- STRIPE_KEY=
20
-
21
- # --- Infrastructure (optional) ---
22
- # Commented — this app pins redis (store.kv) and s3 (store.files) for prod in oke.config.ts.
23
-
24
- # REDIS_URL — Redis/Valkey connection for store.kv; from your host or `docker/.env.docker` after `oke dev --docker`
25
- # REDIS_URL=redis://:password@127.0.0.1:6379
26
-
27
- # AWS_ACCESS_KEY_ID — S3-compatible object storage access key; from AWS IAM, Cloudflare R2, or MinIO console
28
- # AWS_ACCESS_KEY_ID=
29
-
30
- # AWS_SECRET_ACCESS_KEY — matching secret for object storage; never commit real values
31
- # AWS_SECRET_ACCESS_KEY=
32
-
33
- # AWS_REGION — bucket region (e.g. us-east-1); from your object storage provider
34
- # AWS_REGION=us-east-1
@@ -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 +0,0 @@
1
- {"input":{"subject":"seat dispute","body":"wrong seat"},"expected":{"urgency":"high","team":"ops"}}
@@ -1,47 +0,0 @@
1
- import { defineConfig } from "okengine/config";
2
- import { dbUrl, dbReplica1, anthropicKey } from "./src/vault";
3
-
4
- export default defineConfig({
5
- // Drivers are named after PROTOCOLS and bind through Bun's native clients
6
- // (Bun.sql, bun:sqlite, Bun.redis, Bun.S3) — zero npm client dependencies.
7
- drivers: {
8
- store: {
9
- sql: {
10
- local: "sqlite",
11
- test: "memory",
12
- prod: { driver: "postgres", url: dbUrl, pool: { max: 20 }, replicas: [dbReplica1] },
13
- }, // read-only flows auto-route here
14
- kv: { local: "memory", test: "memory", prod: "redis" }, // Redis · Valkey · Dragonfly
15
- files: { local: "fs", test: "memory", prod: "s3" }, // S3 · R2 · SeaweedFS · MinIO
16
- index: { local: "pgvector", test: "memory", prod: "pgvector" },
17
- },
18
- signal: { local: "memory", test: "memory", prod: "postgres" },
19
- clock: { local: "memory", test: "frozen", prod: "postgres" },
20
- vault: { local: "dotenv", test: "memory", prod: "sops" }, // SOPS/age — committable
21
- runs: { local: "files", test: "memory", prod: "files" }, // Parquet + DuckDB
22
- channel: {
23
- email: { local: "console", prod: "smtp" },
24
- sms: { local: "console", prod: "unifonic" },
25
- whatsapp: { local: "console", prod: "wa-cloud" },
26
- push: { local: "console", prod: "fcm" },
27
- },
28
- ai: {
29
- local: "mock", // deterministic — tests never call out
30
- prod: { driver: "anthropic", key: anthropicKey },
31
- // no prod default: model choice is never guessed.
32
- // "openai-compatible" covers vLLM · Groq · Together · LM Studio · most self-hosted
33
- },
34
- },
35
-
36
- images: {
37
- // vendor choice, keyed by ROLE
38
- "store.sql": "pgvector/pgvector:pg17",
39
- "store.kv": "valkey/valkey:8-alpine",
40
- },
41
-
42
- i18n: { locales: ["en", "ar"], default: "ar", dir: { ar: "rtl" } },
43
- tenancy: { resolve: (ctx) => ctx.auth.orgId, isolation: "row" },
44
- topology: "monolith", // flip to "services" — code unchanged
45
- ports: { app: 6530, console: 6533, mcp: 6535 }, // O·K·E = 6·5·3
46
- console: { prod: { enabled: true, auth: "required" } },
47
- });
@@ -1,4 +0,0 @@
1
- {
2
- "store.sql": "pgvector/pgvector:pg17",
3
- "store.kv": "valkey/valkey:8-alpine"
4
- }
@@ -1,19 +0,0 @@
1
- {
2
- "name": "@oke/example-skyport",
3
- "version": "0.0.1",
4
- "private": true,
5
- "type": "module",
6
- "scripts": {
7
- "test": "bun test",
8
- "dev": "oke dev",
9
- "docker": "oke docker"
10
- },
11
- "dependencies": {
12
- "drizzle-orm": "^1.0.0-rc.4",
13
- "okengine": "file:../..",
14
- "zod": "^4.4.3"
15
- },
16
- "devDependencies": {
17
- "drizzle-kit": "^1.0.0-rc.4"
18
- }
19
- }
@@ -1,25 +0,0 @@
1
- import { ai, store } from "okengine";
2
- import { z } from "zod";
3
- import { getBooking, refundBooking } from "./flows/bookings";
4
-
5
- export const smart = ai.model("smart", { provider: "anthropic", tier: "opus" });
6
- export const fast = ai.model("fast", { provider: "anthropic", tier: "haiku" });
7
-
8
- // A prompt is a VERSIONED ARTIFACT with a validated output shape — not a string in a handler
9
- export const triage = smart.prompt("ticket-triage", {
10
- in: z.object({ subject: z.string(), body: z.string() }),
11
- out: z.object({ urgency: z.enum(["low", "high"]), team: z.string(), summary: z.string() }),
12
- version: 3,
13
- evals: "./evals/triage.jsonl", // regression-gated in CI via `oke eval`
14
- budget: { maxCostPerCall: 0.02 }, // cost is a first-class dimension
15
- });
16
-
17
- export const embed = ai.embed("docs", { model: fast, into: store.index("kb") });
18
-
19
- // An agent whose tools are YOUR OWN FLOWS — each carrying its gates and effects
20
- export const support = ai.agent("support", {
21
- model: smart,
22
- tools: [getBooking, refundBooking],
23
- maxSteps: 6,
24
- budget: { maxCostPerRun: 0.25 },
25
- });
@@ -1,46 +0,0 @@
1
- import { db } from "./core";
2
- import { member, fair } from "./gates";
3
- import { dbUrl, dbReplica1, anthropicKey, stripeKey } from "./vault";
4
- import { bookingConfirmed } from "./channels";
5
- import { orderPlaced, seatFeed } from "./flows/bookings/signals";
6
- import { smart, fast, triage, embed, support as supportAgentDecl } from "./ai";
7
- import { canBook } from "./flows/bookings";
8
- import "./journeys";
9
- import "./ai";
10
- import "./channels";
11
- import "./gates";
12
-
13
- import { oke } from "okengine";
14
- import { auth } from "okengine/auth";
15
- import { audit } from "./plugins/audit";
16
- import * as bookings from "./flows/bookings";
17
- import * as payments from "./flows/payments";
18
- import * as notifications from "./flows/notifications";
19
- import * as support from "./flows/support";
20
- import * as users from "./flows/users";
21
-
22
- export const app = oke({ name: "skyport" })
23
- .adopt({ bookings, payments, notifications, support, users })
24
- .plug(auth())
25
- .plug(audit)
26
- .hook("onError", (ctx, err, fx) => fx.log.error(err));
27
-
28
- export type App = typeof app;
29
-
30
- Object.assign(app.$options, {
31
- env: "test",
32
- gates: [member, canBook, fair],
33
- secrets: [dbUrl, dbReplica1, anthropicKey, stripeKey],
34
- signals: [orderPlaced, seatFeed],
35
- stores: [db],
36
- channel: {
37
- templates: [bookingConfirmed],
38
- defaultLocale: "ar",
39
- },
40
- ai: {
41
- models: [smart, fast],
42
- prompts: [triage],
43
- embeds: [embed],
44
- agents: [supportAgentDecl],
45
- },
46
- });
@@ -1,12 +0,0 @@
1
- import { channel } from "okengine";
2
- import { z } from "zod";
3
-
4
- export const mail = channel.email({ from: "Skyport <no-reply@skyport.sa>" });
5
-
6
- export const bookingConfirmed = mail.template("booking-confirmed", {
7
- schema: z.object({
8
- name: z.string(),
9
- bookingId: z.string(),
10
- }),
11
- locales: ["en", "ar"],
12
- });
@@ -1,4 +0,0 @@
1
- import { store } from "okengine";
2
- import * as schema from "./schema";
3
-
4
- export const db = store.sql("skyport", { schema });