okengine 0.1.4 → 0.2.1
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 +1 -1
- package/README.md +48 -15
- package/docs/spec/{complete-example.md → example.md} +0 -1
- package/docs/spec/unified-theory.md +1 -1
- package/package.json +21 -8
- package/src/auth/index.ts +1 -0
- package/src/cli/dev.ts +3 -1
- package/src/cli/doc-drift.ts +54 -21
- package/src/client/index.ts +1 -0
- package/src/config/index.ts +1 -0
- package/src/console/index.ts +26 -13
- package/src/console/server/app.ts +44 -7
- package/src/console/server/flows.ts +2 -6
- package/src/console/server/index.ts +2 -1
- package/src/console/server/lazy-panels.test.ts +27 -0
- package/src/console/server/panel-load.ts +28 -0
- package/src/console/server/plugin.ts +1 -1
- package/src/console/server/plugins.ts +7 -6
- package/src/console/server/public-flows.ts +12 -0
- package/src/console/server/state.ts +159 -122
- package/src/console/server/store.ts +13 -10
- package/src/drivers/index.ts +2 -6
- package/src/drivers/vault-sops.ts +20 -1
- package/src/elements/ai.ts +1 -0
- package/src/elements/channel.ts +1 -0
- package/src/elements/clock.ts +1 -0
- package/src/elements/gate.ts +1 -0
- package/src/elements/signal.ts +1 -0
- package/src/elements/store.ts +1 -0
- package/src/elements/vault.ts +1 -0
- package/src/index.ts +9 -0
- package/src/kernel/app.ts +1 -1
- package/src/kernel/boot-bind/ai.ts +31 -0
- package/src/kernel/boot-bind/channel.ts +27 -0
- package/src/kernel/boot-bind/clock.ts +74 -0
- package/src/kernel/boot-bind/gate.ts +28 -0
- package/src/kernel/boot-bind/runs.ts +28 -0
- package/src/kernel/boot-bind/signal.ts +68 -0
- package/src/kernel/boot-bind/store.ts +47 -0
- package/src/kernel/boot-bind/vault.ts +36 -0
- package/src/kernel/boot.test.ts +85 -1
- package/src/kernel/boot.ts +250 -212
- package/src/kernel/index.ts +2 -0
- package/src/mcp/data.ts +1 -0
- package/src/mcp/docs-index.ts +252 -0
- package/src/mcp/docs-mcp.test.ts +176 -0
- package/src/mcp/docs-server.ts +233 -0
- package/src/mcp/docs-tools.ts +143 -0
- package/src/mcp/index.ts +30 -5
- package/src/mcp/protocol.ts +2 -1
- package/src/release/exports.test.ts +71 -0
- package/src/release/exports.ts +156 -0
- package/src/release/index.ts +21 -0
- package/src/release/limits.ts +9 -0
- package/src/release/measure.exports.test.ts +82 -0
- package/src/release/measure.ts +297 -14
- package/src/release/publish.ts +14 -3
- package/src/release/readme.test.ts +61 -0
- package/src/release/readme.ts +13 -0
- package/src/runs/index.ts +1 -0
- package/src/runtime/index.ts +1 -0
- package/src/runtime/security.test.ts +6 -1
- package/src/runtime/types.ts +6 -0
- package/src/test/create-test-app.ts +2 -0
- package/src/test/index.ts +1 -0
- package/src/console/ui/dist/assets/index-B71Yl_SS.js +0 -10
- package/src/console/ui/dist/assets/panel-access-Dd37LU2c.js +0 -64
- package/src/console/ui/dist/assets/panel-ai-CC7LR6-J.js +0 -1
- package/src/console/ui/dist/assets/panel-architecture-B5b3iKCz.js +0 -1
- package/src/console/ui/dist/assets/panel-channels-CeNjTKXp.js +0 -1
- package/src/console/ui/dist/assets/panel-clock-DgFTLoHV.js +0 -1
- package/src/console/ui/dist/assets/panel-diff-DxehccqB.js +0 -1
- package/src/console/ui/dist/assets/panel-flows-BtrVn-Eg.js +0 -45
- package/src/console/ui/dist/assets/panel-gates-Z9MKRGdH.js +0 -1
- package/src/console/ui/dist/assets/panel-overview-Bd48d9km.js +0 -1
- package/src/console/ui/dist/assets/panel-plugins-DWd0TowH.js +0 -1
- package/src/console/ui/dist/assets/panel-runs-BwsWqKeB.js +0 -1
- package/src/console/ui/dist/assets/panel-signals-9najbZY2.js +0 -1
- package/src/console/ui/dist/assets/panel-store-OHkP2pDp.js +0 -1
- package/src/console/ui/dist/assets/panel-traces-tn2JoY8U.js +0 -1
- package/src/console/ui/dist/assets/panel-vault-BbfWdox0.js +0 -1
- package/src/console/ui/dist/assets/rolldown-runtime-CNC7AqOf.js +0 -1
- package/src/console/ui/dist/assets/style-Cnl7WLya.css +0 -3
- package/src/console/ui/dist/index.html +0 -14
|
@@ -467,6 +467,18 @@ export interface StoreEditInput {
|
|
|
467
467
|
readonly confirmed?: boolean;
|
|
468
468
|
}
|
|
469
469
|
|
|
470
|
+
/** Result of a direct store edit (preview or applied). */
|
|
471
|
+
export interface StoreEditResult {
|
|
472
|
+
readonly ok: true;
|
|
473
|
+
readonly dryRun: boolean;
|
|
474
|
+
readonly willNotFire: ConsoleWillNotFire;
|
|
475
|
+
readonly applied: boolean;
|
|
476
|
+
readonly wouldHaveFired: ReadonlyArray<{
|
|
477
|
+
readonly kind: "send" | "ask";
|
|
478
|
+
readonly resource: string;
|
|
479
|
+
}>;
|
|
480
|
+
}
|
|
481
|
+
|
|
470
482
|
/**
|
|
471
483
|
* Preview or apply a direct row/key edit. Direct edit is NOT a flow execution.
|
|
472
484
|
*
|
|
@@ -483,16 +495,7 @@ export async function editStore(
|
|
|
483
495
|
readonly production: boolean;
|
|
484
496
|
readonly dryRun?: boolean;
|
|
485
497
|
},
|
|
486
|
-
): Promise<{
|
|
487
|
-
readonly ok: true;
|
|
488
|
-
readonly dryRun: boolean;
|
|
489
|
-
readonly willNotFire: ConsoleWillNotFire;
|
|
490
|
-
readonly applied: boolean;
|
|
491
|
-
readonly wouldHaveFired: ReadonlyArray<{
|
|
492
|
-
readonly kind: "send" | "ask";
|
|
493
|
-
readonly resource: string;
|
|
494
|
-
}>;
|
|
495
|
-
}> {
|
|
498
|
+
): Promise<StoreEditResult> {
|
|
496
499
|
const effectRef = (
|
|
497
500
|
input.child && input.ref.startsWith("sql:")
|
|
498
501
|
? `sql:${input.child}`
|
package/src/drivers/index.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Protocol-named drivers, tree-shaken by the Manifest.
|
|
3
3
|
*
|
|
4
4
|
* Subpath: `okengine/drivers/*`
|
|
5
|
+
* @module
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
export type {
|
|
@@ -115,12 +116,7 @@ export {
|
|
|
115
116
|
createSignalNatsFake,
|
|
116
117
|
} from "./signal-nats.ts";
|
|
117
118
|
|
|
118
|
-
|
|
119
|
-
runSqlConformance,
|
|
120
|
-
runKvConformance,
|
|
121
|
-
runFilesConformance,
|
|
122
|
-
runIndexConformance,
|
|
123
|
-
} from "./conformance.ts";
|
|
119
|
+
// Conformance helpers are test-only — import from `okengine/drivers/conformance`.
|
|
124
120
|
|
|
125
121
|
export type {
|
|
126
122
|
VaultDriverId,
|
|
@@ -3,15 +3,32 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Pure TypeScript, in-process. No Go `sops` / `age` binary.
|
|
5
5
|
* Decrypts the SOPS data key with age, then AES-256-GCM value payloads.
|
|
6
|
+
*
|
|
7
|
+
* `age-encryption` is an optional peer — loaded only when this driver runs.
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
|
-
import * as age from "age-encryption";
|
|
9
10
|
import type {
|
|
10
11
|
VaultBag,
|
|
11
12
|
VaultDriver,
|
|
12
13
|
VaultOpenOptions,
|
|
13
14
|
} from "./vault-types.ts";
|
|
14
15
|
|
|
16
|
+
/** Minimal Typage surface used by this driver. */
|
|
17
|
+
type AgeModule = typeof import("age-encryption");
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Lazy-load Typage so apps that never open a sops vault skip the dependency.
|
|
21
|
+
*/
|
|
22
|
+
async function loadAge(): Promise<AgeModule> {
|
|
23
|
+
try {
|
|
24
|
+
return await import("age-encryption");
|
|
25
|
+
} catch {
|
|
26
|
+
throw new Error(
|
|
27
|
+
"sops vault: install optional peer `age-encryption` (bun add age-encryption)",
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
15
32
|
/** SOPS metadata block. */
|
|
16
33
|
interface SopsMeta {
|
|
17
34
|
readonly age?: ReadonlyArray<{
|
|
@@ -109,6 +126,7 @@ async function decryptDataKey(
|
|
|
109
126
|
identity: string,
|
|
110
127
|
encBlock: string,
|
|
111
128
|
): Promise<Uint8Array> {
|
|
129
|
+
const age = await loadAge();
|
|
112
130
|
const d = new age.Decrypter();
|
|
113
131
|
d.addIdentity(identity.trim());
|
|
114
132
|
const trimmed = encBlock.trim();
|
|
@@ -182,6 +200,7 @@ export async function buildSopsFixture(
|
|
|
182
200
|
recipient: string,
|
|
183
201
|
dataKey?: Uint8Array,
|
|
184
202
|
): Promise<{ json: string; dataKey: Uint8Array }> {
|
|
203
|
+
const age = await loadAge();
|
|
185
204
|
const key = dataKey ?? crypto.getRandomValues(new Uint8Array(32));
|
|
186
205
|
const e = new age.Encrypter();
|
|
187
206
|
e.addRecipient(recipient);
|
package/src/elements/ai.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* (`oke eval` gates CI). Agents' tools are the app's own flows (gates apply).
|
|
10
10
|
* PII fields cannot reach a third-party model without explicit `allowPii`.
|
|
11
11
|
* Nondeterministic ⇒ journaling forced, auto-cache disabled.
|
|
12
|
+
* @module
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
export { ai } from "./ai/declare.ts";
|
package/src/elements/channel.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* MIME, attachments, retry, and the unified error hierarchy come from sently.
|
|
9
9
|
* OKE adds: declared templates with i18n, consent/opt-out, fallback chains
|
|
10
10
|
* recorded as chains, and delivery receipts.
|
|
11
|
+
* @module
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
export { channel } from "./channel/declare.ts";
|
package/src/elements/clock.ts
CHANGED
package/src/elements/gate.ts
CHANGED
package/src/elements/signal.ts
CHANGED
package/src/elements/store.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Drivers are protocol-named and live under `src/drivers/*`.
|
|
6
6
|
* All world access still goes through `fx`; this module only declares
|
|
7
7
|
* resources and provides runtime helpers (cache, replica routing, PII).
|
|
8
|
+
* @module
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
export { store, sql, kv, files, index } from "./store/declare.ts";
|
package/src/elements/vault.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* A declaration is a contract, never a value. Boot validates every contract
|
|
8
8
|
* and lists all gaps at once. Logs/traces receive fingerprints and redaction
|
|
9
9
|
* registered at boot — cleartext never appears even when passed to `fx.log`.
|
|
10
|
+
* @module
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
export {
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Full ten-export surface lands as elements ship; this entry exposes the
|
|
5
5
|
* executable core (oke · on · flow · http · fx · contracts).
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { oke, on, flow, http } from "okengine";
|
|
10
|
+
*
|
|
11
|
+
* export const app = oke({ name: "notes" });
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
6
15
|
*/
|
|
7
16
|
|
|
8
17
|
export {
|
package/src/kernel/app.ts
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
// `./boot.ts` pulls in every element + driver module (vault, store, signal,
|
|
23
23
|
// clock, gate, channel, ai, runs) — a type-only import here keeps that whole
|
|
24
24
|
// graph out of the cold-start path; `doBoot` below loads it lazily, only
|
|
25
|
-
// when an app actually boots (AGENTS.md budget: cold start < 75 ms).
|
|
25
|
+
// when an app actually boots (AGENTS.md / unified-theory budget: cold start < 75 ms).
|
|
26
26
|
import type { BootOptions, BootResult, ElementRuntimes } from "./boot.ts";
|
|
27
27
|
import type { CapabilityToken } from "./capability.ts";
|
|
28
28
|
import {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy AI binder — loaded only when AI is declared.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { mockAiDriver } from "../../drivers/ai-mock.ts";
|
|
6
|
+
import {
|
|
7
|
+
createAiRuntime,
|
|
8
|
+
type AiRuntime,
|
|
9
|
+
} from "../../elements/ai.ts";
|
|
10
|
+
import type { GateRuntime } from "../../elements/gate.ts";
|
|
11
|
+
import type { BootOptions } from "../boot.ts";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Construct an AI runtime (mock default; shares gate for agents).
|
|
15
|
+
*
|
|
16
|
+
* @param options - Boot options
|
|
17
|
+
* @param gate - Gate runtime for agent tool checks
|
|
18
|
+
* @param now - Clock
|
|
19
|
+
*/
|
|
20
|
+
export function bindAi(
|
|
21
|
+
options: BootOptions,
|
|
22
|
+
gate: GateRuntime | undefined,
|
|
23
|
+
now: () => number,
|
|
24
|
+
): AiRuntime {
|
|
25
|
+
return createAiRuntime({
|
|
26
|
+
...(options.ai ?? {}),
|
|
27
|
+
defaultDriver: options.ai?.defaultDriver ?? mockAiDriver,
|
|
28
|
+
gates: options.ai?.gates ?? gate,
|
|
29
|
+
now,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy channel binder — loaded only when Channel is declared.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { openConsoleChannel } from "../../drivers/channel-console.ts";
|
|
6
|
+
import {
|
|
7
|
+
createChannelRuntime,
|
|
8
|
+
type ChannelRuntime,
|
|
9
|
+
} from "../../elements/channel.ts";
|
|
10
|
+
import type { BootOptions } from "../boot.ts";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Construct a Channel runtime (console inbox default).
|
|
14
|
+
*
|
|
15
|
+
* @param options - Boot options
|
|
16
|
+
* @param now - Clock
|
|
17
|
+
*/
|
|
18
|
+
export function bindChannel(
|
|
19
|
+
options: BootOptions,
|
|
20
|
+
now: () => number,
|
|
21
|
+
): ChannelRuntime {
|
|
22
|
+
return createChannelRuntime({
|
|
23
|
+
...(options.channel ?? {}),
|
|
24
|
+
drivers: options.channel?.drivers ?? [openConsoleChannel()],
|
|
25
|
+
now,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy clock binder — loaded only when Clock is declared.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
clock as declareClock,
|
|
7
|
+
createClockRuntime,
|
|
8
|
+
createTestClockRuntime,
|
|
9
|
+
type ClockDecl,
|
|
10
|
+
type ClockRuntime,
|
|
11
|
+
} from "../../elements/clock.ts";
|
|
12
|
+
import {
|
|
13
|
+
resolveDriverId,
|
|
14
|
+
type ConfigEnv,
|
|
15
|
+
} from "../../config/index.ts";
|
|
16
|
+
import type { BootOptions } from "../boot.ts";
|
|
17
|
+
|
|
18
|
+
/** Result of binding a clock runtime. */
|
|
19
|
+
export interface BindClockResult {
|
|
20
|
+
readonly clock: ClockRuntime;
|
|
21
|
+
readonly clockDecls: ReadonlyMap<string, ClockDecl>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Construct / adopt a Clock runtime, register decls, reconcile.
|
|
26
|
+
*
|
|
27
|
+
* @param options - Boot options
|
|
28
|
+
* @param env - Active environment
|
|
29
|
+
* @param now - Clock
|
|
30
|
+
* @param prebuilt - Optional injected runtime
|
|
31
|
+
*/
|
|
32
|
+
export async function bindClock(
|
|
33
|
+
options: BootOptions,
|
|
34
|
+
env: ConfigEnv,
|
|
35
|
+
now: () => number,
|
|
36
|
+
prebuilt?: ClockRuntime,
|
|
37
|
+
): Promise<BindClockResult> {
|
|
38
|
+
const clockDriver =
|
|
39
|
+
resolveDriverId(options.config?.drivers?.clock, env) ??
|
|
40
|
+
(env === "test" ? "frozen" : "memory");
|
|
41
|
+
const clock =
|
|
42
|
+
prebuilt ??
|
|
43
|
+
(clockDriver === "frozen" || env === "test"
|
|
44
|
+
? createTestClockRuntime(now(), { instanceId: options.instanceId })
|
|
45
|
+
: createClockRuntime({ instanceId: options.instanceId, now }));
|
|
46
|
+
|
|
47
|
+
const clockDecls = new Map<string, ClockDecl>();
|
|
48
|
+
for (const c of options.clocks ?? []) {
|
|
49
|
+
clockDecls.set(c.name, c);
|
|
50
|
+
}
|
|
51
|
+
for (const b of options.bindings ?? []) {
|
|
52
|
+
if (b.trigger.kind === "every" && !clockDecls.has(b.trigger.interval)) {
|
|
53
|
+
clockDecls.set(
|
|
54
|
+
b.trigger.interval,
|
|
55
|
+
declareClock(b.trigger.interval, { every: b.trigger.interval }),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (const decl of clockDecls.values()) {
|
|
60
|
+
clock.register(decl);
|
|
61
|
+
}
|
|
62
|
+
await clock.reconcile();
|
|
63
|
+
|
|
64
|
+
if (options.onCronFire) {
|
|
65
|
+
const fire = options.onCronFire;
|
|
66
|
+
for (const name of clockDecls.keys()) {
|
|
67
|
+
clock.onCron(name, async () => {
|
|
68
|
+
await fire(name);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { clock, clockDecls };
|
|
74
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy gate binder — loaded only when Gate is declared (or required by AI).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { memoryDrivers } from "../../drivers/memory.ts";
|
|
6
|
+
import {
|
|
7
|
+
createGateRuntime,
|
|
8
|
+
type GateRuntime,
|
|
9
|
+
} from "../../elements/gate.ts";
|
|
10
|
+
import type { BootOptions } from "../boot.ts";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Construct a Gate runtime backed by a memory kv namespace.
|
|
14
|
+
*
|
|
15
|
+
* @param options - Boot options
|
|
16
|
+
* @param now - Clock
|
|
17
|
+
*/
|
|
18
|
+
export async function bindGate(
|
|
19
|
+
options: BootOptions,
|
|
20
|
+
now: () => number,
|
|
21
|
+
): Promise<GateRuntime> {
|
|
22
|
+
const kvNs = await memoryDrivers.kv.open({ name: "oke:gates" });
|
|
23
|
+
return createGateRuntime({
|
|
24
|
+
gates: options.gates ?? [],
|
|
25
|
+
kv: kvNs,
|
|
26
|
+
now,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy runs binder — loaded only when runs are requested.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
createRunsRuntime,
|
|
7
|
+
memoryRunsDriver,
|
|
8
|
+
type CreateRunsRuntimeOptions,
|
|
9
|
+
type RunsRuntime,
|
|
10
|
+
} from "../../runs/index.ts";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Construct and open a runs runtime.
|
|
14
|
+
*
|
|
15
|
+
* @param options - Create options (when not already a runtime)
|
|
16
|
+
*/
|
|
17
|
+
export async function bindRuns(
|
|
18
|
+
options?: CreateRunsRuntimeOptions,
|
|
19
|
+
): Promise<RunsRuntime> {
|
|
20
|
+
const runs = createRunsRuntime({
|
|
21
|
+
driver: options?.driver ?? memoryRunsDriver,
|
|
22
|
+
...(options ?? {}),
|
|
23
|
+
});
|
|
24
|
+
if (!runs.store) {
|
|
25
|
+
await runs.open();
|
|
26
|
+
}
|
|
27
|
+
return runs;
|
|
28
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy signal binder — loaded only when Signal is declared.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { memorySignalDriver } from "../../drivers/signal-memory.ts";
|
|
6
|
+
import {
|
|
7
|
+
createSignalRuntime,
|
|
8
|
+
type SignalRuntime,
|
|
9
|
+
} from "../../elements/signal.ts";
|
|
10
|
+
import {
|
|
11
|
+
resolveDriverId,
|
|
12
|
+
type ConfigEnv,
|
|
13
|
+
} from "../../config/index.ts";
|
|
14
|
+
import type { BootOptions } from "../boot.ts";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Construct a Signal runtime, register decls / binding names, start the bus.
|
|
18
|
+
*
|
|
19
|
+
* @param options - Boot options
|
|
20
|
+
* @param env - Active environment
|
|
21
|
+
* @param now - Clock
|
|
22
|
+
*/
|
|
23
|
+
export async function bindSignal(
|
|
24
|
+
options: BootOptions,
|
|
25
|
+
env: ConfigEnv,
|
|
26
|
+
now: () => number,
|
|
27
|
+
): Promise<SignalRuntime> {
|
|
28
|
+
const signalId =
|
|
29
|
+
resolveDriverId(options.config?.drivers?.signal, env) ?? "memory";
|
|
30
|
+
void signalId;
|
|
31
|
+
const signal = createSignalRuntime({
|
|
32
|
+
driver: memorySignalDriver,
|
|
33
|
+
now,
|
|
34
|
+
});
|
|
35
|
+
for (const decl of options.signals ?? []) {
|
|
36
|
+
signal.register(decl);
|
|
37
|
+
}
|
|
38
|
+
for (const b of options.bindings ?? []) {
|
|
39
|
+
if (b.trigger.kind === "signal") {
|
|
40
|
+
if (!signal.declarations.has(b.trigger.name)) {
|
|
41
|
+
signal.register({
|
|
42
|
+
name: b.trigger.name,
|
|
43
|
+
delivery: "once",
|
|
44
|
+
retries: 3,
|
|
45
|
+
deadLetter: true,
|
|
46
|
+
optional: true,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const bus = await signal.start();
|
|
52
|
+
|
|
53
|
+
if (options.onSignal) {
|
|
54
|
+
const handler = options.onSignal;
|
|
55
|
+
const seen = new Set<string>();
|
|
56
|
+
for (const b of options.bindings ?? []) {
|
|
57
|
+
if (b.trigger.kind !== "signal") continue;
|
|
58
|
+
const name = b.trigger.name;
|
|
59
|
+
if (seen.has(name)) continue;
|
|
60
|
+
seen.add(name);
|
|
61
|
+
await bus.subscribe(name, `oke:${name}`, async (msg) => {
|
|
62
|
+
await handler(name, msg.payload);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return signal;
|
|
68
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy store binder — loaded only when Store is declared.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { memoryDrivers } from "../../drivers/memory.ts";
|
|
6
|
+
import {
|
|
7
|
+
createStoreRuntime,
|
|
8
|
+
type StoreRuntime,
|
|
9
|
+
} from "../../elements/store.ts";
|
|
10
|
+
import {
|
|
11
|
+
resolveDriverId,
|
|
12
|
+
type ConfigEnv,
|
|
13
|
+
} from "../../config/index.ts";
|
|
14
|
+
import type { BootOptions } from "../boot.ts"; // type-only — no cycle at runtime
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Construct a Store runtime and register facet declarations.
|
|
18
|
+
*
|
|
19
|
+
* @param options - Boot options
|
|
20
|
+
* @param env - Active environment
|
|
21
|
+
* @param now - Clock
|
|
22
|
+
*/
|
|
23
|
+
export function bindStore(
|
|
24
|
+
options: BootOptions,
|
|
25
|
+
env: ConfigEnv,
|
|
26
|
+
now: () => number,
|
|
27
|
+
): StoreRuntime {
|
|
28
|
+
const sqlId =
|
|
29
|
+
resolveDriverId(options.config?.drivers?.store?.sql, env) ?? "memory";
|
|
30
|
+
const kvId =
|
|
31
|
+
resolveDriverId(options.config?.drivers?.store?.kv, env) ?? "memory";
|
|
32
|
+
void sqlId;
|
|
33
|
+
void kvId;
|
|
34
|
+
const store = createStoreRuntime({
|
|
35
|
+
drivers: {
|
|
36
|
+
sql: memoryDrivers.sql,
|
|
37
|
+
kv: memoryDrivers.kv,
|
|
38
|
+
files: memoryDrivers.files,
|
|
39
|
+
index: memoryDrivers.index,
|
|
40
|
+
},
|
|
41
|
+
now,
|
|
42
|
+
});
|
|
43
|
+
for (const decl of options.stores ?? []) {
|
|
44
|
+
store.register?.(decl);
|
|
45
|
+
}
|
|
46
|
+
return store;
|
|
47
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy vault binder — loaded only when secrets are declared.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { memoryVaultDriver } from "../../drivers/vault-memory.ts";
|
|
6
|
+
import {
|
|
7
|
+
createVaultRuntime,
|
|
8
|
+
type VaultRuntime,
|
|
9
|
+
} from "../../elements/vault.ts";
|
|
10
|
+
import type { ConfigEnv } from "../../config/index.ts";
|
|
11
|
+
import type { BootOptions } from "../boot.ts";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Construct and boot a Vault runtime from BootOptions.
|
|
15
|
+
*
|
|
16
|
+
* @param options - Boot options
|
|
17
|
+
* @param env - Active environment
|
|
18
|
+
*/
|
|
19
|
+
export async function bindVault(
|
|
20
|
+
options: BootOptions,
|
|
21
|
+
env: ConfigEnv,
|
|
22
|
+
): Promise<VaultRuntime> {
|
|
23
|
+
const vaultSecrets = options.vault?.secrets ?? options.secrets ?? [];
|
|
24
|
+
const vault = createVaultRuntime({
|
|
25
|
+
secrets: vaultSecrets,
|
|
26
|
+
chain: options.vault?.chain ?? [
|
|
27
|
+
{
|
|
28
|
+
driver: memoryVaultDriver,
|
|
29
|
+
options: { secrets: {} },
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
allowDevFallbacks: options.vault?.allowDevFallbacks ?? env !== "prod",
|
|
33
|
+
});
|
|
34
|
+
await vault.boot();
|
|
35
|
+
return vault;
|
|
36
|
+
}
|
package/src/kernel/boot.test.ts
CHANGED
|
@@ -7,10 +7,14 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { afterEach, describe, expect, jest, test } from "bun:test";
|
|
10
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
10
13
|
import { memoryVaultDriver } from "../drivers/index.ts";
|
|
11
14
|
import { clock } from "../elements/clock.ts";
|
|
15
|
+
import { store } from "../elements/store.ts";
|
|
12
16
|
import { vault, VaultBootError } from "../elements/vault.ts";
|
|
13
|
-
import { bootApplication } from "./boot.ts";
|
|
17
|
+
import { bootApplication, resolveElementNeeds } from "./boot.ts";
|
|
14
18
|
import { flow, resetFlowSeq } from "./flow.ts";
|
|
15
19
|
import { every, http } from "./triggers.ts";
|
|
16
20
|
import { oke } from "./app.ts";
|
|
@@ -42,6 +46,86 @@ describe("boot — vault gaps", () => {
|
|
|
42
46
|
expect(boot.message).toContain("DATABASE_URL");
|
|
43
47
|
}
|
|
44
48
|
});
|
|
49
|
+
|
|
50
|
+
test("three missing secrets still list every gap at once", async () => {
|
|
51
|
+
try {
|
|
52
|
+
await bootApplication({
|
|
53
|
+
env: "prod",
|
|
54
|
+
secrets: [
|
|
55
|
+
vault("A", { description: "one" }),
|
|
56
|
+
vault("B", { description: "two" }),
|
|
57
|
+
vault("C", { description: "three" }),
|
|
58
|
+
],
|
|
59
|
+
vault: {
|
|
60
|
+
allowDevFallbacks: false,
|
|
61
|
+
chain: [{ driver: memoryVaultDriver, options: { secrets: {} } }],
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
expect.unreachable("boot should fail");
|
|
65
|
+
} catch (err) {
|
|
66
|
+
expect(err).toBeInstanceOf(VaultBootError);
|
|
67
|
+
const boot = err as VaultBootError;
|
|
68
|
+
expect(boot.gaps.map((g) => g.name).sort()).toEqual(["A", "B", "C"]);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("boot — lazy element needs", () => {
|
|
74
|
+
test("Store-only declarations do not require AI/channel/vault", () => {
|
|
75
|
+
const needs = resolveElementNeeds({
|
|
76
|
+
stores: [store.sql("notes", { schema: {} as never })],
|
|
77
|
+
});
|
|
78
|
+
expect(needs.store).toBe(true);
|
|
79
|
+
expect(needs.ai).toBe(false);
|
|
80
|
+
expect(needs.channel).toBe(false);
|
|
81
|
+
expect(needs.vault).toBe(false);
|
|
82
|
+
expect(needs.signal).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("oke() Store-only graph stays under the prior 41.4 kB baseline", async () => {
|
|
86
|
+
const dir = await mkdtemp(join(tmpdir(), "oke-store-only-"));
|
|
87
|
+
const entry = join(dir, "entry.ts");
|
|
88
|
+
const appPath = join(import.meta.dir, "app.ts");
|
|
89
|
+
const storePath = join(import.meta.dir, "../elements/store.ts");
|
|
90
|
+
await Bun.write(
|
|
91
|
+
entry,
|
|
92
|
+
`import { oke } from ${JSON.stringify(appPath)};\n` +
|
|
93
|
+
`import { store } from ${JSON.stringify(storePath)};\n` +
|
|
94
|
+
`export const app = oke({ name: "notes" });\n` +
|
|
95
|
+
`export { store };\n`,
|
|
96
|
+
);
|
|
97
|
+
try {
|
|
98
|
+
const result = await Bun.build({
|
|
99
|
+
entrypoints: [entry],
|
|
100
|
+
minify: true,
|
|
101
|
+
target: "bun",
|
|
102
|
+
format: "esm",
|
|
103
|
+
external: [
|
|
104
|
+
"@duckdb/node-api",
|
|
105
|
+
"@duckdb/*",
|
|
106
|
+
"age-encryption",
|
|
107
|
+
"sently",
|
|
108
|
+
"sently/*",
|
|
109
|
+
"ajv",
|
|
110
|
+
"ajv/*",
|
|
111
|
+
"ajv-formats",
|
|
112
|
+
"oxc-parser",
|
|
113
|
+
"zod",
|
|
114
|
+
],
|
|
115
|
+
});
|
|
116
|
+
expect(result.success).toBe(true);
|
|
117
|
+
let total = 0;
|
|
118
|
+
for (const o of result.outputs) {
|
|
119
|
+
const raw = await o.arrayBuffer();
|
|
120
|
+
if (raw.byteLength === 0) continue;
|
|
121
|
+
total += Bun.gzipSync(new Uint8Array(raw)).byteLength;
|
|
122
|
+
}
|
|
123
|
+
// Prior eager-bind baseline: ~41.4 kB for oke() alone.
|
|
124
|
+
expect(total).toBeLessThan(41_400);
|
|
125
|
+
} finally {
|
|
126
|
+
await rm(dir, { recursive: true, force: true });
|
|
127
|
+
}
|
|
128
|
+
});
|
|
45
129
|
});
|
|
46
130
|
|
|
47
131
|
describe("boot — capabilities from Manifest effects", () => {
|