okengine 0.6.1 → 0.7.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.
- package/AGENTS.md +2 -2
- package/README.md +73 -181
- package/package.json +2 -2
- package/site/content/docs/console/clock.mdx +25 -5
- package/site/content/docs/console/signals.mdx +13 -4
- package/site/content/docs/elements/ai.mdx +29 -2
- package/site/content/docs/elements/channel.mdx +19 -13
- package/site/content/docs/elements/clock.mdx +56 -24
- package/site/content/docs/elements/flow.mdx +2 -0
- package/site/content/docs/elements/gate.mdx +8 -1
- package/site/content/docs/elements/signal.mdx +173 -29
- package/site/content/docs/elements/store.mdx +464 -73
- package/site/content/docs/elements/vault.mdx +15 -5
- package/site/content/docs/get-started/basic-usage.mdx +13 -6
- package/site/content/docs/get-started/installation.mdx +49 -13
- package/site/content/docs/get-started/introduction.mdx +1 -1
- package/site/content/docs/reference/cli.md +30 -2
- package/site/content/docs/reference/configuration.mdx +13 -13
- package/site/content/docs/reference/environment-variables.mdx +12 -10
- package/site/content/docs/reference/errors.mdx +12 -11
- package/site/content/docs/reference/fx.mdx +15 -13
- package/site/content/docs/reference/security.md +3 -1
- package/src/cli/ai-setup/ai-setup.test.ts +144 -0
- package/src/cli/ai-setup/apply.ts +214 -0
- package/src/cli/ai-setup/catalog.ts +263 -0
- package/src/cli/ai-setup/detect-ollama.ts +166 -0
- package/src/cli/ai-setup/index.ts +228 -0
- package/src/cli/ai-setup/prompts.ts +649 -0
- package/src/cli/ai-setup/recommend.test.ts +100 -0
- package/src/cli/ai-setup/recommend.ts +203 -0
- package/src/cli/ai.ts +38 -0
- package/src/cli/db-auto-push.test.ts +6 -1
- package/src/cli/db-auto-push.ts +8 -3
- package/src/cli/dev-db-push.test.ts +59 -0
- package/src/cli/dev.test.ts +40 -0
- package/src/cli/dev.ts +55 -1
- package/src/cli/docker-clean.test.ts +179 -0
- package/src/cli/docker-clean.ts +406 -0
- package/src/cli/docker.ts +16 -4
- package/src/cli/ensure-drizzle-config.ts +1 -1
- package/src/cli/hero-meta.test.ts +6 -5
- package/src/cli/hero-meta.ts +7 -1
- package/src/cli/index.ts +5 -0
- package/src/cli/load-config.ts +4 -4
- package/src/cli/openbao-bootstrap.test.ts +1 -0
- package/src/cli/openbao-bootstrap.ts +9 -2
- package/src/cli/registry.ts +79 -1
- package/src/client/live-gap.test.ts +35 -0
- package/src/client/transport.test.ts +21 -0
- package/src/client/transport.ts +27 -5
- package/src/compiler/fixtures/skyport/oke.config.ts +1 -1
- package/src/config/define-config.test.ts +6 -6
- package/src/config/index.ts +1 -1
- package/src/console/server/console.test.ts +45 -0
- package/src/console/server/flows.ts +39 -9
- package/src/console/server/i18n.ts +41 -0
- package/src/console/server/vault.ts +12 -38
- package/src/console/ui/dist/assets/index-ClX0ZCe5.js +10 -0
- package/src/console/ui/dist/assets/{panel-access-BGv45snf.js → panel-access-ri1X9Otj.js} +1 -1
- package/src/console/ui/dist/assets/{panel-ai-B2S7LEii.js → panel-ai-DLHjRoJk.js} +1 -1
- package/src/console/ui/dist/assets/{panel-architecture-D7UJh91v.js → panel-architecture-DI3bQF3J.js} +1 -1
- package/src/console/ui/dist/assets/{panel-channels-9T3ybqRu.js → panel-channels-BDYWX29p.js} +1 -1
- package/src/console/ui/dist/assets/{panel-clock-Cb1UXGRQ.js → panel-clock-i-JpTjVB.js} +1 -1
- package/src/console/ui/dist/assets/{panel-diff-DmYbKWmN.js → panel-diff-CFuWxiXy.js} +1 -1
- package/src/console/ui/dist/assets/{panel-flows-PiHwT55z.js → panel-flows-B8TRhcoe.js} +1 -1
- package/src/console/ui/dist/assets/{panel-gates-BQGYXvjT.js → panel-gates-CfkBgco7.js} +1 -1
- package/src/console/ui/dist/assets/{panel-overview-BBnRO18l.js → panel-overview-DONDzBd2.js} +1 -1
- package/src/console/ui/dist/assets/{panel-plugins-D0PsmVw2.js → panel-plugins-DDJwTehL.js} +1 -1
- package/src/console/ui/dist/assets/{panel-runs-CWuRDe0r.js → panel-runs-D5zf-D9c.js} +1 -1
- package/src/console/ui/dist/assets/{panel-signals-Bbg4ewpP.js → panel-signals-Dsxdu_AR.js} +1 -1
- package/src/console/ui/dist/assets/{panel-store-CPCbsDRa.js → panel-store-hy7O8HOs.js} +1 -1
- package/src/console/ui/dist/assets/{panel-traces-DVAzuA_S.js → panel-traces-CBiAe5go.js} +1 -1
- package/src/console/ui/dist/assets/{panel-vault-D1_MvOmo.js → panel-vault-Za1GyJfM.js} +1 -1
- package/src/console/ui/dist/index.html +1 -1
- package/src/console/ui/shell/components/ui.tsx +5 -1
- package/src/console/ui/shell/setup/Wizard.tsx +26 -5
- package/src/docker/cleanup.test.ts +193 -0
- package/src/docker/cleanup.ts +355 -0
- package/src/docker/docker.test.ts +4 -0
- package/src/docker/index.ts +19 -0
- package/src/docker/recipes/ollama.ts +10 -5
- package/src/drivers/ai-ollama.ts +45 -2
- package/src/drivers/ai-providers.test.ts +31 -0
- package/src/drivers/index.ts +3 -0
- package/src/drivers/signal-engine.ts +98 -25
- package/src/drivers/signal-nats.ts +4 -4
- package/src/drivers/signal-postgres.ts +158 -64
- package/src/drivers/signal-redis.ts +4 -4
- package/src/drivers/signal-types.ts +61 -2
- package/src/drivers/vault-driver-removal.test.ts +5 -1
- package/src/elements/clock/chaos-child.ts +150 -0
- package/src/elements/clock/chaos.test.ts +204 -0
- package/src/elements/clock/reconcile.ts +110 -0
- package/src/elements/clock/runtime.ts +11 -0
- package/src/elements/clock.test.ts +164 -2
- package/src/elements/clock.ts +1 -0
- package/src/elements/gate/runtime.ts +6 -0
- package/src/elements/signal/chaos-child.ts +41 -4
- package/src/elements/signal/declare.ts +4 -1
- package/src/elements/signal/delivery-modes.test.ts +179 -0
- package/src/elements/signal/key-ordering.test.ts +306 -0
- package/src/elements/signal/lease-reclaim.test.ts +123 -0
- package/src/elements/signal/optional-emit.test.ts +76 -0
- package/src/elements/signal/order-lifecycle.test.ts +130 -0
- package/src/elements/signal/orphan-messages.test.ts +152 -0
- package/src/elements/signal/runtime.ts +11 -4
- package/src/elements/signal/schema-emit.test.ts +107 -0
- package/src/elements/signal.test.ts +89 -5
- package/src/elements/store/files-fx.ts +104 -0
- package/src/elements/store/files-image.test.ts +251 -0
- package/src/elements/store/files-image.ts +494 -0
- package/src/elements/store/runtime.ts +60 -0
- package/src/elements/store.ts +24 -0
- package/src/elements/vault/boot-chain.ts +150 -0
- package/src/elements/vault/runtime.ts +8 -0
- package/src/kernel/app.ts +3 -1
- package/src/kernel/boot-bind/clock.ts +52 -7
- package/src/kernel/boot-bind/gate.ts +71 -3
- package/src/kernel/boot-bind/honor-config.test.ts +247 -0
- package/src/kernel/boot-bind/signal.ts +64 -7
- package/src/kernel/boot-bind/vault.ts +27 -8
- package/src/kernel/boot.ts +10 -2
- package/src/kernel/errors.ts +8 -2
- package/src/kernel/fx.test.ts +13 -0
- package/src/kernel/fx.ts +27 -4
- package/src/runtime/dev-request-log.test.ts +20 -1
- package/src/runtime/dev-request-log.ts +38 -0
- package/src/term.test.ts +15 -0
- package/src/term.ts +8 -3
- package/src/console/ui/dist/assets/index-CjxwRGVv.js +0 -10
|
@@ -11,21 +11,25 @@ import { dirname } from "node:path";
|
|
|
11
11
|
import type { SignalDecl } from "../elements/signal/declare.ts";
|
|
12
12
|
import type { SignalDelivery } from "../manifest/types.ts";
|
|
13
13
|
import { DryRunWriteIsolationError, setDryRunMessageId, withDryRun } from "../kernel/dry-run.ts";
|
|
14
|
-
import
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
14
|
+
import { OkeError, OKE_ERRORS } from "../kernel/errors.ts";
|
|
15
|
+
import {
|
|
16
|
+
SIGNAL_DEFAULT_LEASE_MS,
|
|
17
|
+
validateSignalEmitPayload,
|
|
18
|
+
type DeadLetter,
|
|
19
|
+
type LiveHandler,
|
|
20
|
+
type SignalBus,
|
|
21
|
+
type SignalDiscardOptions,
|
|
22
|
+
type SignalDriverId,
|
|
23
|
+
type SignalEmitOptions,
|
|
24
|
+
type SignalFailureReason,
|
|
25
|
+
type SignalHandler,
|
|
26
|
+
type SignalMessage,
|
|
27
|
+
type SignalOpenOptions,
|
|
28
|
+
type SignalReplayOptions,
|
|
29
|
+
type SignalReplayResult,
|
|
30
|
+
type SignalStats,
|
|
31
|
+
type SignalTransaction,
|
|
32
|
+
type SignalUnsubscribe,
|
|
29
33
|
} from "./signal-types.ts";
|
|
30
34
|
|
|
31
35
|
/** Internal mutable message. */
|
|
@@ -33,6 +37,8 @@ interface MutMessage {
|
|
|
33
37
|
id: string;
|
|
34
38
|
signal: string;
|
|
35
39
|
payload: unknown;
|
|
40
|
+
/** Optional per-key ordering token (`once` serialization). */
|
|
41
|
+
key: string | null;
|
|
36
42
|
delivery: SignalDelivery;
|
|
37
43
|
attempts: number;
|
|
38
44
|
failures: SignalFailureReason[];
|
|
@@ -41,6 +47,8 @@ interface MutMessage {
|
|
|
41
47
|
status: "pending" | "inflight" | "delivered" | "dead";
|
|
42
48
|
/** For once: which consumer holds the claim. */
|
|
43
49
|
lockedBy: string | null;
|
|
50
|
+
/** For once: when the claim lease expires (epoch ms); null when unlocked. */
|
|
51
|
+
leaseExpiresAt: number | null;
|
|
44
52
|
/** For broadcast: subscriber ids that already received. */
|
|
45
53
|
deliveredTo: Set<string>;
|
|
46
54
|
}
|
|
@@ -48,9 +56,10 @@ interface MutMessage {
|
|
|
48
56
|
/** Persisted snapshot for chaos recovery. */
|
|
49
57
|
interface Snapshot {
|
|
50
58
|
messages: Array<
|
|
51
|
-
Omit<MutMessage, "deliveredTo" | "lockedBy"> & {
|
|
59
|
+
Omit<MutMessage, "deliveredTo" | "lockedBy" | "leaseExpiresAt"> & {
|
|
52
60
|
deliveredTo: string[];
|
|
53
61
|
lockedBy: string | null;
|
|
62
|
+
leaseExpiresAt: number | null;
|
|
54
63
|
}
|
|
55
64
|
>;
|
|
56
65
|
writes: Array<[string, unknown]>;
|
|
@@ -76,6 +85,7 @@ export async function createSignalEngine(
|
|
|
76
85
|
const now = options.now ?? (() => Date.now());
|
|
77
86
|
const signals = options.signals;
|
|
78
87
|
const durablePath = options.durablePath;
|
|
88
|
+
const leaseMs = options.leaseMs ?? SIGNAL_DEFAULT_LEASE_MS;
|
|
79
89
|
|
|
80
90
|
let messages: MutMessage[] = [];
|
|
81
91
|
let writes = new Map<string, unknown>();
|
|
@@ -152,7 +162,9 @@ export async function createSignalEngine(
|
|
|
152
162
|
if (snap) {
|
|
153
163
|
messages = snap.messages.map((m) => ({
|
|
154
164
|
...m,
|
|
165
|
+
key: m.key ?? null,
|
|
155
166
|
lockedBy: m.lockedBy,
|
|
167
|
+
leaseExpiresAt: m.leaseExpiresAt ?? null,
|
|
156
168
|
deliveredTo: new Set(m.deliveredTo),
|
|
157
169
|
}));
|
|
158
170
|
writes = new Map(snap.writes);
|
|
@@ -166,6 +178,7 @@ export async function createSignalEngine(
|
|
|
166
178
|
id: m.id,
|
|
167
179
|
signal: m.signal,
|
|
168
180
|
payload: m.payload,
|
|
181
|
+
key: m.key,
|
|
169
182
|
delivery: m.delivery,
|
|
170
183
|
attempts: m.attempts,
|
|
171
184
|
failures: m.failures,
|
|
@@ -173,6 +186,7 @@ export async function createSignalEngine(
|
|
|
173
186
|
availableAt: m.availableAt,
|
|
174
187
|
status: m.status,
|
|
175
188
|
lockedBy: m.lockedBy,
|
|
189
|
+
leaseExpiresAt: m.leaseExpiresAt,
|
|
176
190
|
deliveredTo: [...m.deliveredTo],
|
|
177
191
|
})),
|
|
178
192
|
writes: [...writes.entries()],
|
|
@@ -189,13 +203,38 @@ export async function createSignalEngine(
|
|
|
189
203
|
return decl;
|
|
190
204
|
}
|
|
191
205
|
|
|
192
|
-
function
|
|
206
|
+
function hasSubscriber(name: string): boolean {
|
|
207
|
+
if (consumers.some((c) => c.signal === name)) return true;
|
|
208
|
+
const live = liveHandlers.get(name);
|
|
209
|
+
return live !== undefined && live.size > 0;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function assertEmitAllowed(name: string): void {
|
|
213
|
+
const decl = requireDecl(name);
|
|
214
|
+
if (decl.optional) return;
|
|
215
|
+
if (hasSubscriber(name)) return;
|
|
216
|
+
throw new OkeError(OKE_ERRORS.ORPHAN_EMIT, {
|
|
217
|
+
flow: "unknown",
|
|
218
|
+
resource: name,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function stageEmit(
|
|
223
|
+
stagedMessages: MutMessage[],
|
|
224
|
+
name: string,
|
|
225
|
+
payload: unknown,
|
|
226
|
+
options?: SignalEmitOptions,
|
|
227
|
+
): Promise<void> {
|
|
193
228
|
const decl = requireDecl(name);
|
|
229
|
+
assertEmitAllowed(name);
|
|
230
|
+
const value = await validateSignalEmitPayload(name, decl, payload);
|
|
194
231
|
const t = now();
|
|
232
|
+
const key = typeof options?.key === "string" && options.key.length > 0 ? options.key : null;
|
|
195
233
|
stagedMessages.push({
|
|
196
234
|
id: crypto.randomUUID(),
|
|
197
235
|
signal: name,
|
|
198
|
-
payload:
|
|
236
|
+
payload: value,
|
|
237
|
+
key,
|
|
199
238
|
delivery: decl.delivery,
|
|
200
239
|
attempts: 0,
|
|
201
240
|
failures: [],
|
|
@@ -203,6 +242,7 @@ export async function createSignalEngine(
|
|
|
203
242
|
availableAt: t,
|
|
204
243
|
status: "pending",
|
|
205
244
|
lockedBy: null,
|
|
245
|
+
leaseExpiresAt: null,
|
|
206
246
|
deliveredTo: new Set(),
|
|
207
247
|
});
|
|
208
248
|
}
|
|
@@ -218,9 +258,9 @@ export async function createSignalEngine(
|
|
|
218
258
|
if (done) throw new Error("transaction finished");
|
|
219
259
|
stagedWrites.set(key, value);
|
|
220
260
|
},
|
|
221
|
-
async emit(signal, payload) {
|
|
261
|
+
async emit(signal, payload, options) {
|
|
222
262
|
if (done) throw new Error("transaction finished");
|
|
223
|
-
stageEmit(stagedMessages, signal, payload);
|
|
263
|
+
await stageEmit(stagedMessages, signal, payload, options);
|
|
224
264
|
},
|
|
225
265
|
async commit() {
|
|
226
266
|
if (done) throw new Error("transaction finished");
|
|
@@ -240,9 +280,13 @@ export async function createSignalEngine(
|
|
|
240
280
|
return Promise.resolve(tx);
|
|
241
281
|
}
|
|
242
282
|
|
|
243
|
-
async function emit(
|
|
283
|
+
async function emit(
|
|
284
|
+
signal: string,
|
|
285
|
+
payload?: unknown,
|
|
286
|
+
options?: SignalEmitOptions,
|
|
287
|
+
): Promise<void> {
|
|
244
288
|
const tx = await begin();
|
|
245
|
-
await tx.emit(signal, payload);
|
|
289
|
+
await tx.emit(signal, payload, options);
|
|
246
290
|
await tx.commit();
|
|
247
291
|
}
|
|
248
292
|
|
|
@@ -287,6 +331,7 @@ export async function createSignalEngine(
|
|
|
287
331
|
id: m.id,
|
|
288
332
|
signal: m.signal,
|
|
289
333
|
payload: m.payload,
|
|
334
|
+
...(m.key !== null ? { key: m.key } : {}),
|
|
290
335
|
delivery: m.delivery,
|
|
291
336
|
attempts: m.attempts,
|
|
292
337
|
failures: [...m.failures],
|
|
@@ -296,18 +341,40 @@ export async function createSignalEngine(
|
|
|
296
341
|
};
|
|
297
342
|
}
|
|
298
343
|
|
|
344
|
+
/** True when another same-(signal, key) message holds an unexpired lease. */
|
|
345
|
+
function keyHeldByOther(candidate: MutMessage, t: number): boolean {
|
|
346
|
+
if (candidate.key === null) return false;
|
|
347
|
+
return messages.some(
|
|
348
|
+
(other) =>
|
|
349
|
+
other !== candidate &&
|
|
350
|
+
other.signal === candidate.signal &&
|
|
351
|
+
other.key === candidate.key &&
|
|
352
|
+
other.status === "inflight" &&
|
|
353
|
+
other.leaseExpiresAt !== null &&
|
|
354
|
+
other.leaseExpiresAt > t,
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
299
358
|
/**
|
|
300
|
-
* Claim the next `once` message with SKIP LOCKED
|
|
359
|
+
* Claim the next `once` message with SKIP LOCKED + lazy lease reclaim.
|
|
360
|
+
*
|
|
361
|
+
* Eligible: pending/unlocked, or inflight whose `leaseExpiresAt` has passed,
|
|
362
|
+
* and not blocked by a same-key unexpired inflight (per-key serialization).
|
|
363
|
+
* No background sweeper — reclaim happens only at claim time.
|
|
301
364
|
*/
|
|
302
365
|
function claimOnce(signal: string, consumerId: string): MutMessage | null {
|
|
303
366
|
const t = now();
|
|
304
367
|
for (const m of messages) {
|
|
305
368
|
if (m.signal !== signal || m.delivery !== "once") continue;
|
|
306
|
-
if (m.status !== "pending") continue;
|
|
307
369
|
if (m.availableAt > t) continue;
|
|
308
|
-
|
|
370
|
+
const pendingOk = m.status === "pending" && m.lockedBy === null;
|
|
371
|
+
const leaseExpired =
|
|
372
|
+
m.status === "inflight" && m.leaseExpiresAt !== null && m.leaseExpiresAt <= t;
|
|
373
|
+
if (!pendingOk && !leaseExpired) continue;
|
|
374
|
+
if (keyHeldByOther(m, t)) continue;
|
|
309
375
|
m.status = "inflight";
|
|
310
376
|
m.lockedBy = consumerId;
|
|
377
|
+
m.leaseExpiresAt = t + leaseMs;
|
|
311
378
|
m.attempts += 1;
|
|
312
379
|
return m;
|
|
313
380
|
}
|
|
@@ -320,10 +387,12 @@ export async function createSignalEngine(
|
|
|
320
387
|
await consumer.handler(toPublic(m));
|
|
321
388
|
m.status = "delivered";
|
|
322
389
|
m.lockedBy = null;
|
|
390
|
+
m.leaseExpiresAt = null;
|
|
323
391
|
noteDelivered();
|
|
324
392
|
} catch (err) {
|
|
325
393
|
m.failures.push(failureFromError(err, m.attempts));
|
|
326
394
|
m.lockedBy = null;
|
|
395
|
+
m.leaseExpiresAt = null;
|
|
327
396
|
if (m.attempts > decl.retries) {
|
|
328
397
|
if (decl.deadLetter) {
|
|
329
398
|
m.status = "dead";
|
|
@@ -348,6 +417,9 @@ export async function createSignalEngine(
|
|
|
348
417
|
const m = claimOnce(consumer.signal, consumer.subscriberId);
|
|
349
418
|
if (!m) continue;
|
|
350
419
|
progress = true;
|
|
420
|
+
// Persist claim + lease before the handler runs so a crash mid-handler
|
|
421
|
+
// leaves a reclaimable inflight row (not a silent pending that never locked).
|
|
422
|
+
await persist();
|
|
351
423
|
await deliverOnce(m, consumer);
|
|
352
424
|
}
|
|
353
425
|
return progress;
|
|
@@ -571,6 +643,7 @@ export async function createSignalEngine(
|
|
|
571
643
|
if (!options.dryRun) {
|
|
572
644
|
m.status = "pending";
|
|
573
645
|
m.lockedBy = null;
|
|
646
|
+
m.leaseExpiresAt = null;
|
|
574
647
|
m.availableAt = now();
|
|
575
648
|
m.attempts = 0;
|
|
576
649
|
if (m.delivery === "broadcast") {
|
|
@@ -87,8 +87,8 @@ export async function openNatsSignal(options: SignalOpenOptions): Promise<Signal
|
|
|
87
87
|
|
|
88
88
|
return {
|
|
89
89
|
driverId: "nats",
|
|
90
|
-
async emit(signal, payload) {
|
|
91
|
-
await outbox.emit(signal, payload);
|
|
90
|
+
async emit(signal, payload, options) {
|
|
91
|
+
await outbox.emit(signal, payload, options);
|
|
92
92
|
await relayToNats(signal, payload);
|
|
93
93
|
},
|
|
94
94
|
async begin() {
|
|
@@ -96,9 +96,9 @@ export async function openNatsSignal(options: SignalOpenOptions): Promise<Signal
|
|
|
96
96
|
const tx = await outbox.begin();
|
|
97
97
|
return {
|
|
98
98
|
write: (k, v) => tx.write(k, v),
|
|
99
|
-
async emit(signal, payload) {
|
|
99
|
+
async emit(signal, payload, options) {
|
|
100
100
|
staged.push({ signal, payload });
|
|
101
|
-
await tx.emit(signal, payload);
|
|
101
|
+
await tx.emit(signal, payload, options);
|
|
102
102
|
},
|
|
103
103
|
async commit() {
|
|
104
104
|
await tx.commit();
|