@synsema/wasm 0.6.57 → 0.6.58
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 +28 -0
- package/index.d.ts +32 -3
- package/index.mjs +65 -9
- package/package.json +1 -1
- package/synsema.wasm +0 -0
- package/worker.mjs +2 -1
package/README.md
CHANGED
|
@@ -238,6 +238,34 @@ in a handler lists only what that request read (v0.6.46: not what the top-level
|
|
|
238
238
|
handler mode: `stream` (SSE), `proxy to`, rate limits, `static` mounts, vhosts, TLS —
|
|
239
239
|
your edge platform does those before calling the handler.
|
|
240
240
|
|
|
241
|
+
## Packages: `project`, `packages` (v0.6.58)
|
|
242
|
+
|
|
243
|
+
There is no `synsema get` here and no module cache: you hand the engine the program's
|
|
244
|
+
`synsema.json` and the files of the packages it pins, and the engine checks every package
|
|
245
|
+
against its hash before the first statement runs.
|
|
246
|
+
|
|
247
|
+
```js
|
|
248
|
+
const syn = await Synsema.load(wasmBytes, {
|
|
249
|
+
project: synsemaJson, // the text of synsema.json (or the object)
|
|
250
|
+
packages: { "tree-sha256:3cdf…": { "synsema.json": "…", "m.syn": "…" } },
|
|
251
|
+
});
|
|
252
|
+
await syn.ready();
|
|
253
|
+
syn.run('use "tok" as tok\nprint(tok.encode("hola"))\n');
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
- `packages` is what `synsema get` left in the cache: per hash, every file of the package by its
|
|
257
|
+
path, the package's own `synsema.json` included. Files are text.
|
|
258
|
+
- Both options go to `load`/`loadNode` and to each call. A call's `project` replaces the
|
|
259
|
+
instance's; its `packages` add to the instance's.
|
|
260
|
+
- A package missing from `packages` stops the run with
|
|
261
|
+
`use "tok": module tok 0.1.0 is not in the packages the host passed`, and one whose files changed
|
|
262
|
+
with `… the files of tok (tree-sha256:…) do not match their hash`.
|
|
263
|
+
- The instance keeps what it verified, by hash: a later call that brings (or only names) a
|
|
264
|
+
verified hash does not hash it again, and `@synsema/wasm` does not copy it to the Worker again.
|
|
265
|
+
Each response with a `project` lists those hashes in `packages_held`.
|
|
266
|
+
- `receipt()` and the sealed receipt's header name the packages that ran
|
|
267
|
+
(`modules.packages`, with their version and hash).
|
|
268
|
+
|
|
241
269
|
## Also: `test`, `check`, `version`
|
|
242
270
|
|
|
243
271
|
`syn.test(source)` runs the file's `test` blocks; `syn.check(source)` parses and
|
package/index.d.ts
CHANGED
|
@@ -79,7 +79,28 @@ export interface Host {
|
|
|
79
79
|
sleep?: ((secs: number) => void | Promise<void>) | true;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
/**
|
|
83
|
+
* v0.6.58 — los paquetes que le pasás al motor, por su hash de árbol: `{"tree-sha256:…": {"<ruta>": "<texto>"}}`,
|
|
84
|
+
* con el `synsema.json` del paquete entre sus archivos (lo que `synsema get` dejó en el caché). El motor verifica
|
|
85
|
+
* cada hash antes de correr; un paquete que no coincide no corre.
|
|
86
|
+
*/
|
|
87
|
+
export type Packages = Record<string, Record<string, string>>;
|
|
88
|
+
|
|
89
|
+
/** v0.6.58 — el proyecto de una llamada: el `synsema.json` del programa (texto u objeto) y sus paquetes. */
|
|
90
|
+
export interface ProjectOptions {
|
|
91
|
+
/**
|
|
92
|
+
* El `synsema.json` del programa: fija qué paquete (y qué hash) es cada `use "nombre"`. El de una llamada
|
|
93
|
+
* reemplaza al de `load`/`loadNode`.
|
|
94
|
+
*/
|
|
95
|
+
project?: string | Record<string, unknown>;
|
|
96
|
+
/**
|
|
97
|
+
* Los archivos de los paquetes que alcanza el programa. Los de una llamada se suman a los de `load`/`loadNode`.
|
|
98
|
+
* La instancia guarda lo verificado por su hash: lo que ya verificó no lo vuelve a hashear ni a copiar al Worker.
|
|
99
|
+
*/
|
|
100
|
+
packages?: Packages;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface RunOptions extends ProjectOptions {
|
|
83
104
|
filename?: string;
|
|
84
105
|
/** Reemplaza al `.env`: `secret("KEY")`/`env("KEY")` resuelven de acá. */
|
|
85
106
|
env?: Record<string, string>;
|
|
@@ -184,6 +205,8 @@ export interface RunResult {
|
|
|
184
205
|
* armar, `ok` es false y `errors` lo dice.
|
|
185
206
|
*/
|
|
186
207
|
receipt?: Record<string, unknown>;
|
|
208
|
+
/** v0.6.58 — los hashes que esta instancia ya verificó (no hace falta volver a mandarlos); en toda respuesta del motor. */
|
|
209
|
+
packages_held?: string[];
|
|
187
210
|
}
|
|
188
211
|
|
|
189
212
|
export interface TestResult {
|
|
@@ -192,11 +215,15 @@ export interface TestResult {
|
|
|
192
215
|
failed: number;
|
|
193
216
|
lines: string[];
|
|
194
217
|
audit: AuditEntry[];
|
|
218
|
+
/** v0.6.58 — los hashes que esta instancia ya verificó (no hace falta volver a mandarlos); en toda respuesta del motor. */
|
|
219
|
+
packages_held?: string[];
|
|
195
220
|
}
|
|
196
221
|
|
|
197
222
|
export interface CheckResult {
|
|
198
223
|
ok: boolean;
|
|
199
224
|
errors: string[];
|
|
225
|
+
/** v0.6.58 — los hashes que esta instancia ya verificó (no hace falta volver a mandarlos); en toda respuesta del motor. */
|
|
226
|
+
packages_held?: string[];
|
|
200
227
|
}
|
|
201
228
|
|
|
202
229
|
export interface HandleRequest {
|
|
@@ -224,6 +251,8 @@ export interface HandleResult {
|
|
|
224
251
|
* del operador: el programa (y el cliente) ven en `errors` un texto fijo, porque cuál sitio cortó lo eligió el
|
|
225
252
|
* dato privado. No se lo devuelvas al modelo ni al cliente. */
|
|
226
253
|
operator_detail?: string;
|
|
254
|
+
/** v0.6.58 — los hashes que esta instancia ya verificó (no hace falta volver a mandarlos); en toda respuesta del motor. */
|
|
255
|
+
packages_held?: string[];
|
|
227
256
|
}
|
|
228
257
|
|
|
229
258
|
export type ModuleSource = WebAssembly.Module | Uint8Array | ArrayBuffer | URL | string | Response;
|
|
@@ -238,7 +267,7 @@ export declare function instantiate(
|
|
|
238
267
|
hostCall: (kind: string, payload: any) => any,
|
|
239
268
|
): Promise<{ exports: WebAssembly.Exports; call(request: object): any }>;
|
|
240
269
|
|
|
241
|
-
export interface LoadOptions {
|
|
270
|
+
export interface LoadOptions extends ProjectOptions {
|
|
242
271
|
/** Cuántos Workers usa el modo asíncrono (default 1). Con más, `runAsync` corre en paralelo. */
|
|
243
272
|
workers?: number;
|
|
244
273
|
/**
|
|
@@ -271,7 +300,7 @@ export declare class Synsema {
|
|
|
271
300
|
version(): string;
|
|
272
301
|
run(source: string, opts?: RunOptions): RunResult;
|
|
273
302
|
test(source: string, opts?: RunOptions): TestResult;
|
|
274
|
-
check(source: string, opts?: { filename?: string }): CheckResult;
|
|
303
|
+
check(source: string, opts?: { filename?: string } & ProjectOptions): CheckResult;
|
|
275
304
|
handle(source: string, request: HandleRequest, opts?: RunOptions): HandleResult;
|
|
276
305
|
/**
|
|
277
306
|
* Modo asíncrono (Worker + Atomics): los hooks del host pueden ser async. Las llamadas esperan en
|
package/index.mjs
CHANGED
|
@@ -393,6 +393,53 @@ function requestOf(op, source, opts = {}) {
|
|
|
393
393
|
return r;
|
|
394
394
|
}
|
|
395
395
|
|
|
396
|
+
/**
|
|
397
|
+
* v0.6.58 (§12, E-12) — el proyecto de una llamada: `project` (el texto de `synsema.json`, o el
|
|
398
|
+
* objeto) y `packages` (`{"tree-sha256:…": {"<ruta>": "<texto>"}}`). Los de la llamada van sobre los
|
|
399
|
+
* de `load`/`loadNode`: el `project` de la llamada reemplaza al de la instancia y sus `packages` se
|
|
400
|
+
* suman a los de la instancia.
|
|
401
|
+
*/
|
|
402
|
+
function withProject(request, defaults, opts) {
|
|
403
|
+
const project = opts.project ?? defaults.project;
|
|
404
|
+
if (project == null) {
|
|
405
|
+
if (opts.packages != null) request.packages = opts.packages;
|
|
406
|
+
return request;
|
|
407
|
+
}
|
|
408
|
+
request.project = typeof project === "string" ? project : JSON.stringify(project);
|
|
409
|
+
const packages = { ...(defaults.packages ?? {}), ...(opts.packages ?? {}) };
|
|
410
|
+
if (Object.keys(packages).length > 0) request.packages = packages;
|
|
411
|
+
return request;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* W9: lo que la instancia de wasm ya verificó no se vuelve a mandar (ni a copiar al Worker): el
|
|
416
|
+
* motor lo tiene guardado por su hash, que es el del contenido. `held` es el conjunto de esa
|
|
417
|
+
* instancia; se actualiza con `packages_held` de cada respuesta.
|
|
418
|
+
*/
|
|
419
|
+
function withoutHeld(request, held) {
|
|
420
|
+
if (!request.packages || !held || held.size === 0) return request;
|
|
421
|
+
const left = {};
|
|
422
|
+
let dropped = false;
|
|
423
|
+
for (const [h, files] of Object.entries(request.packages)) {
|
|
424
|
+
if (held.has(h)) dropped = true;
|
|
425
|
+
else left[h] = files;
|
|
426
|
+
}
|
|
427
|
+
if (!dropped) return request;
|
|
428
|
+
const r = { ...request };
|
|
429
|
+
if (Object.keys(left).length > 0) r.packages = left;
|
|
430
|
+
else delete r.packages;
|
|
431
|
+
return r;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function noteHeld(holder, response) {
|
|
435
|
+
// M6 (ronda 1): una respuesta sin `packages_held` (o un error, en `_dispatch`) no dice qué guarda
|
|
436
|
+
// la instancia, que pudo recrearse: se olvida todo y la próxima llamada manda los paquetes. Ronda 2
|
|
437
|
+
// (N-r2-3): el motor lo pone en TODA respuesta suya (`version`, sin proyecto, un error), así que
|
|
438
|
+
// sólo falta cuando no respondió el motor (un trap, el resultado que arma el Worker).
|
|
439
|
+
holder._held = response && Array.isArray(response.packages_held) ? new Set(response.packages_held) : null;
|
|
440
|
+
return response;
|
|
441
|
+
}
|
|
442
|
+
|
|
396
443
|
function normalizeRequest(request) {
|
|
397
444
|
const headers = Array.isArray(request.headers)
|
|
398
445
|
? request.headers
|
|
@@ -422,6 +469,12 @@ export class Synsema {
|
|
|
422
469
|
}
|
|
423
470
|
this._startMs = opts.startMs;
|
|
424
471
|
this._bridges = [];
|
|
472
|
+
// v0.6.58 (§12): el proyecto y los paquetes de todas las llamadas de esta instancia.
|
|
473
|
+
this._defaults = { project: opts.project, packages: opts.packages };
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
_req(op, source, opts) {
|
|
477
|
+
return withProject(requestOf(op, source, opts), this._defaults, opts);
|
|
425
478
|
}
|
|
426
479
|
|
|
427
480
|
/** Carga el .wasm (bytes, Module, URL/path, Response). */
|
|
@@ -471,7 +524,7 @@ export class Synsema {
|
|
|
471
524
|
const inst = this._instSync();
|
|
472
525
|
this._host = host;
|
|
473
526
|
try {
|
|
474
|
-
return inst.call(request);
|
|
527
|
+
return noteHeld(inst, inst.call(withoutHeld(request, inst._held)));
|
|
475
528
|
} catch (e) {
|
|
476
529
|
// Un trap deja la instancia inservible: se recrea ya (sincrónico, con el Module compilado),
|
|
477
530
|
// así la próxima llamada anda sin `await syn.ready()` (v0.6.45, D4). Si el host no deja
|
|
@@ -508,16 +561,16 @@ export class Synsema {
|
|
|
508
561
|
return this._callSync({ op: "version" }).version;
|
|
509
562
|
}
|
|
510
563
|
run(source, opts = {}) {
|
|
511
|
-
return this._callSync(
|
|
564
|
+
return this._callSync(this._req("run", source, opts), opts.host);
|
|
512
565
|
}
|
|
513
566
|
test(source, opts = {}) {
|
|
514
|
-
return this._callSync(
|
|
567
|
+
return this._callSync(this._req("test", source, opts), opts.host);
|
|
515
568
|
}
|
|
516
569
|
check(source, opts = {}) {
|
|
517
|
-
return this._callSync({ op: "check", source, filename: opts.filename ?? "<embedded>" });
|
|
570
|
+
return this._callSync(withProject({ op: "check", source, filename: opts.filename ?? "<embedded>" }, this._defaults, opts));
|
|
518
571
|
}
|
|
519
572
|
handle(source, request, opts = {}) {
|
|
520
|
-
return this._callSync({ ...
|
|
573
|
+
return this._callSync({ ...this._req("handle", source, opts), request: normalizeRequest(request) }, opts.host);
|
|
521
574
|
}
|
|
522
575
|
|
|
523
576
|
// ---- modo asíncrono (Worker + Atomics) ----
|
|
@@ -591,7 +644,10 @@ export class Synsema {
|
|
|
591
644
|
}
|
|
592
645
|
|
|
593
646
|
_dispatch(b, it) {
|
|
594
|
-
|
|
647
|
+
// W9: cada Worker tiene su instancia de wasm, y con ella lo que ya verificó.
|
|
648
|
+
b.call(withoutHeld(it.request, b._held), it.host, it.timeoutMs).then((r) => it.resolve(noteHeld(b, r)), (e) => {
|
|
649
|
+
// M6 (ronda 1): el Worker pudo recrear su instancia (un trap) o morir: no guarda nada.
|
|
650
|
+
b._held = null;
|
|
595
651
|
// No se llegó a mandar (el Worker se cerró antes): vuelve al principio de la cola. Lo que sí
|
|
596
652
|
// corrió (un timeout, un Worker que murió en el medio) NO se reintenta: pudo tener efectos.
|
|
597
653
|
if (e && e.synsemaWorkerClosed && !this._closed) {
|
|
@@ -622,13 +678,13 @@ export class Synsema {
|
|
|
622
678
|
});
|
|
623
679
|
}
|
|
624
680
|
async runAsync(source, opts = {}) {
|
|
625
|
-
return this._callAsync(
|
|
681
|
+
return this._callAsync(this._req("run", source, opts), opts);
|
|
626
682
|
}
|
|
627
683
|
async testAsync(source, opts = {}) {
|
|
628
|
-
return this._callAsync(
|
|
684
|
+
return this._callAsync(this._req("test", source, opts), opts);
|
|
629
685
|
}
|
|
630
686
|
async handleAsync(source, request, opts = {}) {
|
|
631
|
-
return this._callAsync({ ...
|
|
687
|
+
return this._callAsync({ ...this._req("handle", source, opts), request: normalizeRequest(request) }, opts);
|
|
632
688
|
}
|
|
633
689
|
/**
|
|
634
690
|
* Termina los Workers del modo asíncrono. Es FINAL (ronda 2, B1): lo que esperaba en la cola falla
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synsema/wasm",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.58",
|
|
4
4
|
"description": "Synsema — the programming language for AI agents — embedded as WebAssembly: run .syn programs inside browsers, Node/Bun/Deno and edge runtimes, lending them http/kv/llm from your app",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/synsema.wasm
CHANGED
|
Binary file
|
package/worker.mjs
CHANGED
|
@@ -84,7 +84,8 @@ onMessage(async (m) => {
|
|
|
84
84
|
post({ type: "fatal", message: `the module trapped and could not be instantiated again: ${safeMsg(e2)}` });
|
|
85
85
|
return;
|
|
86
86
|
}
|
|
87
|
-
|
|
87
|
+
// M6 (ronda 1): la instancia nueva no guarda ningún paquete, diga lo que diga la vieja.
|
|
88
|
+
if (e && e.synsemaResult) post({ type: "result", response: { ...e.synsemaResult, packages_held: [] } });
|
|
88
89
|
else post({ type: "error", message: `synsema: the module trapped (${safeMsg(e)}); the instance was recreated` });
|
|
89
90
|
}
|
|
90
91
|
}
|