arcane-os 0.52.1 → 0.54.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/CHANGELOG.md +42 -0
- package/NOTICE +4 -1
- package/README.md +1 -1
- package/bin/arcane-test.mjs +2 -1
- package/browser-runtime/ai/browser-decisions.mjs +405 -0
- package/browser-runtime/ai/decision-runtime.mjs +281 -0
- package/browser-runtime/ai/decision-worker.mjs +67 -0
- package/browser-runtime/dependencies/event-pubsub/dependencies/strong-type/index.js +1151 -0
- package/browser-runtime/dependencies/event-pubsub/dependencies/strong-type/licence +21 -0
- package/browser-runtime/dependencies/event-pubsub/dependencies/strong-type/package.json +61 -0
- package/browser-runtime/dependencies/event-pubsub/index.js +2 -2
- package/browser-runtime/dependencies/event-pubsub/package.json +11 -4
- package/browser-runtime/dependencies/ws-share/WS.js +128 -0
- package/browser-runtime/dependencies/ws-share/licence.md +27 -0
- package/browser-runtime/dependencies/ws-share/package.json +64 -0
- package/browser-runtime/websocket-client.mjs +1 -0
- package/docs/event-manager.md +7 -4
- package/docs/reference/ai/browser-decisions.md +184 -0
- package/docs/reference/inventory/package-api.json +44 -2
- package/docs/reference/inventory/runtime-modules.json +17 -4
- package/docs/reference/protocols.md +76 -1
- package/docs/reference/runtime-modules.md +45 -2
- package/docs/reference/sdk-api.md +280 -6
- package/docs/reference/websocket-client.md +132 -0
- package/node_modules/event-pubsub/CHANGELOG.md +12 -1
- package/node_modules/event-pubsub/MIGRATION.md +8 -5
- package/node_modules/event-pubsub/README.md +75 -19
- package/node_modules/event-pubsub/SECURITY.md +3 -3
- package/node_modules/event-pubsub/index.js +2 -2
- package/node_modules/event-pubsub/package.json +11 -4
- package/package.json +9 -5
- package/runtime/arcane/modules/HTMLImport.js +81 -26
- package/runtime/arcane/modules/HTMLImportScript.js +3 -0
- package/src/import-map.mjs +416 -59
- package/src/index.mjs +2 -0
- package/src/packager/core.mjs +24 -6
- package/src/packager/native-modules.mjs +458 -0
- package/src/testing-loader.mjs +41 -20
- package/src/websocket.mjs +479 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.54.0
|
|
4
|
+
|
|
5
|
+
- Add `WS` through `arcane-os/websocket-client` and the Node root export,
|
|
6
|
+
using published `ws-share@3.1.0`. Compatible callers in one loaded module
|
|
7
|
+
share the exact native socket; `WS.observe()` exposes global lifecycle
|
|
8
|
+
events and `WS.getConnections()` supplies current connection records.
|
|
9
|
+
Native per-socket events, complete payloads and `acceptWebSocket` remain
|
|
10
|
+
unchanged. No automatic reconnect or cross-realm sharing is introduced.
|
|
11
|
+
- Update `event-pubsub` to `6.1.1` and preserve its own `strong-type@2.0.0`
|
|
12
|
+
browser scope alongside the SDK's existing `strong-type@2.0.1`. Managed
|
|
13
|
+
maps, installed-package routes and application test contexts carry that
|
|
14
|
+
scope. Consumers regenerate their normal public SDK projections.
|
|
15
|
+
- Add opt-in `moduleFormat: 'native'` application packaging with operation
|
|
16
|
+
`outputDirectory`, reachable module graphs, scoped import-map resolution,
|
|
17
|
+
complete selected assets and external HTMLImport script lifecycle. Correct
|
|
18
|
+
named-expression lexical scope without changing default import-map output.
|
|
19
|
+
This is a packaging capability, not a complete browser-extension scaffold.
|
|
20
|
+
- Add `createBrowserDecisionModel` through `arcane-os/ai/browser-decisions`
|
|
21
|
+
for explicit Laya typed-decisions FP16 and Julia-1 FP32 browser operations.
|
|
22
|
+
Model activation is lazy and uses the selected upstream Transformers.js
|
|
23
|
+
runtime; complete inputs/results, ordered batches, lifecycle and cancellation
|
|
24
|
+
remain observable. No chat-provider replacement, automatic model loading,
|
|
25
|
+
precision fallback, application default switch or saved-data migration occurs.
|
|
26
|
+
|
|
27
|
+
## 0.53.0
|
|
28
|
+
|
|
29
|
+
- Add `acceptWebSocket` through `arcane-os/websocket` and the Node root export.
|
|
30
|
+
Accept a caller-selected native HTTP/1.1 Upgrade and exchange complete text
|
|
31
|
+
and binary messages with fragmentation, Ping/Pong, ordered writable
|
|
32
|
+
backpressure, close/abort handling and observable connection settlement.
|
|
33
|
+
Buffered Upgrade input reaches synchronously registered listeners in order.
|
|
34
|
+
- Preserve valid text, leading Unicode markers and binary view boundaries.
|
|
35
|
+
Text that cannot be represented exactly as UTF-8 reports an explicit error
|
|
36
|
+
before transmission or a close-state change. Browser clients use native
|
|
37
|
+
`WebSocket`; existing HTTP/TLS listeners, routes and application messages
|
|
38
|
+
remain caller-owned.
|
|
39
|
+
- No third-party WebSocket runtime, client sharing, reconnect, application
|
|
40
|
+
envelope, storage, HTTP/2 extended CONNECT or extension negotiation is added.
|
|
41
|
+
Existing SDK behavior and dependencies remain unchanged. Ordinary consumers
|
|
42
|
+
update through their public package workflow; optional server integration
|
|
43
|
+
belongs to the consuming application.
|
|
44
|
+
|
|
3
45
|
## 0.52.1
|
|
4
46
|
|
|
5
47
|
- Preserve an authored host `index.html` at `/` independently of application
|
package/NOTICE
CHANGED
|
@@ -7,7 +7,10 @@ informational notice and is not itself a grant of commercial rights.
|
|
|
7
7
|
|
|
8
8
|
Third-party material retains its own terms:
|
|
9
9
|
|
|
10
|
-
- event-pubsub 6.1.
|
|
10
|
+
- event-pubsub 6.1.1: MIT License; see browser-runtime/dependencies/event-pubsub/licence.
|
|
11
|
+
- event-pubsub's strong-type 2.0.0 dependency: MIT License; see
|
|
12
|
+
browser-runtime/dependencies/event-pubsub/dependencies/strong-type/licence.
|
|
13
|
+
- ws-share 3.1.0: DBAD License; see browser-runtime/dependencies/ws-share/licence.md.
|
|
11
14
|
- js-queue 3.1.1: MIT License; see browser-runtime/dependencies/js-queue/licence.md.
|
|
12
15
|
- js-queue's easy-stack 2.1.0 dependency: MIT License; see its installed
|
|
13
16
|
`licence` file. The browser Queue entry does not import this stack module.
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ selected SDK projection or an explicit live SDK source mount. Both profiles
|
|
|
20
20
|
share the theme, packaging, event, cancellation,
|
|
21
21
|
and browser run contracts while retaining their selected application layout.
|
|
22
22
|
|
|
23
|
-
This checkout defines the `0.
|
|
23
|
+
This checkout defines the `0.54.0` SDK contract. Applications pin one exact npm
|
|
24
24
|
version and lockfile; registry state is deliberately not baked into application
|
|
25
25
|
artifacts.
|
|
26
26
|
|
package/bin/arcane-test.mjs
CHANGED
|
@@ -115,7 +115,8 @@ async function readManagedImportMapContext(source,signal,testFile){
|
|
|
115
115
|
protocol:MANAGED_IMPORT_MAP_PROTOCOL,
|
|
116
116
|
boundary:context.boundary,
|
|
117
117
|
baseURL:pathToFileURL(`${canonicalBase}${path.sep}`).href,
|
|
118
|
-
imports:{...context.imports}
|
|
118
|
+
imports:{...context.imports},
|
|
119
|
+
scopes:context.scopes??{}
|
|
119
120
|
};
|
|
120
121
|
}
|
|
121
122
|
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import Is from '../dependencies/strong-type/index.js';
|
|
2
|
+
import {createArcaneEventSource} from '../event-manager.mjs';
|
|
3
|
+
|
|
4
|
+
const is = new Is(false);
|
|
5
|
+
const STATE_EVENT = 'ai.decisions.state';
|
|
6
|
+
const DEFAULT_RUNTIME_MODULE = 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@4.3.0/dist/transformers.web.js';
|
|
7
|
+
|
|
8
|
+
function requiredText(value, name) {
|
|
9
|
+
if (!is.string(value) || value.length === 0) {
|
|
10
|
+
throw new TypeError(`${name} must be a nonempty string.`);
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function cancellationReason(signal, message) {
|
|
16
|
+
if (signal?.reason !== undefined) return signal.reason;
|
|
17
|
+
const error = new Error(message);
|
|
18
|
+
error.name = 'AbortError';
|
|
19
|
+
error.code = 'ARCANE_AI_REQUEST_ABORTED';
|
|
20
|
+
return error;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function reviveWorkerError(value, seen = new Map()) {
|
|
24
|
+
if (is.error(value) || value === null || !is.object(value)) return value;
|
|
25
|
+
if (!is.string(value.name) || !is.string(value.message)) return value;
|
|
26
|
+
if (seen.has(value)) return seen.get(value);
|
|
27
|
+
const error = new Error(value.message);
|
|
28
|
+
seen.set(value, error);
|
|
29
|
+
for (const key of Object.keys(value)) {
|
|
30
|
+
let item = value[key];
|
|
31
|
+
if (key === 'cause') item = reviveWorkerError(item, seen);
|
|
32
|
+
if (key === 'errors' && is.array(item)) {
|
|
33
|
+
item = item.map(
|
|
34
|
+
function reviveNestedError(nested) {
|
|
35
|
+
return reviveWorkerError(nested, seen);
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
Object.defineProperty(
|
|
40
|
+
error,
|
|
41
|
+
key,
|
|
42
|
+
{value: item, enumerable: true, configurable: true, writable: true}
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return error;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createDecisionOperation(op) {
|
|
49
|
+
const operation = {op};
|
|
50
|
+
operation.promise = new Promise(
|
|
51
|
+
function createDecisionOperationPromise(resolve, reject) {
|
|
52
|
+
operation.resolve = resolve;
|
|
53
|
+
operation.reject = reject;
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
return operation;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* One lazily activated model Worker. Cancelling any active operation terminates
|
|
61
|
+
* that Worker and rejects all of its operations with the same cancellation
|
|
62
|
+
* reason. A later load/evaluate may activate a fresh Worker after cancellation,
|
|
63
|
+
* unloading, or failure; disposal is terminal.
|
|
64
|
+
*/
|
|
65
|
+
class BrowserDecisionModel {
|
|
66
|
+
#configuration;
|
|
67
|
+
#events;
|
|
68
|
+
#activation = null;
|
|
69
|
+
#generation = 0;
|
|
70
|
+
#nextId = 1;
|
|
71
|
+
#state = 'unloaded';
|
|
72
|
+
#progress = null;
|
|
73
|
+
#error = null;
|
|
74
|
+
#disposed = false;
|
|
75
|
+
|
|
76
|
+
constructor({family, model, revision = 'main', device = 'webgpu', runtime = {}} = {}) {
|
|
77
|
+
if (family !== 'laya' && family !== 'julia') {
|
|
78
|
+
throw new TypeError('family must be "laya" or "julia".');
|
|
79
|
+
}
|
|
80
|
+
if (runtime === null || !is.object(runtime)) {
|
|
81
|
+
throw new TypeError('runtime must be an object.');
|
|
82
|
+
}
|
|
83
|
+
const moduleUrl = runtime.moduleUrl === undefined
|
|
84
|
+
? DEFAULT_RUNTIME_MODULE
|
|
85
|
+
: runtime.moduleUrl;
|
|
86
|
+
this.#configuration = {
|
|
87
|
+
family,
|
|
88
|
+
model: requiredText(model, 'model'),
|
|
89
|
+
revision: requiredText(revision, 'revision'),
|
|
90
|
+
device: requiredText(device, 'device'),
|
|
91
|
+
dtype: family === 'laya' ? 'fp16' : 'fp32',
|
|
92
|
+
runtime: {...runtime, moduleUrl: requiredText(moduleUrl, 'runtime.moduleUrl')}
|
|
93
|
+
};
|
|
94
|
+
this.#events = createArcaneEventSource(
|
|
95
|
+
this,
|
|
96
|
+
{source: 'ai-browser-decisions', eventTypes: [STATE_EVENT]}
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
status() {
|
|
101
|
+
const {family, model, revision, device, dtype} = this.#configuration;
|
|
102
|
+
const activeRequests = this.#activation?.evaluations.size ?? 0;
|
|
103
|
+
return {
|
|
104
|
+
family,
|
|
105
|
+
model,
|
|
106
|
+
revision,
|
|
107
|
+
device,
|
|
108
|
+
dtype,
|
|
109
|
+
state: this.#state,
|
|
110
|
+
loaded: this.#state === 'ready',
|
|
111
|
+
busy: this.#state === 'loading' || activeRequests > 0,
|
|
112
|
+
activeRequests,
|
|
113
|
+
progress: this.#progress === null ? null : {...this.#progress},
|
|
114
|
+
error: this.#error
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
subscribe(listener, {emitCurrent = true, signal} = {}) {
|
|
119
|
+
if (!is.function(listener)) throw new TypeError('listener must be a function.');
|
|
120
|
+
if (!is.boolean(emitCurrent)) throw new TypeError('emitCurrent must be a boolean.');
|
|
121
|
+
function forwardDecisionState(event) {
|
|
122
|
+
listener(event.detail);
|
|
123
|
+
}
|
|
124
|
+
const unsubscribe = this.#events.on(
|
|
125
|
+
STATE_EVENT,
|
|
126
|
+
forwardDecisionState,
|
|
127
|
+
{signal}
|
|
128
|
+
);
|
|
129
|
+
try {
|
|
130
|
+
if (emitCurrent && !signal?.aborted) {
|
|
131
|
+
const current = this.status();
|
|
132
|
+
listener(current);
|
|
133
|
+
}
|
|
134
|
+
} catch (error) {
|
|
135
|
+
unsubscribe();
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
return unsubscribe;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
#publish() {
|
|
142
|
+
const current = this.status();
|
|
143
|
+
this.#events.dispatch(STATE_EVENT, current);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
#assertOperational(signal) {
|
|
147
|
+
if (this.#disposed) {
|
|
148
|
+
const error = new Error('The browser decision model is disposed.');
|
|
149
|
+
error.code = 'ARCANE_AI_DISPOSED';
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
if (signal !== undefined && signal !== null
|
|
153
|
+
&& (!is.boolean(signal.aborted)
|
|
154
|
+
|| !is.function(signal.addEventListener)
|
|
155
|
+
|| !is.function(signal.removeEventListener))) {
|
|
156
|
+
throw new TypeError('signal must be an AbortSignal.');
|
|
157
|
+
}
|
|
158
|
+
if (signal?.aborted) {
|
|
159
|
+
throw cancellationReason(signal, 'The browser decision operation was cancelled.');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#isCurrent(activation) {
|
|
164
|
+
return this.#activation === activation && this.#generation === activation.generation;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#observeSignal(activation, signal) {
|
|
168
|
+
const model = this;
|
|
169
|
+
function cancelDecisionActivation() {
|
|
170
|
+
model.#stop(
|
|
171
|
+
activation,
|
|
172
|
+
cancellationReason(signal, 'The browser decision operation was cancelled.'),
|
|
173
|
+
'unloaded'
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
if (signal) {
|
|
177
|
+
signal.addEventListener(
|
|
178
|
+
'abort',
|
|
179
|
+
cancelDecisionActivation,
|
|
180
|
+
{once: true}
|
|
181
|
+
);
|
|
182
|
+
if (signal.aborted) cancelDecisionActivation();
|
|
183
|
+
}
|
|
184
|
+
function stopObservingDecisionSignal() {
|
|
185
|
+
signal?.removeEventListener('abort', cancelDecisionActivation);
|
|
186
|
+
}
|
|
187
|
+
return stopObservingDecisionSignal;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
#activate() {
|
|
191
|
+
const activation = {
|
|
192
|
+
generation: ++this.#generation,
|
|
193
|
+
worker: null,
|
|
194
|
+
listeners: null,
|
|
195
|
+
load: createDecisionOperation('load'),
|
|
196
|
+
pending: new Map(),
|
|
197
|
+
evaluations: new Set(),
|
|
198
|
+
reason: undefined
|
|
199
|
+
};
|
|
200
|
+
this.#activation = activation;
|
|
201
|
+
this.#state = 'loading';
|
|
202
|
+
this.#progress = {phase: 'loading-runtime'};
|
|
203
|
+
this.#error = null;
|
|
204
|
+
this.#publish();
|
|
205
|
+
if (!this.#isCurrent(activation)) return activation;
|
|
206
|
+
const model = this;
|
|
207
|
+
try {
|
|
208
|
+
const worker = new globalThis.Worker(
|
|
209
|
+
new URL('./decision-worker.mjs', import.meta.url),
|
|
210
|
+
{type: 'module', name: 'arcane-decisions'}
|
|
211
|
+
);
|
|
212
|
+
activation.worker = worker;
|
|
213
|
+
function handleDecisionWorkerMessage(event) {
|
|
214
|
+
model.#receive(activation, event.data);
|
|
215
|
+
}
|
|
216
|
+
function handleDecisionWorkerError(event) {
|
|
217
|
+
const error = event.error ?? new Error(
|
|
218
|
+
event.message,
|
|
219
|
+
{cause: event}
|
|
220
|
+
);
|
|
221
|
+
model.#stop(activation, error, 'error');
|
|
222
|
+
}
|
|
223
|
+
function handleDecisionWorkerMessageError(event) {
|
|
224
|
+
const error = new Error(
|
|
225
|
+
'The browser could not deserialize the decision worker response.',
|
|
226
|
+
{cause: event}
|
|
227
|
+
);
|
|
228
|
+
model.#stop(activation, error, 'error');
|
|
229
|
+
}
|
|
230
|
+
activation.listeners = {
|
|
231
|
+
message: handleDecisionWorkerMessage,
|
|
232
|
+
error: handleDecisionWorkerError,
|
|
233
|
+
messageerror: handleDecisionWorkerMessageError
|
|
234
|
+
};
|
|
235
|
+
for (const [type, listener] of Object.entries(activation.listeners)) {
|
|
236
|
+
worker.addEventListener(type, listener);
|
|
237
|
+
}
|
|
238
|
+
const id = this.#nextId++;
|
|
239
|
+
activation.pending.set(id, activation.load);
|
|
240
|
+
worker.postMessage(
|
|
241
|
+
{id, op: 'load', payload: this.#configuration}
|
|
242
|
+
);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
this.#stop(activation, error, 'error');
|
|
245
|
+
}
|
|
246
|
+
return activation;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
#receive(activation, message) {
|
|
250
|
+
if (!this.#isCurrent(activation)) return;
|
|
251
|
+
const operation = message && activation.pending.get(message.id);
|
|
252
|
+
if (!operation) {
|
|
253
|
+
this.#stop(
|
|
254
|
+
activation,
|
|
255
|
+
new Error(
|
|
256
|
+
'The decision worker returned an unknown operation.',
|
|
257
|
+
{cause: message}
|
|
258
|
+
),
|
|
259
|
+
'error'
|
|
260
|
+
);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (Object.hasOwn(message, 'progress')) {
|
|
264
|
+
this.#progress = message.progress;
|
|
265
|
+
this.#publish();
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (Object.hasOwn(message, 'error')) {
|
|
269
|
+
const error = reviveWorkerError(message.error);
|
|
270
|
+
// Transformers.js keeps a Worker-local inference promise chain.
|
|
271
|
+
// A rejected run can poison that chain; a fresh Worker resets it.
|
|
272
|
+
this.#stop(activation, error, 'error');
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (!Object.hasOwn(message, 'result')) {
|
|
276
|
+
this.#stop(
|
|
277
|
+
activation,
|
|
278
|
+
new Error(
|
|
279
|
+
'The decision worker response has no result.',
|
|
280
|
+
{cause: message}
|
|
281
|
+
),
|
|
282
|
+
'error'
|
|
283
|
+
);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
activation.pending.delete(message.id);
|
|
287
|
+
if (operation.op === 'load') {
|
|
288
|
+
this.#state = 'ready';
|
|
289
|
+
this.#progress = {phase: 'ready'};
|
|
290
|
+
this.#error = null;
|
|
291
|
+
this.#publish();
|
|
292
|
+
if (this.#isCurrent(activation)) {
|
|
293
|
+
const current = this.status();
|
|
294
|
+
operation.resolve(current);
|
|
295
|
+
}
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
operation.resolve(message.result);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
#stop(activation, reason, state) {
|
|
302
|
+
if (!this.#isCurrent(activation)) return;
|
|
303
|
+
activation.reason = reason;
|
|
304
|
+
this.#activation = null;
|
|
305
|
+
this.#generation += 1;
|
|
306
|
+
this.#state = state;
|
|
307
|
+
this.#progress = null;
|
|
308
|
+
this.#error = reason;
|
|
309
|
+
if (activation.worker) {
|
|
310
|
+
for (const [type, listener] of Object.entries(activation.listeners ?? {})) {
|
|
311
|
+
activation.worker.removeEventListener(type, listener);
|
|
312
|
+
}
|
|
313
|
+
activation.worker.terminate();
|
|
314
|
+
}
|
|
315
|
+
activation.load.reject(reason);
|
|
316
|
+
for (const operation of activation.pending.values()) operation.reject(reason);
|
|
317
|
+
activation.pending.clear();
|
|
318
|
+
this.#publish();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async load({signal} = {}) {
|
|
322
|
+
this.#assertOperational(signal);
|
|
323
|
+
const activation = this.#activation ?? this.#activate();
|
|
324
|
+
const stopObserving = this.#observeSignal(activation, signal);
|
|
325
|
+
try {
|
|
326
|
+
await activation.load.promise;
|
|
327
|
+
if (!this.#isCurrent(activation)) throw activation.reason;
|
|
328
|
+
return this.status();
|
|
329
|
+
} finally {
|
|
330
|
+
stopObserving();
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async evaluate(rows, {signal} = {}) {
|
|
335
|
+
this.#assertOperational(signal);
|
|
336
|
+
const activation = this.#activation ?? this.#activate();
|
|
337
|
+
const evaluation = {};
|
|
338
|
+
activation.evaluations.add(evaluation);
|
|
339
|
+
const stopObserving = this.#observeSignal(activation, signal);
|
|
340
|
+
if (this.#isCurrent(activation)) {
|
|
341
|
+
this.#progress = {
|
|
342
|
+
phase: this.#state === 'ready' ? 'evaluating' : 'waiting-for-model'
|
|
343
|
+
};
|
|
344
|
+
this.#error = null;
|
|
345
|
+
this.#publish();
|
|
346
|
+
}
|
|
347
|
+
try {
|
|
348
|
+
await activation.load.promise;
|
|
349
|
+
if (!this.#isCurrent(activation)) throw activation.reason;
|
|
350
|
+
const operation = createDecisionOperation('evaluate');
|
|
351
|
+
const id = this.#nextId++;
|
|
352
|
+
activation.pending.set(id, operation);
|
|
353
|
+
try {
|
|
354
|
+
activation.worker.postMessage(
|
|
355
|
+
{id, op: 'evaluate', payload: rows}
|
|
356
|
+
);
|
|
357
|
+
} catch (error) {
|
|
358
|
+
activation.pending.delete(id);
|
|
359
|
+
this.#error = error;
|
|
360
|
+
operation.reject(error);
|
|
361
|
+
this.#publish();
|
|
362
|
+
}
|
|
363
|
+
return await operation.promise;
|
|
364
|
+
} finally {
|
|
365
|
+
stopObserving();
|
|
366
|
+
activation.evaluations.delete(evaluation);
|
|
367
|
+
if (this.#isCurrent(activation)) this.#publish();
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
unload() {
|
|
372
|
+
if (this.#disposed) return this.status();
|
|
373
|
+
const reason = cancellationReason(undefined, 'The browser decision model was unloaded.');
|
|
374
|
+
if (this.#activation) {
|
|
375
|
+
this.#stop(this.#activation, reason, 'unloaded');
|
|
376
|
+
} else {
|
|
377
|
+
this.#state = 'unloaded';
|
|
378
|
+
this.#progress = null;
|
|
379
|
+
this.#error = null;
|
|
380
|
+
this.#publish();
|
|
381
|
+
}
|
|
382
|
+
return this.status();
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
dispose() {
|
|
386
|
+
if (this.#disposed) return this.status();
|
|
387
|
+
this.#disposed = true;
|
|
388
|
+
const reason = cancellationReason(undefined, 'The browser decision model was disposed.');
|
|
389
|
+
if (this.#activation) {
|
|
390
|
+
this.#stop(this.#activation, reason, 'disposed');
|
|
391
|
+
} else {
|
|
392
|
+
this.#state = 'disposed';
|
|
393
|
+
this.#progress = null;
|
|
394
|
+
this.#error = null;
|
|
395
|
+
this.#publish();
|
|
396
|
+
}
|
|
397
|
+
this.#events.dispose();
|
|
398
|
+
return this.status();
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/** Creates a client without starting a Worker, fetching a runtime, or loading a model. */
|
|
403
|
+
export function createBrowserDecisionModel(configuration) {
|
|
404
|
+
return new BrowserDecisionModel(configuration);
|
|
405
|
+
}
|