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.
- package/README.md +11 -32
- package/package.json +2 -4
- package/src/cli.test.ts +29 -117
- package/src/cli.ts +16 -139
- package/src/docs-origin.ts +2 -3
- package/src/index.ts +1 -2
- package/src/scaffold.ts +13 -31
- package/src/templates.ts +12 -90
- package/src/transform.ts +1 -1
- package/template/.env.example +56 -0
- package/{templates/standard → template}/README.md +25 -12
- package/template/bun.lock +845 -0
- package/template/drizzle.config.ts +25 -0
- package/{templates/full → template}/oke.config.ts +11 -7
- package/{templates/minimal → template}/src/core.ts +1 -1
- package/template/src/schema.decl.ts +12 -0
- package/examples/linkly/.env.example +0 -15
- package/examples/linkly/drizzle.config.ts +0 -11
- package/examples/linkly/oke.config.ts +0 -12
- package/examples/linkly/package.json +0 -18
- package/examples/linkly/src/app.ts +0 -21
- package/examples/linkly/src/core.ts +0 -4
- package/examples/linkly/src/flows/analytics/index.ts +0 -39
- package/examples/linkly/src/flows/links/index.ts +0 -82
- package/examples/linkly/src/flows/links/shapes.ts +0 -22
- package/examples/linkly/src/flows/links/signals.ts +0 -14
- package/examples/linkly/src/gates.ts +0 -10
- package/examples/linkly/src/schema.decl.ts +0 -39
- package/examples/linkly/src/schema.generated.ts +0 -30
- package/examples/linkly/src/schema.ts +0 -2
- package/examples/linkly/tests/linkly.test.ts +0 -19
- package/examples/notes/.env.example +0 -12
- package/examples/notes/drizzle.config.ts +0 -11
- package/examples/notes/oke.config.ts +0 -14
- package/examples/notes/package.json +0 -19
- package/examples/notes/src/app.ts +0 -13
- package/examples/notes/src/core.ts +0 -4
- package/examples/notes/src/flows/notes/index.ts +0 -58
- package/examples/notes/src/schema.ts +0 -9
- package/examples/notes/tests/notes.test.ts +0 -10
- package/examples/provisions/.env.example +0 -19
- package/examples/provisions/drizzle.config.ts +0 -11
- package/examples/provisions/oke.config.ts +0 -19
- package/examples/provisions/package.json +0 -18
- package/examples/provisions/src/app.ts +0 -60
- package/examples/provisions/src/channels.ts +0 -15
- package/examples/provisions/src/core.ts +0 -4
- package/examples/provisions/src/flows/notifications/index.ts +0 -25
- package/examples/provisions/src/flows/orders/index.ts +0 -97
- package/examples/provisions/src/flows/orders/shapes.ts +0 -21
- package/examples/provisions/src/flows/orders/signals.ts +0 -19
- package/examples/provisions/src/flows/payments/index.ts +0 -43
- package/examples/provisions/src/flows/payments/shapes.ts +0 -3
- package/examples/provisions/src/flows/payments/stripe.ts +0 -18
- package/examples/provisions/src/gates.ts +0 -4
- package/examples/provisions/src/locales/ar.ts +0 -1
- package/examples/provisions/src/locales/en.ts +0 -1
- package/examples/provisions/src/plugins/audit-schema.ts +0 -2
- package/examples/provisions/src/plugins/audit.ts +0 -14
- package/examples/provisions/src/schema.ts +0 -16
- package/examples/provisions/src/vault.ts +0 -16
- package/examples/provisions/tests/orders.test.ts +0 -19
- package/examples/skyport/.env.example +0 -34
- package/examples/skyport/drizzle.config.ts +0 -11
- package/examples/skyport/evals/triage.jsonl +0 -1
- package/examples/skyport/oke.config.ts +0 -47
- package/examples/skyport/oke.images.lock +0 -4
- package/examples/skyport/package.json +0 -19
- package/examples/skyport/src/ai.ts +0 -25
- package/examples/skyport/src/app.ts +0 -46
- package/examples/skyport/src/channels.ts +0 -12
- package/examples/skyport/src/core.ts +0 -4
- package/examples/skyport/src/flows/bookings/index.ts +0 -73
- package/examples/skyport/src/flows/bookings/shapes.ts +0 -6
- package/examples/skyport/src/flows/bookings/signals.ts +0 -13
- package/examples/skyport/src/flows/notifications/index.ts +0 -16
- package/examples/skyport/src/flows/payments/index.ts +0 -14
- package/examples/skyport/src/flows/payments/shapes.ts +0 -5
- package/examples/skyport/src/flows/support/index.ts +0 -50
- package/examples/skyport/src/flows/users/elements.ts +0 -2
- package/examples/skyport/src/flows/users/index.ts +0 -12
- package/examples/skyport/src/flows/users/shapes.ts +0 -6
- package/examples/skyport/src/gates.ts +0 -10
- package/examples/skyport/src/journeys.ts +0 -9
- package/examples/skyport/src/locales/ar.ts +0 -4
- package/examples/skyport/src/locales/en.ts +0 -4
- package/examples/skyport/src/plugins/audit-schema.ts +0 -7
- package/examples/skyport/src/plugins/audit.ts +0 -14
- package/examples/skyport/src/plugins/panel.tsx +0 -4
- package/examples/skyport/src/schema.ts +0 -25
- package/examples/skyport/src/vault.ts +0 -24
- package/examples/skyport/tests/support.test.ts +0 -16
- package/src/sync-templates.ts +0 -77
- package/templates/full/.env.example +0 -24
- package/templates/full/README.md +0 -65
- package/templates/full/drizzle.config.ts +0 -14
- package/templates/full/package.json +0 -18
- package/templates/full/src/ai.ts +0 -11
- package/templates/full/src/app.ts +0 -34
- package/templates/full/src/channels.ts +0 -9
- package/templates/full/src/core.ts +0 -4
- package/templates/full/src/flows/main/index.ts +0 -45
- package/templates/full/src/flows/main/shapes.ts +0 -3
- package/templates/full/src/flows/main/signals.ts +0 -8
- package/templates/full/src/gates.ts +0 -4
- package/templates/full/src/locales/ar.ts +0 -3
- package/templates/full/src/locales/en.ts +0 -3
- package/templates/full/src/schema.ts +0 -8
- package/templates/full/src/vault.ts +0 -9
- package/templates/full/tests/full.test.ts +0 -11
- package/templates/hello/.env.example +0 -9
- package/templates/hello/README.md +0 -40
- package/templates/hello/oke.config.ts +0 -3
- package/templates/hello/package.json +0 -14
- package/templates/hello/src/app.ts +0 -10
- package/templates/hello/src/flows/hello/index.ts +0 -27
- package/templates/hello/tests/hello.test.ts +0 -10
- package/templates/minimal/.env.example +0 -13
- package/templates/minimal/README.md +0 -49
- package/templates/minimal/drizzle.config.ts +0 -14
- package/templates/minimal/oke.config.ts +0 -14
- package/templates/minimal/package.json +0 -18
- package/templates/minimal/src/app.ts +0 -13
- package/templates/minimal/src/flows/main/index.ts +0 -49
- package/templates/minimal/src/schema.ts +0 -8
- package/templates/minimal/tests/minimal.test.ts +0 -13
- package/templates/standard/.env.example +0 -21
- package/templates/standard/drizzle.config.ts +0 -14
- package/templates/standard/oke.config.ts +0 -51
- package/templates/standard/src/core.ts +0 -4
- package/templates/standard/src/schema.ts +0 -9
- /package/{templates/standard → template}/package.json +0 -0
- /package/{templates/standard → template}/src/app.ts +0 -0
- /package/{templates/standard → template}/src/channels.ts +0 -0
- /package/{templates/standard → template}/src/flows/main/index.ts +0 -0
- /package/{templates/standard → template}/src/flows/main/shapes.ts +0 -0
- /package/{templates/standard → template}/src/flows/main/signals.ts +0 -0
- /package/{templates/standard → template}/src/gates.ts +0 -0
- /package/{templates/standard → template}/src/locales/ar.ts +0 -0
- /package/{templates/standard → template}/src/locales/en.ts +0 -0
- /package/{templates/standard → template}/src/vault.ts +0 -0
- /package/{templates/standard → template}/tests/standard.test.ts +0 -0
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { on, flow, gate, http } from "okengine";
|
|
2
|
-
import { eq } from "drizzle-orm";
|
|
3
|
-
import { db } from "../../core";
|
|
4
|
-
import { member, fair } from "../../gates";
|
|
5
|
-
import { orderPlaced, seatFeed } from "./signals";
|
|
6
|
-
import { NewBooking, BookingId, BookingRow, FlightFull } from "./shapes";
|
|
7
|
-
import { bookings, flights } from "../../schema";
|
|
8
|
-
|
|
9
|
-
export const canBook = gate.policy("booking:create", ({ auth }) =>
|
|
10
|
-
auth.scopes.has("booking:create"),
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
export const create = on(
|
|
14
|
-
http.post("/bookings").gate(member, canBook, fair),
|
|
15
|
-
flow({
|
|
16
|
-
slo: { availability: "99.9%", latency: { p99: "200ms" } },
|
|
17
|
-
in: NewBooking,
|
|
18
|
-
out: BookingId,
|
|
19
|
-
errors: { FlightFull },
|
|
20
|
-
do: async ({ flightId, seats }, fx) => {
|
|
21
|
-
const [flight] = await fx
|
|
22
|
-
.store(db)
|
|
23
|
-
.select()
|
|
24
|
-
.from(flights)
|
|
25
|
-
.where(eq(flights.id, flightId))
|
|
26
|
-
.limit(1);
|
|
27
|
-
if (!flight || flight.seatsAvailable < seats)
|
|
28
|
-
return fx.fail("FlightFull", { seatsLeft: flight?.seatsAvailable ?? 0 });
|
|
29
|
-
|
|
30
|
-
const id = fx.id();
|
|
31
|
-
await fx.store(db).insert(bookings).values({
|
|
32
|
-
id,
|
|
33
|
-
userId: fx.auth.userId,
|
|
34
|
-
flightId,
|
|
35
|
-
seats,
|
|
36
|
-
status: "pending",
|
|
37
|
-
createdAt: Date.now(),
|
|
38
|
-
});
|
|
39
|
-
await fx.emit(orderPlaced, { orderId: id });
|
|
40
|
-
await fx.emit(seatFeed, { flightId, left: flight.seatsAvailable - seats });
|
|
41
|
-
return { id };
|
|
42
|
-
},
|
|
43
|
-
}),
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
export const mine = on(
|
|
47
|
-
http.get("/bookings").gate(member).live(),
|
|
48
|
-
flow({
|
|
49
|
-
out: BookingRow.array(),
|
|
50
|
-
do: (_, fx) => fx.store(db).select().from(bookings).where(eq(bookings.userId, fx.auth.userId)),
|
|
51
|
-
}),
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
export const getBooking = flow({
|
|
55
|
-
in: BookingId,
|
|
56
|
-
out: BookingRow,
|
|
57
|
-
do: async ({ id }, fx) => {
|
|
58
|
-
const [b] = await fx.store(db).select().from(bookings).where(eq(bookings.id, id)).limit(1);
|
|
59
|
-
return b;
|
|
60
|
-
},
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
// The agent's second tool — refunding is a distinct, gated capability, never the same
|
|
64
|
-
// permission as reading a booking, since the agent's tool list is exactly its authority.
|
|
65
|
-
export const refundBooking = flow({
|
|
66
|
-
in: BookingId,
|
|
67
|
-
out: BookingRow,
|
|
68
|
-
do: async ({ id }, fx) => {
|
|
69
|
-
await fx.store(db).update(bookings).set({ status: "refunded" }).where(eq(bookings.id, id));
|
|
70
|
-
const [b] = await fx.store(db).select().from(bookings).where(eq(bookings.id, id)).limit(1);
|
|
71
|
-
return b;
|
|
72
|
-
},
|
|
73
|
-
});
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
export const NewBooking = z.object({ flightId: z.string(), seats: z.number().min(1).max(9) });
|
|
4
|
-
export const BookingId = z.object({ id: z.string() });
|
|
5
|
-
export const BookingRow = z.object({ id: z.string(), status: z.string(), seats: z.number() });
|
|
6
|
-
export const FlightFull = z.object({ seatsLeft: z.number() });
|
|
@@ -1,13 +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: 5,
|
|
8
|
-
deadLetter: true,
|
|
9
|
-
});
|
|
10
|
-
export const seatFeed = signal("seat-feed", {
|
|
11
|
-
schema: z.object({ flightId: z.string(), left: z.number() }),
|
|
12
|
-
delivery: "live",
|
|
13
|
-
});
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { on, flow } from "okengine";
|
|
2
|
-
import { bookingConfirmed } from "../../channels";
|
|
3
|
-
import { orderPlaced } from "../bookings/signals";
|
|
4
|
-
|
|
5
|
-
export const send = on(
|
|
6
|
-
orderPlaced,
|
|
7
|
-
flow({
|
|
8
|
-
name: "notifications.send",
|
|
9
|
-
do: async ({ id }, fx) => {
|
|
10
|
-
await fx.send(bookingConfirmed, {
|
|
11
|
-
to: fx.auth.userId ?? "guest",
|
|
12
|
-
data: { name: "Traveler", bookingId: id },
|
|
13
|
-
});
|
|
14
|
-
},
|
|
15
|
-
}),
|
|
16
|
-
);
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { flow } from "okengine";
|
|
2
|
-
import { stripeKey } from "../../vault";
|
|
3
|
-
import { ChargeIn } from "./shapes";
|
|
4
|
-
|
|
5
|
-
export const chargeBooking = flow({
|
|
6
|
-
name: "payments.chargeBooking",
|
|
7
|
-
durable: true,
|
|
8
|
-
in: ChargeIn,
|
|
9
|
-
do: async ({ orderId }, fx) => {
|
|
10
|
-
const intent = await fx.step("create-intent", () => fx.vault(stripeKey));
|
|
11
|
-
await fx.clock.sleep("verify-window", "2m");
|
|
12
|
-
return fx.step("confirm", () => ({ orderId, intent }));
|
|
13
|
-
},
|
|
14
|
-
});
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { on, flow, http } from "okengine";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { triage, support, embed, smart, fast } from "../../ai";
|
|
4
|
-
import { member } from "../../gates";
|
|
5
|
-
import { db } from "../../core";
|
|
6
|
-
import { tickets } from "../../schema";
|
|
7
|
-
|
|
8
|
-
// ① A prompt call with a provider fallback chain and a validated result
|
|
9
|
-
export const createTicket = on(
|
|
10
|
-
http.post("/tickets").gate(member),
|
|
11
|
-
flow({
|
|
12
|
-
in: z.object({ subject: z.string(), body: z.string() }),
|
|
13
|
-
out: z.object({ id: z.string(), urgency: z.string() }),
|
|
14
|
-
do: async (input, fx) => {
|
|
15
|
-
const t = await fx.ask(triage, input, { via: [smart, fast] });
|
|
16
|
-
const id = fx.id();
|
|
17
|
-
await fx
|
|
18
|
-
.store(db)
|
|
19
|
-
.insert(tickets)
|
|
20
|
-
.values({ id, ...input, ...t });
|
|
21
|
-
return { id, urgency: t.urgency };
|
|
22
|
-
},
|
|
23
|
-
}),
|
|
24
|
-
);
|
|
25
|
-
// effects → writes[sql:tickets] asks[ticket-triage v3] cost[~$0.01] nondeterministic
|
|
26
|
-
|
|
27
|
-
// ② RAG — retrieve, then answer with streaming tokens
|
|
28
|
-
export const askDocs = on(
|
|
29
|
-
http.post("/ask").gate(member).live(),
|
|
30
|
-
flow({
|
|
31
|
-
in: z.object({ question: z.string() }),
|
|
32
|
-
do: async ({ question }, fx) => {
|
|
33
|
-
const context = await fx.search(embed, question, { topK: 5 });
|
|
34
|
-
return fx.stream(smart, { prompt: "answer-with-context", data: { question, context } });
|
|
35
|
-
// streaming reaches the client through the Signal element — no separate socket layer
|
|
36
|
-
},
|
|
37
|
-
}),
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
// ③ A durable, bounded agent
|
|
41
|
-
export const supportAgent = on(
|
|
42
|
-
http.post("/support").gate(member),
|
|
43
|
-
flow({
|
|
44
|
-
durable: true, // nondeterministic calls are ALWAYS journaled
|
|
45
|
-
in: z.object({ message: z.string() }),
|
|
46
|
-
do: ({ message }, fx) => fx.run(support, { message }),
|
|
47
|
-
// the agent can only call getBooking and refundBooking, and only within THIS user's
|
|
48
|
-
// gates and tenant scope — it cannot exceed what the code declares
|
|
49
|
-
}),
|
|
50
|
-
);
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { on, flow, http } from "okengine";
|
|
2
|
-
import { member } from "../../gates";
|
|
3
|
-
import { UserProfile } from "./shapes";
|
|
4
|
-
|
|
5
|
-
export const me = on(
|
|
6
|
-
http.get("/me").gate(member),
|
|
7
|
-
flow({
|
|
8
|
-
name: "users.me",
|
|
9
|
-
out: UserProfile,
|
|
10
|
-
do: (_input, fx) => ({ id: fx.auth.userId ?? "anon" }),
|
|
11
|
-
}),
|
|
12
|
-
);
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { journey } from "okengine";
|
|
2
|
-
import { create } from "./flows/bookings";
|
|
3
|
-
import { chargeBooking } from "./flows/payments";
|
|
4
|
-
import { send } from "./flows/notifications";
|
|
5
|
-
|
|
6
|
-
journey("book-a-flight", {
|
|
7
|
-
path: [create, chargeBooking, send],
|
|
8
|
-
slo: { availability: "99.5%" },
|
|
9
|
-
});
|
|
@@ -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,25 +0,0 @@
|
|
|
1
|
-
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
|
|
2
|
-
import { id } from "okengine/store";
|
|
3
|
-
|
|
4
|
-
export const bookings = sqliteTable("bookings", {
|
|
5
|
-
id: text("id").primaryKey().$defaultFn(id),
|
|
6
|
-
userId: text("user_id").notNull(),
|
|
7
|
-
flightId: text("flight_id").notNull(),
|
|
8
|
-
seats: integer("seats").notNull(),
|
|
9
|
-
status: text("status").notNull().default("pending"),
|
|
10
|
-
createdAt: integer("created_at").notNull(),
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
export const flights = sqliteTable("flights", {
|
|
14
|
-
id: text("id").primaryKey(),
|
|
15
|
-
seatsAvailable: integer("seats_available").notNull(),
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
export const tickets = sqliteTable("tickets", {
|
|
19
|
-
id: text("id").primaryKey(),
|
|
20
|
-
subject: text("subject").notNull(),
|
|
21
|
-
body: text("body").notNull(),
|
|
22
|
-
urgency: text("urgency"),
|
|
23
|
-
team: text("team"),
|
|
24
|
-
summary: text("summary"),
|
|
25
|
-
});
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { vault } from "okengine";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
|
|
4
|
-
export const dbUrl = vault.secret("DATABASE_URL", {
|
|
5
|
-
schema: z.string().url(),
|
|
6
|
-
dev: vault.fromDocker("store.sql"),
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
export const dbReplica1 = vault.secret("DATABASE_REPLICA_1", {
|
|
10
|
-
schema: z.string().url(),
|
|
11
|
-
dev: "postgres://localhost/oke-replica",
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
export const anthropicKey = vault.secret("ANTHROPIC_KEY", {
|
|
15
|
-
schema: z.string().min(1),
|
|
16
|
-
dev: "sk-ant-test",
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
export const stripeKey = vault.secret("STRIPE_KEY", {
|
|
20
|
-
schema: z.string().startsWith("sk_"),
|
|
21
|
-
description: "Payments gateway key",
|
|
22
|
-
rotate: "90d",
|
|
23
|
-
dev: "sk_test_local",
|
|
24
|
-
});
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { test, expect } from "bun:test";
|
|
2
|
-
import { createTestApp } from "okengine/test";
|
|
3
|
-
import { app } from "../src/app";
|
|
4
|
-
import { triage } from "../src/ai";
|
|
5
|
-
|
|
6
|
-
test("AI triage is mockable and cost-bounded", async () => {
|
|
7
|
-
const t = await createTestApp(app);
|
|
8
|
-
t.ai.mock(triage, { urgency: "high", team: "ops", summary: "seat dispute" });
|
|
9
|
-
|
|
10
|
-
const u = await t.auth.loginAs({});
|
|
11
|
-
const { data } = await t.api.support.createTicket({ subject: "…", body: "…" }, { as: u });
|
|
12
|
-
|
|
13
|
-
expect(data!.urgency).toBe("high");
|
|
14
|
-
expect(t.ai.cost()).toBeLessThan(0.02); // budgets are assertable
|
|
15
|
-
await t.close();
|
|
16
|
-
});
|
package/src/sync-templates.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* prepack: copy repo-root `templates/` and `examples/` into this package so the
|
|
4
|
-
* published npm package can scaffold without the monorepo checkout.
|
|
5
|
-
*
|
|
6
|
-
* Applies the same skip rules as runtime scaffold (no node_modules, no
|
|
7
|
-
* monorepo-only docker.test.ts).
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
11
|
-
import { join, relative, resolve } from "node:path";
|
|
12
|
-
import { EXAMPLES, packageRoot, TEMPLATES } from "./templates.ts";
|
|
13
|
-
import { shouldSkipTemplatePath } from "./transform.ts";
|
|
14
|
-
|
|
15
|
-
const pkg = packageRoot();
|
|
16
|
-
const templatesRoot = resolve(pkg, "../../templates");
|
|
17
|
-
const examplesRoot = resolve(pkg, "../../examples");
|
|
18
|
-
const outTemplates = join(pkg, "templates");
|
|
19
|
-
const outExamples = join(pkg, "examples");
|
|
20
|
-
|
|
21
|
-
if (!existsSync(templatesRoot)) {
|
|
22
|
-
console.error(`create-oke prepack: templates/ not found at ${templatesRoot}`);
|
|
23
|
-
process.exit(1);
|
|
24
|
-
}
|
|
25
|
-
if (!existsSync(examplesRoot)) {
|
|
26
|
-
console.error(`create-oke prepack: examples/ not found at ${examplesRoot}`);
|
|
27
|
-
process.exit(1);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
rmSync(outTemplates, { recursive: true, force: true });
|
|
31
|
-
rmSync(outExamples, { recursive: true, force: true });
|
|
32
|
-
mkdirSync(outTemplates, { recursive: true });
|
|
33
|
-
mkdirSync(outExamples, { recursive: true });
|
|
34
|
-
|
|
35
|
-
for (const id of TEMPLATES) {
|
|
36
|
-
const src = join(templatesRoot, id);
|
|
37
|
-
const dst = join(outTemplates, id);
|
|
38
|
-
if (!existsSync(src)) {
|
|
39
|
-
console.error(`create-oke prepack: missing template ${src}`);
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
42
|
-
copyFiltered(src, dst, src);
|
|
43
|
-
console.log(`create-oke prepack: template ${id}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
for (const id of EXAMPLES) {
|
|
47
|
-
const src = join(examplesRoot, id);
|
|
48
|
-
const dst = join(outExamples, id);
|
|
49
|
-
if (!existsSync(src)) {
|
|
50
|
-
console.error(`create-oke prepack: missing example ${src}`);
|
|
51
|
-
process.exit(1);
|
|
52
|
-
}
|
|
53
|
-
copyFiltered(src, dst, src);
|
|
54
|
-
console.log(`create-oke prepack: example ${id}`);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* @param src - Source file or directory
|
|
59
|
-
* @param dst - Destination path
|
|
60
|
-
* @param root - Template root for relative skip paths
|
|
61
|
-
*/
|
|
62
|
-
function copyFiltered(src: string, dst: string, root: string): void {
|
|
63
|
-
const st = statSync(src);
|
|
64
|
-
const rel = relative(root, src).split(/[/\\]/).join("/");
|
|
65
|
-
if (rel && shouldSkipTemplatePath(rel)) return;
|
|
66
|
-
if (st.isDirectory()) {
|
|
67
|
-
mkdirSync(dst, { recursive: true });
|
|
68
|
-
for (const entry of readdirSync(src)) {
|
|
69
|
-
copyFiltered(join(src, entry), join(dst, entry), root);
|
|
70
|
-
}
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
if (st.isFile()) {
|
|
74
|
-
mkdirSync(join(dst, ".."), { recursive: true });
|
|
75
|
-
cpSync(src, dst);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
@@ -1,24 +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
|
-
# APP_SECRET — signs sessions and internal tokens; generate with `openssl rand -hex 32`
|
|
10
|
-
# Local scaffold default (also set as vault `dev:` fallback in src/vault.ts).
|
|
11
|
-
APP_SECRET=dev-only-secret
|
|
12
|
-
|
|
13
|
-
# --- Infrastructure (optional) ---
|
|
14
|
-
# Commented — this app pins postgres, redis, and smtp (Mailpit) in oke.config.ts.
|
|
15
|
-
# Run `oke dev --docker` to write role-accurate keys into `docker/.env.docker`.
|
|
16
|
-
|
|
17
|
-
# DATABASE_URL — primary Postgres connection; from your host or `docker/.env.docker` after `oke dev --docker`
|
|
18
|
-
# DATABASE_URL=postgres://user:password@127.0.0.1:5432/oke
|
|
19
|
-
|
|
20
|
-
# REDIS_URL — Redis/Valkey connection for store.kv; from your host or `docker/.env.docker` after `oke dev --docker`
|
|
21
|
-
# REDIS_URL=redis://:password@127.0.0.1:6379
|
|
22
|
-
|
|
23
|
-
# SMTP_URL — outbound email (Mailpit in docker dev); from your provider or `docker/.env.docker` after `oke dev --docker`
|
|
24
|
-
# SMTP_URL=smtp://127.0.0.1:1025
|
package/templates/full/README.md
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
# full
|
|
2
|
-
|
|
3
|
-
**Every element present and wired** — no business logic. Use when you want the
|
|
4
|
-
complete surface ready to fill in.
|
|
5
|
-
|
|
6
|
-
## Run
|
|
7
|
-
|
|
8
|
-
```bash
|
|
9
|
-
bun install
|
|
10
|
-
oke dev # app :6530 · Console :6533 · MCP :6535
|
|
11
|
-
# or: oke mode docker && oke dev # postgres · redis · Mailpit · RustFS · sops
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
Local `oke dev` auto-runs `oke db push` when `schema.ts` changes (opt out:
|
|
15
|
-
`--no-db-push`). Docker/prod never auto-apply DDL — use `oke db migrate`.
|
|
16
|
-
|
|
17
|
-
| Port | Try |
|
|
18
|
-
| ------- | ------------------------- |
|
|
19
|
-
| `:6530` | `GET /` · `GET /health` |
|
|
20
|
-
| `:6533` | Console |
|
|
21
|
-
| `:6535` | MCP |
|
|
22
|
-
|
|
23
|
-
## What’s in this template
|
|
24
|
-
|
|
25
|
-
```text
|
|
26
|
-
full/
|
|
27
|
-
├── .env.example # copied to .env.local by create-oke
|
|
28
|
-
├── drizzle.config.ts # oke db push|generate|migrate
|
|
29
|
-
├── oke.config.ts # local: sqlite · docker/prod: postgres
|
|
30
|
-
├── src/
|
|
31
|
-
│ ├── app.ts # gates, secrets, signals, stores, channel, ai
|
|
32
|
-
│ ├── core.ts / schema.ts # Store
|
|
33
|
-
│ ├── gates.ts # open gate (used on HTTP)
|
|
34
|
-
│ ├── vault.ts # appSecret
|
|
35
|
-
│ ├── channels.ts # welcome template
|
|
36
|
-
│ ├── ai.ts # stub model + echo prompt
|
|
37
|
-
│ ├── locales/en.ts · ar.ts
|
|
38
|
-
│ └── flows/main/
|
|
39
|
-
│ ├── index.ts # GET / · GET /health · every("1d")
|
|
40
|
-
│ ├── shapes.ts
|
|
41
|
-
│ └── signals.ts # pinged
|
|
42
|
-
└── tests/full.test.ts
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
- **Elements wired:** Flow · Store · Signal · Clock (`every`) · Gate · Vault · Channel · AI
|
|
46
|
-
- **Unit:** `main`
|
|
47
|
-
- **App `$options`:** `gates`, `secrets`, `signals`, `stores`, `channel`, `ai`
|
|
48
|
-
- **Flows:** `root`, `health` (emits `pinged`), plus a trivial daily cron
|
|
49
|
-
|
|
50
|
-
Replace stubs with real models, prompts, channels, and domain flows. Prefer
|
|
51
|
-
`standard` if you do not need AI / clock / vault on day one.
|
|
52
|
-
|
|
53
|
-
## Docker mode (`oke dev --docker`)
|
|
54
|
-
|
|
55
|
-
Closest to production protocols: Postgres, Redis, SMTP (Mailpit), S3 (RustFS),
|
|
56
|
-
vault `sops`/age. Compose credentials land in `docker/.env.docker`.
|
|
57
|
-
|
|
58
|
-
| Surface | URL |
|
|
59
|
-
| -------------- | ------------------------------------------------ |
|
|
60
|
-
| Mailpit UI | [http://127.0.0.1:8025](http://127.0.0.1:8025) |
|
|
61
|
-
| RustFS console | [http://127.0.0.1:9001](http://127.0.0.1:9001) |
|
|
62
|
-
|
|
63
|
-
## Agent contract
|
|
64
|
-
|
|
65
|
-
See [`AGENTS.md`](./AGENTS.md). Handbook: [okengine.vercel.app/docs](https://okengine.vercel.app/docs)
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from "drizzle-kit";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Domain schema sync for `oke db push|generate|migrate`.
|
|
5
|
-
* Versioned prod migrations land in `./drizzle` — never mix with `.oke/`.
|
|
6
|
-
*/
|
|
7
|
-
export default defineConfig({
|
|
8
|
-
dialect: "sqlite",
|
|
9
|
-
schema: "./src/schema.ts",
|
|
10
|
-
out: "./drizzle",
|
|
11
|
-
dbCredentials: {
|
|
12
|
-
url: process.env.OKE_SQLITE_URL ?? "file:.oke/app.sqlite",
|
|
13
|
-
},
|
|
14
|
-
});
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@oke/template-full",
|
|
3
|
-
"version": "0.0.1",
|
|
4
|
-
"private": true,
|
|
5
|
-
"type": "module",
|
|
6
|
-
"dependencies": {
|
|
7
|
-
"okengine": "file:../..",
|
|
8
|
-
"drizzle-orm": "^1.0.0-rc.4",
|
|
9
|
-
"zod": "^4.4.3"
|
|
10
|
-
},
|
|
11
|
-
"devDependencies": {
|
|
12
|
-
"drizzle-kit": "^1.0.0-rc.4"
|
|
13
|
-
},
|
|
14
|
-
"scripts": {
|
|
15
|
-
"test": "bun test",
|
|
16
|
-
"dev": "oke dev"
|
|
17
|
-
}
|
|
18
|
-
}
|
package/templates/full/src/ai.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { ai } from "okengine";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
|
|
4
|
-
export const stub = ai.model("stub", { provider: "anthropic", tier: "haiku" });
|
|
5
|
-
|
|
6
|
-
/** Stub prompt — replace with real versioned prompts. */
|
|
7
|
-
export const echo = stub.prompt("echo", {
|
|
8
|
-
in: z.object({ text: z.string() }),
|
|
9
|
-
out: z.object({ text: z.string() }),
|
|
10
|
-
version: 1,
|
|
11
|
-
});
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { db } from "./core";
|
|
2
|
-
import { open } from "./gates";
|
|
3
|
-
import { appSecret } from "./vault";
|
|
4
|
-
import { welcome } from "./channels";
|
|
5
|
-
import { stub, echo } from "./ai";
|
|
6
|
-
import { pinged } from "./flows/main/signals";
|
|
7
|
-
import "./channels";
|
|
8
|
-
import "./gates";
|
|
9
|
-
import "./ai";
|
|
10
|
-
import "./locales/en";
|
|
11
|
-
import "./locales/ar";
|
|
12
|
-
|
|
13
|
-
import { oke } from "okengine";
|
|
14
|
-
import * as main from "./flows/main";
|
|
15
|
-
|
|
16
|
-
export const app = oke({ name: "full" }).adopt({ main });
|
|
17
|
-
|
|
18
|
-
export type App = typeof app;
|
|
19
|
-
|
|
20
|
-
Object.assign(app.$options, {
|
|
21
|
-
env: "test",
|
|
22
|
-
gates: [open],
|
|
23
|
-
secrets: [appSecret],
|
|
24
|
-
signals: [pinged],
|
|
25
|
-
stores: [db],
|
|
26
|
-
channel: {
|
|
27
|
-
templates: [welcome],
|
|
28
|
-
defaultLocale: "en",
|
|
29
|
-
},
|
|
30
|
-
ai: {
|
|
31
|
-
models: [stub],
|
|
32
|
-
prompts: [echo],
|
|
33
|
-
},
|
|
34
|
-
});
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { channel } from "okengine";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
|
|
4
|
-
export const mail = channel.email({ from: "App <no-reply@example.com>" });
|
|
5
|
-
|
|
6
|
-
/** Stub channel template — replace with real copy. */
|
|
7
|
-
export const welcome = mail.template("welcome", {
|
|
8
|
-
schema: z.object({ name: z.string() }),
|
|
9
|
-
});
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { on, flow, http, every } from "okengine";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { open } from "../../gates";
|
|
4
|
-
import { Health } from "./shapes";
|
|
5
|
-
import { pinged } from "./signals";
|
|
6
|
-
|
|
7
|
-
/** First-run welcome — visit :6530/ after `oke dev`. */
|
|
8
|
-
export const root = on(
|
|
9
|
-
http.get("/").gate(open),
|
|
10
|
-
flow({
|
|
11
|
-
out: z.object({
|
|
12
|
-
ok: z.literal(true),
|
|
13
|
-
console: z.string(),
|
|
14
|
-
try: z.string(),
|
|
15
|
-
}),
|
|
16
|
-
do: () => ({
|
|
17
|
-
ok: true as const,
|
|
18
|
-
console: "http://127.0.0.1:6533",
|
|
19
|
-
try: "/health",
|
|
20
|
-
}),
|
|
21
|
-
}),
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
export const health = on(
|
|
25
|
-
http.get("/health").gate(open),
|
|
26
|
-
flow({
|
|
27
|
-
out: Health,
|
|
28
|
-
do: async (_input, fx) => {
|
|
29
|
-
await fx.emit(pinged, { at: Date.now() });
|
|
30
|
-
return { ok: true as const };
|
|
31
|
-
},
|
|
32
|
-
}),
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
on(pinged, flow({
|
|
36
|
-
do: async () => {},
|
|
37
|
-
}));
|
|
38
|
-
|
|
39
|
-
/** Trivial cron — replace with real schedules. */
|
|
40
|
-
on(
|
|
41
|
-
every("1d"),
|
|
42
|
-
flow({
|
|
43
|
-
do: async () => {},
|
|
44
|
-
}),
|
|
45
|
-
);
|