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
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Files image pipeline — Bun.Image over memory files driver + fx gating.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import { deflateSync } from "node:zlib";
|
|
7
|
+
import { memoryFilesDriver } from "../../drivers/memory.ts";
|
|
8
|
+
import { createFxContext } from "../../kernel/fx.ts";
|
|
9
|
+
import {
|
|
10
|
+
createFilesImagePipeline,
|
|
11
|
+
DEFAULT_FILES_IMAGE_MAX_PIXELS,
|
|
12
|
+
putImageToBucket,
|
|
13
|
+
resolveFilesImageCtorOptions,
|
|
14
|
+
variantObjectKey,
|
|
15
|
+
} from "./files-image.ts";
|
|
16
|
+
import { createStoreRuntime, store, type FilesStoreFxHandle } from "../store.ts";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Tiny PNG whose IHDR claims `width × height` (decompression-bomb header).
|
|
20
|
+
*
|
|
21
|
+
* @param width - Claimed width
|
|
22
|
+
* @param height - Claimed height
|
|
23
|
+
*/
|
|
24
|
+
function pngClaimingPixels(width: number, height: number): Uint8Array {
|
|
25
|
+
const crcTable = (() => {
|
|
26
|
+
const table = new Uint32Array(256);
|
|
27
|
+
for (let n = 0; n < 256; n++) {
|
|
28
|
+
let c = n;
|
|
29
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
30
|
+
table[n] = c;
|
|
31
|
+
}
|
|
32
|
+
return table;
|
|
33
|
+
})();
|
|
34
|
+
const crc32 = (data: Uint8Array): number => {
|
|
35
|
+
let c = 0xffffffff;
|
|
36
|
+
for (const b of data) c = crcTable[(c ^ b) & 0xff]! ^ (c >>> 8);
|
|
37
|
+
return (c ^ 0xffffffff) >>> 0;
|
|
38
|
+
};
|
|
39
|
+
const chunk = (tag: string, data: Uint8Array): Uint8Array => {
|
|
40
|
+
const tagBytes = new TextEncoder().encode(tag);
|
|
41
|
+
const body = new Uint8Array(tagBytes.length + data.length);
|
|
42
|
+
body.set(tagBytes, 0);
|
|
43
|
+
body.set(data, tagBytes.length);
|
|
44
|
+
const out = new Uint8Array(4 + body.length + 4);
|
|
45
|
+
new DataView(out.buffer).setUint32(0, data.length);
|
|
46
|
+
out.set(body, 4);
|
|
47
|
+
new DataView(out.buffer).setUint32(4 + body.length, crc32(body));
|
|
48
|
+
return out;
|
|
49
|
+
};
|
|
50
|
+
const ihdr = new Uint8Array(13);
|
|
51
|
+
const view = new DataView(ihdr.buffer);
|
|
52
|
+
view.setUint32(0, width);
|
|
53
|
+
view.setUint32(4, height);
|
|
54
|
+
ihdr[8] = 8; // bit depth
|
|
55
|
+
ihdr[9] = 2; // RGB
|
|
56
|
+
const sig = Uint8Array.of(137, 80, 78, 71, 13, 10, 26, 10);
|
|
57
|
+
const idat = chunk("IDAT", deflateSync(Uint8Array.of(0, 255, 0, 0)));
|
|
58
|
+
const parts = [sig, chunk("IHDR", ihdr), idat, chunk("IEND", new Uint8Array())];
|
|
59
|
+
let len = 0;
|
|
60
|
+
for (const p of parts) len += p.length;
|
|
61
|
+
const png = new Uint8Array(len);
|
|
62
|
+
let o = 0;
|
|
63
|
+
for (const p of parts) {
|
|
64
|
+
png.set(p, o);
|
|
65
|
+
o += p.length;
|
|
66
|
+
}
|
|
67
|
+
return png;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Valid 32×32 red PNG (zlib). */
|
|
71
|
+
const PNG_32 = Uint8Array.from([
|
|
72
|
+
137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 32, 0, 0, 0, 32, 8, 2, 0,
|
|
73
|
+
0, 0, 252, 24, 237, 163, 0, 0, 0, 40, 73, 68, 65, 84, 120, 156, 237, 205, 177, 13, 0, 0, 12, 194,
|
|
74
|
+
48, 254, 127, 186, 125, 2, 54, 75, 153, 227, 92, 50, 109, 123, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 197,
|
|
75
|
+
30, 50, 195, 252, 46, 60, 190, 144, 144, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130,
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
function asFiles(handle: unknown): FilesStoreFxHandle {
|
|
79
|
+
if (
|
|
80
|
+
!handle ||
|
|
81
|
+
typeof handle !== "object" ||
|
|
82
|
+
!("image" in handle) ||
|
|
83
|
+
typeof (handle as FilesStoreFxHandle).image !== "function"
|
|
84
|
+
) {
|
|
85
|
+
throw new Error("expected files handle");
|
|
86
|
+
}
|
|
87
|
+
return handle as FilesStoreFxHandle;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
describe("variantObjectKey", () => {
|
|
91
|
+
test("strips extension and appends variant + format", () => {
|
|
92
|
+
expect(variantObjectKey("photos/x.jpg", "thumb", "webp")).toBe("photos/x.thumb.webp");
|
|
93
|
+
expect(variantObjectKey("x.png", "medium", "jpeg")).toBe("x.medium.jpg");
|
|
94
|
+
expect(variantObjectKey("noext", "thumb", "png")).toBe("noext.thumb.png");
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("files image maxPixels default", () => {
|
|
99
|
+
test("resolveFilesImageCtorOptions always sets the 16 MP ceiling", () => {
|
|
100
|
+
expect(DEFAULT_FILES_IMAGE_MAX_PIXELS).toBe(4096 * 4096);
|
|
101
|
+
expect(resolveFilesImageCtorOptions().maxPixels).toBe(DEFAULT_FILES_IMAGE_MAX_PIXELS);
|
|
102
|
+
expect(resolveFilesImageCtorOptions({}).maxPixels).toBe(DEFAULT_FILES_IMAGE_MAX_PIXELS);
|
|
103
|
+
expect(resolveFilesImageCtorOptions({ maxPixels: 1_000_000 }).maxPixels).toBe(1_000_000);
|
|
104
|
+
expect(resolveFilesImageCtorOptions({ maxPixels: false }).maxPixels).toBe(
|
|
105
|
+
Number.POSITIVE_INFINITY,
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("oversized image is rejected by DEFAULT with no options passed", async () => {
|
|
110
|
+
// 5000×5000 = 25 MP > DEFAULT_FILES_IMAGE_MAX_PIXELS (16 MP).
|
|
111
|
+
const bomb = pngClaimingPixels(5000, 5000);
|
|
112
|
+
expect(5000 * 5000).toBeGreaterThan(DEFAULT_FILES_IMAGE_MAX_PIXELS);
|
|
113
|
+
|
|
114
|
+
const access = {
|
|
115
|
+
get: async () => null,
|
|
116
|
+
put: async () => undefined,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
await expect(createFilesImagePipeline(access, bomb).metadata()).rejects.toMatchObject({
|
|
120
|
+
code: "ERR_IMAGE_TOO_MANY_PIXELS",
|
|
121
|
+
});
|
|
122
|
+
await expect(putImageToBucket(access, "bomb.png", bomb)).rejects.toMatchObject({
|
|
123
|
+
code: "ERR_IMAGE_TOO_MANY_PIXELS",
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("maxPixels: false opts out of the default ceiling", async () => {
|
|
128
|
+
const bomb = pngClaimingPixels(5000, 5000);
|
|
129
|
+
const access = {
|
|
130
|
+
get: async () => null,
|
|
131
|
+
put: async () => undefined,
|
|
132
|
+
};
|
|
133
|
+
const meta = await createFilesImagePipeline(access, bomb, { maxPixels: false }).metadata();
|
|
134
|
+
expect(meta).toEqual({ width: 5000, height: 5000, format: "png" });
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe("files image pipeline (bucket)", () => {
|
|
139
|
+
test("metadata / resize→webp / put / missing key", async () => {
|
|
140
|
+
const bucket = await memoryFilesDriver.open({ name: "img" });
|
|
141
|
+
const access = {
|
|
142
|
+
get: (key: string) => bucket.get(key),
|
|
143
|
+
put: (key: string, data: Uint8Array | string) => bucket.put(key, data),
|
|
144
|
+
};
|
|
145
|
+
await access.put("orig.png", PNG_32);
|
|
146
|
+
|
|
147
|
+
const meta = await createFilesImagePipeline(access, "orig.png").metadata();
|
|
148
|
+
expect(meta).toEqual({ width: 32, height: 32, format: "png" });
|
|
149
|
+
|
|
150
|
+
await createFilesImagePipeline(access, "orig.png")
|
|
151
|
+
.resize(16, 16, { fit: "inside" })
|
|
152
|
+
.webp({ quality: 80 })
|
|
153
|
+
.put("thumb.webp");
|
|
154
|
+
const thumb = await access.get("thumb.webp");
|
|
155
|
+
expect(thumb).not.toBeNull();
|
|
156
|
+
expect(thumb!.byteLength).toBeGreaterThan(0);
|
|
157
|
+
const thumbMeta = await new Bun.Image(thumb!).metadata();
|
|
158
|
+
expect(thumbMeta.format).toBe("webp");
|
|
159
|
+
expect(thumbMeta.width).toBeLessThanOrEqual(16);
|
|
160
|
+
expect(thumbMeta.height).toBeLessThanOrEqual(16);
|
|
161
|
+
|
|
162
|
+
await expect(createFilesImagePipeline(access, "missing.png").metadata()).rejects.toThrow(
|
|
163
|
+
/object not found/,
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("putImage writes original, variants, and placeholder", async () => {
|
|
168
|
+
const bucket = await memoryFilesDriver.open({ name: "img2" });
|
|
169
|
+
const access = {
|
|
170
|
+
get: (key: string) => bucket.get(key),
|
|
171
|
+
put: (key: string, data: Uint8Array | string) => bucket.put(key, data),
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const result = await putImageToBucket(access, "photos/x.png", PNG_32, {
|
|
175
|
+
variants: {
|
|
176
|
+
thumb: { resize: [16, 16, { fit: "inside" }], webp: { quality: 80 } },
|
|
177
|
+
medium: { resize: [24], webp: { quality: 85 } },
|
|
178
|
+
},
|
|
179
|
+
placeholder: true,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
expect(result.key).toBe("photos/x.png");
|
|
183
|
+
expect(result.meta).toEqual({ width: 32, height: 32, format: "png" });
|
|
184
|
+
expect(result.variants).toEqual({
|
|
185
|
+
thumb: "photos/x.thumb.webp",
|
|
186
|
+
medium: "photos/x.medium.webp",
|
|
187
|
+
});
|
|
188
|
+
expect(result.placeholder?.startsWith("data:image/png;base64,")).toBe(true);
|
|
189
|
+
|
|
190
|
+
expect(await access.get("photos/x.png")).not.toBeNull();
|
|
191
|
+
expect(await access.get("photos/x.thumb.webp")).not.toBeNull();
|
|
192
|
+
expect(await access.get("photos/x.medium.webp")).not.toBeNull();
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe("files image via fx.store", () => {
|
|
197
|
+
test("image() chains sync; terminals record read/write", async () => {
|
|
198
|
+
const decl = store.files("uploads");
|
|
199
|
+
const runtime = createStoreRuntime({
|
|
200
|
+
drivers: { files: memoryFilesDriver },
|
|
201
|
+
});
|
|
202
|
+
runtime.register(decl);
|
|
203
|
+
|
|
204
|
+
const { fx, ledger } = createFxContext({
|
|
205
|
+
flow: "img-flow",
|
|
206
|
+
effects: { reads: ["files:uploads"], writes: ["files:uploads"] },
|
|
207
|
+
storeRuntime: runtime,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const files = fx.store(decl) as FilesStoreFxHandle;
|
|
211
|
+
await files.put("hero.png", PNG_32);
|
|
212
|
+
|
|
213
|
+
const pipeline = files.image("hero.png");
|
|
214
|
+
expect(typeof pipeline.resize).toBe("function");
|
|
215
|
+
expect(pipeline).not.toBeInstanceOf(Promise);
|
|
216
|
+
|
|
217
|
+
const meta = await pipeline.metadata();
|
|
218
|
+
expect(meta.width).toBe(32);
|
|
219
|
+
|
|
220
|
+
await files
|
|
221
|
+
.image("hero.png")
|
|
222
|
+
.resize(8, 8, { fit: "inside" })
|
|
223
|
+
.webp({ quality: 70 })
|
|
224
|
+
.put("hero.thumb.webp");
|
|
225
|
+
|
|
226
|
+
const putImage = await files.putImage("a.png", PNG_32, {
|
|
227
|
+
variants: { thumb: { resize: [8], webp: { quality: 70 } } },
|
|
228
|
+
placeholder: true,
|
|
229
|
+
});
|
|
230
|
+
expect(putImage.variants.thumb).toBe("a.thumb.webp");
|
|
231
|
+
|
|
232
|
+
const kinds = ledger.entries.map((e) => e.kind);
|
|
233
|
+
expect(kinds).toContain("read");
|
|
234
|
+
expect(kinds).toContain("write");
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test("runtime handle exposes image + putImage", async () => {
|
|
238
|
+
const decl = store.files("direct");
|
|
239
|
+
const runtime = createStoreRuntime({
|
|
240
|
+
drivers: { files: memoryFilesDriver },
|
|
241
|
+
});
|
|
242
|
+
runtime.register(decl);
|
|
243
|
+
const handle = asFiles(
|
|
244
|
+
await runtime.open(decl, {
|
|
245
|
+
effects: { reads: ["files:direct"], writes: ["files:direct"] },
|
|
246
|
+
}),
|
|
247
|
+
);
|
|
248
|
+
const fromBytes = await handle.image(PNG_32).placeholder();
|
|
249
|
+
expect(fromBytes.startsWith("data:image/png;base64,")).toBe(true);
|
|
250
|
+
});
|
|
251
|
+
});
|