@aethervtt/shard-platform 0.1.0 → 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/dist/audio-runtime.d.ts +142 -0
- package/dist/audio-runtime.js +16 -0
- package/dist/index.d.ts +56 -1
- package/dist/index.js +3 -1
- package/dist/workers.d.ts +18 -0
- package/dist/workers.js +41 -3
- package/package.json +3 -3
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { AudioClipSource, AudioVoiceDesc, AudioVoiceParams } from './index.js';
|
|
2
|
+
/** Optional, host-neutral extensions. Legacy AudioBackend implementations remain valid. */
|
|
3
|
+
export interface AudioCapabilities {
|
|
4
|
+
scheduled: boolean;
|
|
5
|
+
automation: boolean;
|
|
6
|
+
mixer: boolean;
|
|
7
|
+
media: boolean;
|
|
8
|
+
meters: boolean;
|
|
9
|
+
offline: boolean;
|
|
10
|
+
sessions: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface AudioClock {
|
|
13
|
+
/** Seconds on the rendering clock, frozen while the context is suspended. */
|
|
14
|
+
now(): number;
|
|
15
|
+
/** Monotonic expiry clock, including suspension. Falls back to frame time in legacy hosts. */
|
|
16
|
+
wallNow?(): number;
|
|
17
|
+
/** Independent of rendering frames. Returns an idempotent unsubscribe. */
|
|
18
|
+
wake(callback: () => void, intervalSeconds: number): () => void;
|
|
19
|
+
}
|
|
20
|
+
export interface AudioPreparation {
|
|
21
|
+
readonly clip: AudioClipSource;
|
|
22
|
+
readonly decodedBytes: number;
|
|
23
|
+
readonly encodedBytes: number;
|
|
24
|
+
readonly sampleRate: number;
|
|
25
|
+
readonly samples: number;
|
|
26
|
+
readonly channels: number;
|
|
27
|
+
readonly released: boolean;
|
|
28
|
+
release(): void;
|
|
29
|
+
}
|
|
30
|
+
export interface AudioMemory {
|
|
31
|
+
/** Additional bounded finite-effect kernels, separate from decoded asset PCM. */
|
|
32
|
+
effectBytes: number;
|
|
33
|
+
encodedBytes: number;
|
|
34
|
+
decodedBytes: number;
|
|
35
|
+
budgetBytes: number;
|
|
36
|
+
loading: number;
|
|
37
|
+
pinned: number;
|
|
38
|
+
evictable: number;
|
|
39
|
+
voices: number;
|
|
40
|
+
nodes: number;
|
|
41
|
+
urls: number;
|
|
42
|
+
}
|
|
43
|
+
export type AudioPresentationState = 'preparing' | 'blocked' | 'scheduled' | 'rendering' | 'stopping' | 'ended' | 'failed';
|
|
44
|
+
export interface AudioPresentation {
|
|
45
|
+
state: AudioPresentationState;
|
|
46
|
+
scheduledAt: number;
|
|
47
|
+
endedAt: number;
|
|
48
|
+
error: string | null;
|
|
49
|
+
}
|
|
50
|
+
export interface AudioScheduledVoice extends AudioVoiceDesc {
|
|
51
|
+
/** Absolute rendering-clock time. Omitted means immediate, preserving the old API. */
|
|
52
|
+
when?: number;
|
|
53
|
+
/** Duration in output seconds, not clip seconds. */
|
|
54
|
+
duration?: number;
|
|
55
|
+
lowpass?: number;
|
|
56
|
+
highpass?: number;
|
|
57
|
+
}
|
|
58
|
+
export type AudioAutomationProperty = 'gain' | 'envelope' | 'pitch' | 'lowpass' | 'highpass';
|
|
59
|
+
export interface AudioAutomation {
|
|
60
|
+
property: AudioAutomationProperty;
|
|
61
|
+
at: number;
|
|
62
|
+
value: number;
|
|
63
|
+
duration: number;
|
|
64
|
+
curve?: 'linear' | 'exponential' | 'equal-power-in' | 'equal-power-out';
|
|
65
|
+
}
|
|
66
|
+
export interface AudioSessionOptions {
|
|
67
|
+
maxVoices?: number;
|
|
68
|
+
decodedBudgetBytes?: number;
|
|
69
|
+
maxDecodes?: number;
|
|
70
|
+
priority?: number;
|
|
71
|
+
}
|
|
72
|
+
export interface AudioEffectDesc {
|
|
73
|
+
id: string;
|
|
74
|
+
kind: 'lowpass' | 'highpass' | 'peaking' | 'compressor' | 'delay' | 'reverb';
|
|
75
|
+
frequency: number;
|
|
76
|
+
q: number;
|
|
77
|
+
gainDb: number;
|
|
78
|
+
threshold: number;
|
|
79
|
+
ratio: number;
|
|
80
|
+
attack: number;
|
|
81
|
+
release: number;
|
|
82
|
+
seconds: number;
|
|
83
|
+
feedback: number;
|
|
84
|
+
tailSeconds: number;
|
|
85
|
+
ir?: AudioClipSource;
|
|
86
|
+
}
|
|
87
|
+
export interface AudioSendDesc {
|
|
88
|
+
target: string;
|
|
89
|
+
gain: number;
|
|
90
|
+
pre: boolean;
|
|
91
|
+
}
|
|
92
|
+
export interface AudioBusDesc {
|
|
93
|
+
id: string;
|
|
94
|
+
parent: string;
|
|
95
|
+
gain: number;
|
|
96
|
+
userGain: number;
|
|
97
|
+
effects: readonly AudioEffectDesc[];
|
|
98
|
+
sends: readonly AudioSendDesc[];
|
|
99
|
+
return: boolean;
|
|
100
|
+
}
|
|
101
|
+
export interface AudioMixerGraph {
|
|
102
|
+
buses: readonly AudioBusDesc[];
|
|
103
|
+
maxNodes: number;
|
|
104
|
+
}
|
|
105
|
+
export interface AudioMeter {
|
|
106
|
+
available: boolean;
|
|
107
|
+
peak: number;
|
|
108
|
+
rms: number;
|
|
109
|
+
samples: number;
|
|
110
|
+
sampleRate: number;
|
|
111
|
+
at: number;
|
|
112
|
+
}
|
|
113
|
+
export interface AudioRuntimeExtension {
|
|
114
|
+
readonly capabilities: Readonly<AudioCapabilities>;
|
|
115
|
+
readonly clock: AudioClock;
|
|
116
|
+
readonly activeVoices: number;
|
|
117
|
+
setDecodedBudget(bytes: number): void;
|
|
118
|
+
/** Reserve a synchronous batch before starting any stem; release unused slots in finally. */
|
|
119
|
+
reserveVoices(count: number): () => void;
|
|
120
|
+
prepare(clip: AudioClipSource): Promise<AudioPreparation>;
|
|
121
|
+
/** Reclaims unpinned cache entries. Active voices retain their own pins. */
|
|
122
|
+
collect(): void;
|
|
123
|
+
memory(): AudioMemory;
|
|
124
|
+
play(desc: AudioScheduledVoice): number;
|
|
125
|
+
/** Waits until all requested source starts have been prepared and submitted, or rejects on failure. */
|
|
126
|
+
flush(): Promise<void>;
|
|
127
|
+
presentation(id: number): Readonly<AudioPresentation> | undefined;
|
|
128
|
+
/** In-place route/loop/spatial edits; false asks the caller to recreate at the current phase. */
|
|
129
|
+
reconfigure(id: number, desc: AudioVoiceDesc): boolean;
|
|
130
|
+
automate(id: number, command: AudioAutomation): void;
|
|
131
|
+
setVoiceSend(id: number, target: string, gain: number, rampSeconds: number): void;
|
|
132
|
+
/** Cancels a scheduled start; stops an already audible voice, with a truthful result. */
|
|
133
|
+
cancel(id: number): 'cancelled' | 'already-started' | 'ended';
|
|
134
|
+
configureMixer(graph: AudioMixerGraph): Promise<void>;
|
|
135
|
+
setMixerValue(bus: string, field: string, value: number, rampSeconds: number): void;
|
|
136
|
+
meter(bus: string): AudioMeter;
|
|
137
|
+
/** Headless clocks are explicitly advanced; live clocks ignore this. */
|
|
138
|
+
advance?(seconds: number): void;
|
|
139
|
+
update(id: number, params: AudioVoiceParams): void;
|
|
140
|
+
}
|
|
141
|
+
/** Shared host validation, also used by the headless scheduling model. */
|
|
142
|
+
export declare function validateAudioAutomation(command: AudioAutomation): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ShardError } from "@aethervtt/shard-core";
|
|
2
|
+
function validateAudioAutomation(command) {
|
|
3
|
+
if (!Number.isFinite(command.at) || command.at < 0 || !Number.isFinite(command.duration) || command.duration < 0 || !Number.isFinite(command.value) || command.value < 0 || (command.property === "pitch" || command.property === "lowpass") && command.value === 0)
|
|
4
|
+
throw new ShardError(
|
|
5
|
+
"audio/invalid-range",
|
|
6
|
+
"Automation requires finite nonnegative times and values, and positive pitch/cutoff",
|
|
7
|
+
{ path: "/automation" }
|
|
8
|
+
);
|
|
9
|
+
if (command.property !== "gain" && command.property !== "envelope" && command.property !== "pitch" && command.property !== "lowpass" && command.property !== "highpass")
|
|
10
|
+
throw new ShardError("audio/invalid-range", "Unknown automation property", {
|
|
11
|
+
path: "/automation/property"
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
validateAudioAutomation
|
|
16
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import type { ShardError } from '@aethervtt/shard-core';
|
|
1
|
+
import type { HeapStats, HotFunction, ShardError } from '@aethervtt/shard-core';
|
|
2
|
+
import type { AudioRuntimeExtension, AudioSessionOptions } from './audio-runtime.js';
|
|
2
3
|
import type { Workers } from './workers.js';
|
|
4
|
+
export type * from './audio-runtime.js';
|
|
5
|
+
export { validateAudioAutomation } from './audio-runtime.js';
|
|
3
6
|
/**
|
|
4
7
|
* Everything the engine needs from its host. The engine only talks to this interface;
|
|
5
8
|
* hosts (browser, Tauri, headless) provide an implementation at startup.
|
|
@@ -34,6 +37,37 @@ export interface HostPerformance {
|
|
|
34
37
|
onLongTask?(listener: (startMs: number, durationMs: number) => void): () => void;
|
|
35
38
|
/** Bytes fetched so far (scripts, WASM, assets), over the wire and decoded. Absent without resource timing. */
|
|
36
39
|
downloads?(): HostDownloads;
|
|
40
|
+
/**
|
|
41
|
+
* The JS heap (0074): `v8.getHeapStatistics()` in Node; in a page,
|
|
42
|
+
* `performance.measureUserAgentSpecificMemory()` when isolated (at most every 5 s, so the last
|
|
43
|
+
* result), else `performance.memory` where it exists. Absent or undefined elsewhere.
|
|
44
|
+
*/
|
|
45
|
+
heap?(): HeapStats | undefined;
|
|
46
|
+
/** Garbage collections (Node): start on `performance.now()`'s timeline, pause, and kind. */
|
|
47
|
+
onGc?(listener: (start: number, ms: number, kind: string) => void): () => void;
|
|
48
|
+
/**
|
|
49
|
+
* Starts sampling JavaScript (0074): `node:inspector`'s profiler in Node, the JS Self-Profiling
|
|
50
|
+
* API in pages served with `Document-Policy: js-profiling`. Undefined where it can't.
|
|
51
|
+
*/
|
|
52
|
+
startSampler?(): Promise<HostSampler | undefined>;
|
|
53
|
+
/** Shows a span in the browser's own profiler (`performance.measure` with DevTools track data). */
|
|
54
|
+
measure?(name: string, track: string, start: number, ms: number): void;
|
|
55
|
+
}
|
|
56
|
+
/** A sampling profiler running for a capture's window. */
|
|
57
|
+
export interface HostSampler {
|
|
58
|
+
stop(): Promise<HostSamples>;
|
|
59
|
+
}
|
|
60
|
+
export interface HostSamples {
|
|
61
|
+
/** The functions with the most self time. */
|
|
62
|
+
hottest: HotFunction[];
|
|
63
|
+
/** Stretches of the same top function on `performance.now()`'s timeline (browsers). */
|
|
64
|
+
samples?: {
|
|
65
|
+
name: string;
|
|
66
|
+
start: number;
|
|
67
|
+
ms: number;
|
|
68
|
+
}[];
|
|
69
|
+
/** The V8 `.cpuprofile` (Node). */
|
|
70
|
+
cpuprofile?: unknown;
|
|
37
71
|
}
|
|
38
72
|
export interface HostDownloads {
|
|
39
73
|
/** Bytes over the wire, headers included. Cached responses add nothing. */
|
|
@@ -163,10 +197,18 @@ export interface FileChangeEvent {
|
|
|
163
197
|
readonly path: string;
|
|
164
198
|
}
|
|
165
199
|
export interface PlatformFileSystem {
|
|
200
|
+
/** A media/fetch URL for an existing host file; absent when the host cannot expose one. */
|
|
201
|
+
resolveUrl?(path: string): string | undefined;
|
|
166
202
|
/** False on hosts that can only read (for example a static web build). */
|
|
167
203
|
readonly writable: boolean;
|
|
168
204
|
readText(path: string): Promise<string>;
|
|
169
205
|
readBytes(path: string): Promise<Uint8Array>;
|
|
206
|
+
/**
|
|
207
|
+
* `length` bytes from `offset` (fewer at the end of the file): one ranged read, so a terrain
|
|
208
|
+
* page comes out of a pack of hundreds without reading the rest (0071). Absent on hosts that
|
|
209
|
+
* can't; callers read the whole file then.
|
|
210
|
+
*/
|
|
211
|
+
readRange?(path: string, offset: number, length: number): Promise<Uint8Array>;
|
|
170
212
|
writeText(path: string, data: string): Promise<void>;
|
|
171
213
|
writeBytes(path: string, data: Uint8Array): Promise<void>;
|
|
172
214
|
exists(path: string): Promise<boolean>;
|
|
@@ -221,6 +263,10 @@ export interface AudioClipSource {
|
|
|
221
263
|
readonly id: string;
|
|
222
264
|
/** The file as imported (WAV, Ogg, MP3, FLAC). A new array means new contents. */
|
|
223
265
|
readonly bytes: Uint8Array;
|
|
266
|
+
/** Resolved immutable imported-artifact URL for lazy media playback. Bytes may be empty. */
|
|
267
|
+
readonly url?: string;
|
|
268
|
+
readonly channels?: number;
|
|
269
|
+
readonly sampleRate?: number;
|
|
224
270
|
/** `wav`, `vorbis`, `opus`, `mp3`, or `flac`. */
|
|
225
271
|
readonly codec: string;
|
|
226
272
|
/** Seconds. */
|
|
@@ -255,6 +301,11 @@ export interface AudioVoiceParams {
|
|
|
255
301
|
z: number;
|
|
256
302
|
}
|
|
257
303
|
export interface AudioVoiceDesc extends AudioVoiceParams {
|
|
304
|
+
/** Additive post-spatial sends; routes must be declared by the source bus mixer sends. */
|
|
305
|
+
readonly sends?: readonly {
|
|
306
|
+
target: string;
|
|
307
|
+
gain: number;
|
|
308
|
+
}[];
|
|
258
309
|
readonly clip: AudioClipSource;
|
|
259
310
|
/** Bus name; the backend mixes it at the gain last given to `setBus`. */
|
|
260
311
|
readonly bus: string;
|
|
@@ -270,6 +321,10 @@ export type AudioContextState = 'running' | 'suspended' | 'closed' | 'headless';
|
|
|
270
321
|
* voices; a backend only makes the sound (or, headless, records it).
|
|
271
322
|
*/
|
|
272
323
|
export interface AudioBackend {
|
|
324
|
+
/** Advanced features are opt-in; unsupported operations must not silently degrade. */
|
|
325
|
+
readonly runtime?: AudioRuntimeExtension;
|
|
326
|
+
/** An independent session under a shared host context/cache. */
|
|
327
|
+
createSession?(options?: AudioSessionOptions): AudioBackend;
|
|
273
328
|
/** `web`, `headless`, ... */
|
|
274
329
|
readonly kind: string;
|
|
275
330
|
/** Browsers keep the context suspended until a user gesture; voices wait until then. */
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validateAudioAutomation } from "./audio-runtime.js";
|
|
1
2
|
export * from "./workers.js";
|
|
2
3
|
const MODIFIERS = { shift: 1, ctrl: 2, alt: 4, meta: 8 };
|
|
3
4
|
function createMemoryStorage() {
|
|
@@ -14,5 +15,6 @@ function createMemoryStorage() {
|
|
|
14
15
|
}
|
|
15
16
|
export {
|
|
16
17
|
MODIFIERS,
|
|
17
|
-
createMemoryStorage
|
|
18
|
+
createMemoryStorage,
|
|
19
|
+
validateAudioAutomation
|
|
18
20
|
};
|
package/dist/workers.d.ts
CHANGED
|
@@ -4,6 +4,19 @@ export interface WorkerRunOptions {
|
|
|
4
4
|
readonly transfer?: readonly ArrayBuffer[];
|
|
5
5
|
/** `high` jobs start before every queued `normal` one. Default `normal`. */
|
|
6
6
|
readonly priority?: 'high' | 'normal';
|
|
7
|
+
/** What the profiler calls the job: its span is `worker/<kind>` (0074). Default: `fn`. */
|
|
8
|
+
readonly kind?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* One finished job as the worker timed it (0074), on the calling thread's `performance.now()`
|
|
12
|
+
* timeline: the two contexts' `timeOrigin`s are subtracted, so it lines up with main-thread spans.
|
|
13
|
+
*/
|
|
14
|
+
export interface WorkerJobTiming {
|
|
15
|
+
kind: string;
|
|
16
|
+
/** The pool's thread that ran it, from 0. */
|
|
17
|
+
worker: number;
|
|
18
|
+
start: number;
|
|
19
|
+
ms: number;
|
|
7
20
|
}
|
|
8
21
|
/**
|
|
9
22
|
* A pool of background threads that run functions exported by ES modules. Results never depend on
|
|
@@ -23,6 +36,11 @@ export interface Workers {
|
|
|
23
36
|
run<T>(module: string, fn: string, args: readonly unknown[], options?: WorkerRunOptions): Promise<T>;
|
|
24
37
|
/** Stops every worker. Jobs still running or queued reject. */
|
|
25
38
|
dispose(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Calls `listener` with every finished job's timing, measured inside the worker and carried back
|
|
41
|
+
* with its result, so it costs no extra messages. Returns an unsubscribe function.
|
|
42
|
+
*/
|
|
43
|
+
observe?(listener: (timing: WorkerJobTiming) => void): () => void;
|
|
26
44
|
}
|
|
27
45
|
/** What a host gives the pool for one worker thread. */
|
|
28
46
|
export interface WorkerThread {
|
package/dist/workers.js
CHANGED
|
@@ -1,17 +1,36 @@
|
|
|
1
1
|
import { ShardError } from "@aethervtt/shard-core";
|
|
2
|
+
const clock = globalThis;
|
|
3
|
+
function timeOrigin() {
|
|
4
|
+
return clock.performance?.timeOrigin ?? 0;
|
|
5
|
+
}
|
|
6
|
+
function now() {
|
|
7
|
+
return clock.performance ? clock.performance.now() : Date.now();
|
|
8
|
+
}
|
|
2
9
|
function createWorkerPool(options) {
|
|
3
10
|
const slots = [];
|
|
4
11
|
const high = [];
|
|
5
12
|
const normal = [];
|
|
13
|
+
const observers = /* @__PURE__ */ new Set();
|
|
6
14
|
let nextId = 1;
|
|
7
15
|
let disposed = false;
|
|
8
16
|
const start = (slot) => {
|
|
17
|
+
const index = slots.indexOf(slot);
|
|
9
18
|
const events = {
|
|
10
19
|
message: (message) => {
|
|
11
20
|
const reply = message;
|
|
12
21
|
const job = slot.job;
|
|
13
22
|
if (!job || job.id !== reply.id) return;
|
|
14
23
|
slot.job = void 0;
|
|
24
|
+
const time = reply.time;
|
|
25
|
+
if (time && observers.size > 0) {
|
|
26
|
+
const timing = {
|
|
27
|
+
kind: job.kind,
|
|
28
|
+
worker: index,
|
|
29
|
+
start: time[0] - timeOrigin(),
|
|
30
|
+
ms: time[1]
|
|
31
|
+
};
|
|
32
|
+
for (const listener of observers) listener(timing);
|
|
33
|
+
}
|
|
15
34
|
if (reply.ok) job.resolve(reply.result);
|
|
16
35
|
else job.reject(toError(reply.error));
|
|
17
36
|
pump();
|
|
@@ -70,6 +89,7 @@ function createWorkerPool(options) {
|
|
|
70
89
|
id: nextId++,
|
|
71
90
|
module,
|
|
72
91
|
fn,
|
|
92
|
+
kind: opts.kind ?? fn,
|
|
73
93
|
args,
|
|
74
94
|
transfer: opts.transfer ?? [],
|
|
75
95
|
resolve,
|
|
@@ -78,8 +98,13 @@ function createWorkerPool(options) {
|
|
|
78
98
|
(opts.priority === "high" ? high : normal).push(job);
|
|
79
99
|
pump();
|
|
80
100
|
}),
|
|
101
|
+
observe: (listener) => {
|
|
102
|
+
observers.add(listener);
|
|
103
|
+
return () => observers.delete(listener);
|
|
104
|
+
},
|
|
81
105
|
dispose: () => {
|
|
82
106
|
disposed = true;
|
|
107
|
+
observers.clear();
|
|
83
108
|
const stopped = new ShardError("platform/workers-disposed", "The worker pool was disposed");
|
|
84
109
|
for (const job of [...high.splice(0), ...normal.splice(0)]) job.reject(stopped);
|
|
85
110
|
for (const slot of slots) {
|
|
@@ -99,10 +124,15 @@ function toError(e) {
|
|
|
99
124
|
}
|
|
100
125
|
function createInlineWorkers() {
|
|
101
126
|
const modules = /* @__PURE__ */ new Map();
|
|
127
|
+
const observers = /* @__PURE__ */ new Set();
|
|
102
128
|
let disposed = false;
|
|
103
129
|
return {
|
|
104
130
|
size: 0,
|
|
105
|
-
|
|
131
|
+
observe: (listener) => {
|
|
132
|
+
observers.add(listener);
|
|
133
|
+
return () => observers.delete(listener);
|
|
134
|
+
},
|
|
135
|
+
run: async (module, fn, args, options = {}) => {
|
|
106
136
|
if (disposed)
|
|
107
137
|
throw new ShardError("platform/workers-disposed", "The worker pool was disposed");
|
|
108
138
|
let mod = modules.get(module);
|
|
@@ -119,7 +149,13 @@ function createInlineWorkers() {
|
|
|
119
149
|
hint: "Worker functions are named exports of the module passed to run()."
|
|
120
150
|
});
|
|
121
151
|
}
|
|
122
|
-
|
|
152
|
+
const start = now();
|
|
153
|
+
const result = await f(...args);
|
|
154
|
+
if (observers.size > 0) {
|
|
155
|
+
const timing = { kind: options.kind ?? fn, worker: 0, start, ms: now() - start };
|
|
156
|
+
for (const listener of observers) listener(timing);
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
123
159
|
},
|
|
124
160
|
dispose: () => {
|
|
125
161
|
disposed = true;
|
|
@@ -160,8 +196,10 @@ const handle = async (msg, post) => {
|
|
|
160
196
|
if (typeof f !== 'function') {
|
|
161
197
|
throw Object.assign(new Error('"' + module + '" has no export "' + fn + '"'), { code: 'platform/worker-no-export' })
|
|
162
198
|
}
|
|
199
|
+
const t0 = performance.now()
|
|
163
200
|
const result = await f(...args)
|
|
164
|
-
|
|
201
|
+
const time = [performance.timeOrigin + t0, performance.now() - t0]
|
|
202
|
+
post({ id, ok: true, result, time }, transfers(result))
|
|
165
203
|
} catch (e) {
|
|
166
204
|
post({ id, ok: false, error: { code: e && e.code, message: String((e && e.message) || e), hint: e && e.hint, path: e && e.path } }, [])
|
|
167
205
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aethervtt/shard-platform",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"README.md"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@aethervtt/shard-core": "0.1
|
|
19
|
+
"@aethervtt/shard-core": "0.2.1"
|
|
20
20
|
},
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
@@ -28,6 +28,6 @@
|
|
|
28
28
|
"provenance": true
|
|
29
29
|
},
|
|
30
30
|
"shard": {
|
|
31
|
-
"buildSha": "
|
|
31
|
+
"buildSha": "772eb0d2b8a5fc499105708f4ae92c58788cc9ea"
|
|
32
32
|
}
|
|
33
33
|
}
|