@hew-lang/sandbox-vm 0.7.0 → 0.8.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/dist/interpreter/v1/exec.js +28 -1
- package/dist/interpreter/v1/instructions.generated.d.ts +1 -1
- package/dist/interpreter/v1/package-validator.generated.js +7 -1
- package/dist/interpreter/v1/semaphores.d.ts +39 -0
- package/dist/interpreter/v1/semaphores.js +109 -0
- package/dist/interpreter/v1/shims.d.ts +18 -0
- package/dist/interpreter/v1/shims.js +26 -9
- package/dist/interpreter/values.d.ts +1 -1
- package/dist/interpreter/values.js +8 -0
- package/package.json +4 -4
|
@@ -16,6 +16,7 @@ import { StructuralRenderer } from "./structural.js";
|
|
|
16
16
|
import { timestampBinary } from "./timestamp.js";
|
|
17
17
|
import { selectedValue, collectionOperation, sharedOperation, } from "./value-operations.js";
|
|
18
18
|
import { Pipes } from "./pipes.js";
|
|
19
|
+
import { Semaphores } from "./semaphores.js";
|
|
19
20
|
import { FrameScheduler } from "./scheduler.js";
|
|
20
21
|
import { DeterministicIds } from "../../scheduler/ids.js";
|
|
21
22
|
import { Mt19937 } from "./mt19937.js";
|
|
@@ -128,6 +129,7 @@ class ExecutorV1 {
|
|
|
128
129
|
rootFault = null;
|
|
129
130
|
pkg;
|
|
130
131
|
directories;
|
|
132
|
+
semaphores;
|
|
131
133
|
constructor(admission, trace, stdin, policy, options) {
|
|
132
134
|
this.trace = trace;
|
|
133
135
|
this.stdin = stdin;
|
|
@@ -136,6 +138,7 @@ class ExecutorV1 {
|
|
|
136
138
|
this.pkg = pkg;
|
|
137
139
|
this.directories = new BrowserDirectories(options.directoryRoots);
|
|
138
140
|
this.scheduler = new FrameScheduler(trace, policy);
|
|
141
|
+
this.semaphores = new Semaphores((nanos, ready) => this.scheduler.after(nanos, ready));
|
|
139
142
|
this.ids = new DeterministicIds(trace.replay.seed);
|
|
140
143
|
this.host = {
|
|
141
144
|
argv: [...(options.argv ?? ["main.hew"])],
|
|
@@ -168,6 +171,7 @@ class ExecutorV1 {
|
|
|
168
171
|
}
|
|
169
172
|
},
|
|
170
173
|
pipes: this.pipes,
|
|
174
|
+
semaphores: this.semaphores,
|
|
171
175
|
generatorStream: (value) => {
|
|
172
176
|
const id = this.ids.channel();
|
|
173
177
|
this.generatorStreams.set(id, {
|
|
@@ -2080,6 +2084,25 @@ class ExecutorV1 {
|
|
|
2080
2084
|
}
|
|
2081
2085
|
return;
|
|
2082
2086
|
}
|
|
2087
|
+
case "SemaphoreAcquire": {
|
|
2088
|
+
const handle = this.boundary(act, term.inputs[0]);
|
|
2089
|
+
const timeout = term.detail.operation === "AcquireTimeout"
|
|
2090
|
+
? this.boundary(act, term.inputs[1])
|
|
2091
|
+
: null;
|
|
2092
|
+
if (timeout !== null && timeout.kind !== "i64")
|
|
2093
|
+
throw new Error("semaphore timeout must be an integer");
|
|
2094
|
+
let operation;
|
|
2095
|
+
const wake = this.park(act, term, () => { }, () => operation?.take());
|
|
2096
|
+
operation = this.semaphores.acquire(handle, timeout?.value ?? null, (acquired) => {
|
|
2097
|
+
wake(timeout === null ? UNIT : { kind: "bool", value: acquired });
|
|
2098
|
+
});
|
|
2099
|
+
const cancel = act.context.cancel;
|
|
2100
|
+
act.context.cancel = (fault) => {
|
|
2101
|
+
operation?.cancel();
|
|
2102
|
+
cancel?.(fault);
|
|
2103
|
+
};
|
|
2104
|
+
return;
|
|
2105
|
+
}
|
|
2083
2106
|
case "Sleep": {
|
|
2084
2107
|
const duration = term.inputs[0]
|
|
2085
2108
|
? this.boundary(act, term.inputs[0])
|
|
@@ -2113,7 +2136,7 @@ class ExecutorV1 {
|
|
|
2113
2136
|
message: `local actor wait cycle at ${operation}: ${path.map((actor) => actor.identity).join(" -> ")}`,
|
|
2114
2137
|
});
|
|
2115
2138
|
}
|
|
2116
|
-
park(act, term, cleanup = () => { }) {
|
|
2139
|
+
park(act, term, cleanup = () => { }, accepted = () => { }) {
|
|
2117
2140
|
this.running = false;
|
|
2118
2141
|
act.context.parked = true;
|
|
2119
2142
|
let pending = null;
|
|
@@ -2138,6 +2161,7 @@ class ExecutorV1 {
|
|
|
2138
2161
|
: term.unwind);
|
|
2139
2162
|
}
|
|
2140
2163
|
else {
|
|
2164
|
+
accepted();
|
|
2141
2165
|
if (term.result && term.result !== "never")
|
|
2142
2166
|
this.define(act, term.result.value, outcome.value);
|
|
2143
2167
|
const edge = term.resumes[outcome.edge];
|
|
@@ -2480,6 +2504,9 @@ class ExecutorV1 {
|
|
|
2480
2504
|
value.cell.weak -= 1;
|
|
2481
2505
|
}
|
|
2482
2506
|
break;
|
|
2507
|
+
case "semaphore":
|
|
2508
|
+
this.semaphores.close(value);
|
|
2509
|
+
break;
|
|
2483
2510
|
case "host_resource": {
|
|
2484
2511
|
if (value.owner.closed)
|
|
2485
2512
|
break;
|
|
@@ -119,7 +119,7 @@ export type AskDetail = {
|
|
|
119
119
|
policy: SendPolicy;
|
|
120
120
|
sealed: boolean;
|
|
121
121
|
};
|
|
122
|
-
export type AsyncIoOp = "TcpRead" | "TcpWrite" | "TcpAccept" | "TcpConnect" | "TcpConnectTimeout" | "StdinReadLine" | "SinkFinish" | "TlsConnect" | "TlsRead" | "TlsWrite" | "FileRead" | "FileWrite" | "WebSocketAccept";
|
|
122
|
+
export type AsyncIoOp = "TcpRead" | "TcpWrite" | "TcpAccept" | "TcpConnect" | "TcpConnectTimeout" | "StdinReadLine" | "SinkFinish" | "TlsConnect" | "TlsRead" | "TlsWrite" | "FileRead" | "FileWrite" | "WebSocketAccept" | "WebSocketSendText" | "WebSocketSendBinary";
|
|
123
123
|
export type BlockV1 = {
|
|
124
124
|
id: number;
|
|
125
125
|
params: Array<ValueDef>;
|
|
@@ -512,6 +512,8 @@ const schema19 = {
|
|
|
512
512
|
"FileRead",
|
|
513
513
|
"FileWrite",
|
|
514
514
|
"WebSocketAccept",
|
|
515
|
+
"WebSocketSendText",
|
|
516
|
+
"WebSocketSendBinary",
|
|
515
517
|
],
|
|
516
518
|
type: "string",
|
|
517
519
|
},
|
|
@@ -31936,6 +31938,8 @@ const schema70 = {
|
|
|
31936
31938
|
"FileRead",
|
|
31937
31939
|
"FileWrite",
|
|
31938
31940
|
"WebSocketAccept",
|
|
31941
|
+
"WebSocketSendText",
|
|
31942
|
+
"WebSocketSendBinary",
|
|
31939
31943
|
],
|
|
31940
31944
|
type: "string",
|
|
31941
31945
|
};
|
|
@@ -31973,7 +31977,9 @@ function validate230(data, { instancePath = "", parentData, parentDataProperty,
|
|
|
31973
31977
|
data === "TlsWrite" ||
|
|
31974
31978
|
data === "FileRead" ||
|
|
31975
31979
|
data === "FileWrite" ||
|
|
31976
|
-
data === "WebSocketAccept"
|
|
31980
|
+
data === "WebSocketAccept" ||
|
|
31981
|
+
data === "WebSocketSendText" ||
|
|
31982
|
+
data === "WebSocketSendBinary")) {
|
|
31977
31983
|
validate230.errors = [
|
|
31978
31984
|
{
|
|
31979
31985
|
instancePath,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { VmValue } from "../values.js";
|
|
2
|
+
interface State {
|
|
3
|
+
count: bigint;
|
|
4
|
+
waiting: Set<Acquisition>;
|
|
5
|
+
}
|
|
6
|
+
export interface SemaphoreHandle {
|
|
7
|
+
kind: "semaphore";
|
|
8
|
+
state: State;
|
|
9
|
+
closed: boolean;
|
|
10
|
+
}
|
|
11
|
+
type After = (nanos: bigint, ready: () => void) => () => void;
|
|
12
|
+
/** A released permit belongs to its selected waiter until the resume edge
|
|
13
|
+
* takes it. Cancellation before that edge hands the permit onwards. */
|
|
14
|
+
export declare class Acquisition {
|
|
15
|
+
private readonly state;
|
|
16
|
+
private readonly ready;
|
|
17
|
+
private status;
|
|
18
|
+
private cancelTimer;
|
|
19
|
+
constructor(state: State, ready: (acquired: boolean) => void);
|
|
20
|
+
start(timeoutMs: bigint | null, after: After): void;
|
|
21
|
+
reserve(): void;
|
|
22
|
+
take(): void;
|
|
23
|
+
cancel(): void;
|
|
24
|
+
private detach;
|
|
25
|
+
}
|
|
26
|
+
/** The VM executes the compiler's semaphore operations against one shared
|
|
27
|
+
* permit count. Explicit clones own independent handles, not independent counts. */
|
|
28
|
+
export declare class Semaphores {
|
|
29
|
+
private readonly after;
|
|
30
|
+
constructor(after: After);
|
|
31
|
+
create(count: bigint): VmValue;
|
|
32
|
+
clone(value: VmValue): VmValue;
|
|
33
|
+
close(value: VmValue): void;
|
|
34
|
+
count(value: VmValue): bigint;
|
|
35
|
+
tryAcquire(value: VmValue): boolean;
|
|
36
|
+
release(value: VmValue): void;
|
|
37
|
+
acquire(value: VmValue, timeoutMs: bigint | null, ready: (acquired: boolean) => void): Acquisition;
|
|
38
|
+
}
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
function handle(value) {
|
|
2
|
+
if (value.kind !== "semaphore" || value.closed)
|
|
3
|
+
throw new Error("expected a live semaphore handle");
|
|
4
|
+
return value;
|
|
5
|
+
}
|
|
6
|
+
function release(state) {
|
|
7
|
+
const waiter = state.waiting.values().next().value;
|
|
8
|
+
if (waiter)
|
|
9
|
+
waiter.reserve();
|
|
10
|
+
else if (state.count < 2147483647n)
|
|
11
|
+
state.count += 1n;
|
|
12
|
+
}
|
|
13
|
+
/** A released permit belongs to its selected waiter until the resume edge
|
|
14
|
+
* takes it. Cancellation before that edge hands the permit onwards. */
|
|
15
|
+
export class Acquisition {
|
|
16
|
+
state;
|
|
17
|
+
ready;
|
|
18
|
+
status = "waiting";
|
|
19
|
+
cancelTimer = () => { };
|
|
20
|
+
constructor(state, ready) {
|
|
21
|
+
this.state = state;
|
|
22
|
+
this.ready = ready;
|
|
23
|
+
}
|
|
24
|
+
start(timeoutMs, after) {
|
|
25
|
+
if (this.state.count > 0n) {
|
|
26
|
+
this.state.count -= 1n;
|
|
27
|
+
this.reserve();
|
|
28
|
+
}
|
|
29
|
+
else if (timeoutMs !== null && timeoutMs <= 0n) {
|
|
30
|
+
this.status = "finished";
|
|
31
|
+
this.ready(false);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
this.state.waiting.add(this);
|
|
35
|
+
if (timeoutMs !== null)
|
|
36
|
+
this.cancelTimer = after(timeoutMs * 1000000n, () => {
|
|
37
|
+
if (this.status !== "waiting")
|
|
38
|
+
return;
|
|
39
|
+
this.detach();
|
|
40
|
+
this.status = "finished";
|
|
41
|
+
this.ready(false);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
reserve() {
|
|
46
|
+
this.detach();
|
|
47
|
+
this.status = "reserved";
|
|
48
|
+
this.ready(true);
|
|
49
|
+
}
|
|
50
|
+
take() {
|
|
51
|
+
if (this.status === "reserved")
|
|
52
|
+
this.status = "finished";
|
|
53
|
+
}
|
|
54
|
+
cancel() {
|
|
55
|
+
this.detach();
|
|
56
|
+
const reserved = this.status === "reserved";
|
|
57
|
+
this.status = "finished";
|
|
58
|
+
if (reserved)
|
|
59
|
+
release(this.state);
|
|
60
|
+
}
|
|
61
|
+
detach() {
|
|
62
|
+
this.state.waiting.delete(this);
|
|
63
|
+
this.cancelTimer();
|
|
64
|
+
this.cancelTimer = () => { };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** The VM executes the compiler's semaphore operations against one shared
|
|
68
|
+
* permit count. Explicit clones own independent handles, not independent counts. */
|
|
69
|
+
export class Semaphores {
|
|
70
|
+
after;
|
|
71
|
+
constructor(after) {
|
|
72
|
+
this.after = after;
|
|
73
|
+
}
|
|
74
|
+
create(count) {
|
|
75
|
+
if (count < 0n || count > 2147483647n)
|
|
76
|
+
throw new Error("semaphore capacity is outside the supported range");
|
|
77
|
+
return {
|
|
78
|
+
kind: "semaphore",
|
|
79
|
+
state: { count, waiting: new Set() },
|
|
80
|
+
closed: false,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
clone(value) {
|
|
84
|
+
return { ...handle(value), closed: false };
|
|
85
|
+
}
|
|
86
|
+
close(value) {
|
|
87
|
+
if (value.kind !== "semaphore")
|
|
88
|
+
throw new Error("expected a semaphore handle");
|
|
89
|
+
value.closed = true;
|
|
90
|
+
}
|
|
91
|
+
count(value) {
|
|
92
|
+
return handle(value).state.count;
|
|
93
|
+
}
|
|
94
|
+
tryAcquire(value) {
|
|
95
|
+
const state = handle(value).state;
|
|
96
|
+
if (state.count === 0n)
|
|
97
|
+
return false;
|
|
98
|
+
state.count -= 1n;
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
release(value) {
|
|
102
|
+
release(handle(value).state);
|
|
103
|
+
}
|
|
104
|
+
acquire(value, timeoutMs, ready) {
|
|
105
|
+
const operation = new Acquisition(handle(value).state, ready);
|
|
106
|
+
operation.start(timeoutMs, this.after);
|
|
107
|
+
return operation;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -16,6 +16,7 @@ export interface ShimHost {
|
|
|
16
16
|
codecBridge?: (request: string) => string;
|
|
17
17
|
encodingError?(format: EncodingFormat, message?: string): string;
|
|
18
18
|
pipes?: Pipes;
|
|
19
|
+
semaphores?: import("./semaphores.js").Semaphores;
|
|
19
20
|
newPipe?(capacity: number): VmValue;
|
|
20
21
|
generatorStream?(generator: VmValue): VmValue;
|
|
21
22
|
encoderSink?(sink: VmValue, encoder: VmValue): VmValue;
|
|
@@ -107,6 +108,8 @@ declare const NATIVE_IO_ROUTES: {
|
|
|
107
108
|
readonly TlsRead: Refused;
|
|
108
109
|
readonly TlsWrite: Refused;
|
|
109
110
|
readonly WebSocketAccept: Refused;
|
|
111
|
+
readonly WebSocketSendText: Refused;
|
|
112
|
+
readonly WebSocketSendBinary: Refused;
|
|
110
113
|
readonly FileRead: "FileRead";
|
|
111
114
|
readonly FileWrite: "FileWrite";
|
|
112
115
|
};
|
|
@@ -207,6 +210,21 @@ export declare function resolveSuspension(term: SuspendTerm): Refused | {
|
|
|
207
210
|
resumes: Array<import("./instructions.generated.js").Edge>;
|
|
208
211
|
cancel: import("./instructions.generated.js").Edge | null;
|
|
209
212
|
unwind: import("./instructions.generated.js").Edge | null;
|
|
213
|
+
} & {
|
|
214
|
+
kind: "SemaphoreAcquire";
|
|
215
|
+
detail: import("./instructions.generated.js").SemaphoreAcquireDetail;
|
|
216
|
+
}) | ({
|
|
217
|
+
span?: import("./instructions.generated.js").SpanV1 | null | null;
|
|
218
|
+
} & {
|
|
219
|
+
op: "suspend";
|
|
220
|
+
request_shapes?: import("./instructions.generated.js").RequestShapes | null | null;
|
|
221
|
+
result_shape?: number | null | null;
|
|
222
|
+
error_shape?: number | null | null;
|
|
223
|
+
inputs: Array<import("./instructions.generated.js").BoundaryOperand>;
|
|
224
|
+
result: import("./instructions.generated.js").CallResult;
|
|
225
|
+
resumes: Array<import("./instructions.generated.js").Edge>;
|
|
226
|
+
cancel: import("./instructions.generated.js").Edge | null;
|
|
227
|
+
unwind: import("./instructions.generated.js").Edge | null;
|
|
210
228
|
} & {
|
|
211
229
|
kind: "Ask";
|
|
212
230
|
detail: import("./instructions.generated.js").AskDetail;
|
|
@@ -188,7 +188,7 @@ const SUSPENSION_ROUTES = {
|
|
|
188
188
|
Accept: refused("Accept", true),
|
|
189
189
|
SleepUntil: refused("SleepUntil"),
|
|
190
190
|
ServiceAcquire: refused("ServiceAcquire"),
|
|
191
|
-
SemaphoreAcquire:
|
|
191
|
+
SemaphoreAcquire: "SemaphoreAcquire",
|
|
192
192
|
ActorSend: refused("ActorSend"),
|
|
193
193
|
RestartWait: refused("RestartWait"),
|
|
194
194
|
CallClosure: refused("CallClosure"),
|
|
@@ -215,6 +215,8 @@ const NATIVE_IO_ROUTES = {
|
|
|
215
215
|
TlsRead: refused("NativeIo::TlsRead", true),
|
|
216
216
|
TlsWrite: refused("NativeIo::TlsWrite", true),
|
|
217
217
|
WebSocketAccept: refused("NativeIo::WebSocketAccept", true),
|
|
218
|
+
WebSocketSendText: refused("NativeIo::WebSocketSendText", true),
|
|
219
|
+
WebSocketSendBinary: refused("NativeIo::WebSocketSendBinary", true),
|
|
218
220
|
FileRead: "FileRead",
|
|
219
221
|
FileWrite: "FileWrite",
|
|
220
222
|
};
|
|
@@ -531,14 +533,17 @@ const UNIT_FAMILIES = {
|
|
|
531
533
|
ActorRequestRelease: refused("ActorRequestRelease"),
|
|
532
534
|
ActorCallFree: refused("ActorCallFree"),
|
|
533
535
|
ActorRequestTake: refused("ActorRequestTake"),
|
|
534
|
-
DurationAbs:
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
536
|
+
DurationAbs: (_host, args) => {
|
|
537
|
+
const nanos = integer(args, 0);
|
|
538
|
+
return int(nanos === -(1n << 63n) ? (1n << 63n) - 1n : nanos < 0n ? -nanos : nanos);
|
|
539
|
+
},
|
|
540
|
+
DurationHours: (_host, args) => int(integer(args, 0) / 3600000000000n),
|
|
541
|
+
DurationIsZero: (_host, args) => bool(integer(args, 0) === 0n),
|
|
542
|
+
DurationMicros: (_host, args) => int(integer(args, 0) / 1000n),
|
|
543
|
+
DurationMillis: (_host, args) => int(integer(args, 0) / 1000000n),
|
|
544
|
+
DurationMins: (_host, args) => int(integer(args, 0) / 60000000000n),
|
|
545
|
+
DurationNanos: (_host, args) => int(integer(args, 0)),
|
|
546
|
+
DurationSecs: (_host, args) => int(integer(args, 0) / 1000000000n),
|
|
542
547
|
DynBoxAlloc: refused("DynBoxAlloc"),
|
|
543
548
|
DynBoxFree: refused("DynBoxFree"),
|
|
544
549
|
HashMapContainsKeyLayout: refused("HashMapContainsKeyLayout"),
|
|
@@ -1065,6 +1070,18 @@ function bigMax(left, right) {
|
|
|
1065
1070
|
}
|
|
1066
1071
|
// ── externs ─────────────────────────────────────────────────────────────────
|
|
1067
1072
|
const EXTERN_SHIMS = {
|
|
1073
|
+
hew_semaphore_new: (host, args) => host.semaphores.create(integer(args, 0)),
|
|
1074
|
+
hew_semaphore_clone: (host, args) => host.semaphores.clone(arg(args, 0)),
|
|
1075
|
+
hew_semaphore_try_acquire: (host, args) => bool(host.semaphores.tryAcquire(arg(args, 0))),
|
|
1076
|
+
hew_semaphore_count: (host, args) => int(host.semaphores.count(arg(args, 0))),
|
|
1077
|
+
hew_semaphore_release: (host, args) => {
|
|
1078
|
+
host.semaphores.release(arg(args, 0));
|
|
1079
|
+
return UNIT;
|
|
1080
|
+
},
|
|
1081
|
+
hew_semaphore_free: (host, args) => {
|
|
1082
|
+
host.semaphores.close(arg(args, 0));
|
|
1083
|
+
return UNIT;
|
|
1084
|
+
},
|
|
1068
1085
|
hew_path_separator: () => str("/"),
|
|
1069
1086
|
hew_calendar_format: {
|
|
1070
1087
|
kind: "ordinary",
|
|
@@ -33,6 +33,10 @@ export function cloneValue(value) {
|
|
|
33
33
|
return { kind: "weak", cell: value.cell, closed: false };
|
|
34
34
|
case "host_resource":
|
|
35
35
|
throw new Error("a filesystem owner cannot be copied");
|
|
36
|
+
case "semaphore":
|
|
37
|
+
// The opaque ABI pointer is copied for calls borrowing the enclosing
|
|
38
|
+
// affine Semaphore. Only its explicit clone operation creates an owner.
|
|
39
|
+
return value;
|
|
36
40
|
case "timer":
|
|
37
41
|
throw new Error("a timer cancellation handle cannot be copied");
|
|
38
42
|
case "unit":
|
|
@@ -208,6 +212,8 @@ export function renderF64(value) {
|
|
|
208
212
|
}
|
|
209
213
|
export function renderStdout(value) {
|
|
210
214
|
switch (value.kind) {
|
|
215
|
+
case "semaphore":
|
|
216
|
+
return "Semaphore";
|
|
211
217
|
case "encoding":
|
|
212
218
|
return JSON.stringify(value.value);
|
|
213
219
|
case "host_resource":
|
|
@@ -269,6 +275,8 @@ export function renderStdout(value) {
|
|
|
269
275
|
}
|
|
270
276
|
export function toJsonValue(value) {
|
|
271
277
|
switch (value.kind) {
|
|
278
|
+
case "semaphore":
|
|
279
|
+
return "Semaphore";
|
|
272
280
|
case "encoding":
|
|
273
281
|
return value.value;
|
|
274
282
|
case "host_resource":
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hew-lang/sandbox-vm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"hewSource": {
|
|
5
|
-
"commit": "
|
|
6
|
-
"tag": "v0.
|
|
5
|
+
"commit": "9322b962e46f92cc9c595b7cf742f73202751d1a",
|
|
6
|
+
"tag": "v0.8.0",
|
|
7
7
|
"dirty": false
|
|
8
8
|
},
|
|
9
9
|
"description": "Deterministic TypeScript interpreter for the Hew educational sandbox",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"cli/"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@hew-lang/wasm": "0.
|
|
24
|
+
"@hew-lang/wasm": "0.8.0"
|
|
25
25
|
},
|
|
26
26
|
"keywords": [
|
|
27
27
|
"hew",
|