@thi.ng/wasm-api 0.17.3 → 0.18.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2022-10-31T09:19:33Z
3
+ - **Last updated**: 2022-10-31T23:01:45Z
4
4
  - **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
5
5
 
6
6
  All notable changes to this project will be documented in this file.
@@ -9,6 +9,17 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
9
9
  **Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
10
10
  and/or version bumps of transitive dependencies.
11
11
 
12
+ ## [0.18.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/wasm-api@0.18.0) (2022-10-31)
13
+
14
+ #### 🚀 Features
15
+
16
+ - update WasmBridge & child API module specs ([6773494](https://github.com/thi-ng/umbrella/commit/6773494))
17
+ - update IWasmAPI interface to declare import ID & module dependencies
18
+ - update WasmBridge ctor (array instead of object of sub-modules)
19
+ - update WasmBridge.init() to initialize modules in dependency order
20
+ - replace illegalArgs() with assert()
21
+ - add [@thi.ng/arrays](https://github.com/thi-ng/umbrella/tree/main/packages/arrays) as dependency, update pkg
22
+
12
23
  ### [0.17.3](https://github.com/thi-ng/umbrella/tree/@thi.ng/wasm-api@0.17.3) (2022-10-31)
13
24
 
14
25
  #### ♻️ Refactoring
package/README.md CHANGED
@@ -505,6 +505,13 @@ following example provides a brief overview:
505
505
  import { IWasmAPI, WasmBridge } from "@thi.ng/wasm-api";
506
506
 
507
507
  export class CustomAPI implements IWasmAPI {
508
+ // Unique API module identifier to group WASM imports,
509
+ // must match ID used by native code (see further below).
510
+ readonly id = "custom";
511
+ // optionally list IDs of other API modules this module depends on
512
+ // these are used to infer the correct initialization order
513
+ readonly dependencies = [];
514
+
508
515
  parent!: WasmBridge;
509
516
 
510
517
  async init(parent: WasmBridge) {
@@ -538,7 +545,7 @@ export class CustomAPI implements IWasmAPI {
538
545
  Now we can supply this custom API when creating the main WASM bridge:
539
546
 
540
547
  ```ts
541
- export const bridge = new WasmBridge({ custom: new CustomAPI() });
548
+ export const bridge = new WasmBridge([new CustomAPI()]);
542
549
  ```
543
550
 
544
551
  In Zig (or any other language of your choice) we can then utilize this custom
@@ -681,7 +688,7 @@ node --experimental-repl-await
681
688
  > const wasmApi = await import("@thi.ng/wasm-api");
682
689
  ```
683
690
 
684
- Package sizes (gzipped, pre-treeshake): ESM: 7.06 KB
691
+ Package sizes (gzipped, pre-treeshake): ESM: 7.13 KB
685
692
 
686
693
  **IMPORTANT:** The package includes multiple language code generators which are
687
694
  **not** required for normal use of the API bridge. Hence, the actual package
@@ -691,6 +698,7 @@ size in production will be MUCH smaller than what's stated here!
691
698
 
692
699
  - [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/develop/packages/api)
693
700
  - [@thi.ng/args](https://github.com/thi-ng/umbrella/tree/develop/packages/args)
701
+ - [@thi.ng/arrays](https://github.com/thi-ng/umbrella/tree/develop/packages/arrays)
694
702
  - [@thi.ng/binary](https://github.com/thi-ng/umbrella/tree/develop/packages/binary)
695
703
  - [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/develop/packages/checks)
696
704
  - [@thi.ng/compare](https://github.com/thi-ng/umbrella/tree/develop/packages/compare)
package/api.d.ts CHANGED
@@ -13,6 +13,18 @@ export declare type BigIntArray = bigint[] | BigInt64Array | BigUint64Array;
13
13
  * certain exports declared by WASM module.
14
14
  */
15
15
  export interface IWasmAPI<T extends WasmExports = WasmExports> {
16
+ /**
17
+ * The unique ID for grouping the WASM imports of this module. MUST be the
18
+ * same as used by the native side of the module.
19
+ */
20
+ readonly id: string;
21
+ /**
22
+ * IDs of other WASM API modules which this module depends on. Used to infer
23
+ * correct initialization order. The core module (w/ unique ID: `wasmapi`)
24
+ * is always considered an implicit dependency, will be initialized first
25
+ * and MUST NOT be stated here.
26
+ */
27
+ readonly dependencies?: string[];
16
28
  /**
17
29
  * Called by {@link WasmBridge.init} to initialize all child APIs (async)
18
30
  * after the WASM module has been instantiated. If the method returns false
package/bridge.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import type { Event, INotify, Listener, NumericArray } from "@thi.ng/api";
2
+ import type { Event, INotify, IObjectOf, Listener, NumericArray } from "@thi.ng/api";
3
3
  import type { ILogger } from "@thi.ng/logger";
4
4
  import { BigIntArray, CoreAPI, IWasmAPI, IWasmMemoryAccess, MemorySlice, WasmExports } from "./api.js";
5
5
  export declare const Panic: {
@@ -36,11 +36,11 @@ export declare const OutOfMemoryError: {
36
36
  * mechanisms like JS `DataView`...
37
37
  *
38
38
  * 64bit integers are handled via JS `BigInt` and hence require the host env to
39
- * support it. No polyfill is provided.
39
+ * support it. No polyfills are provided.
40
40
  */
41
41
  export declare class WasmBridge<T extends WasmExports = WasmExports> implements IWasmMemoryAccess, INotify {
42
- modules: Record<string, IWasmAPI<T>>;
43
42
  logger: ILogger;
43
+ readonly id = "wasmapi";
44
44
  i8: Int8Array;
45
45
  u8: Uint8Array;
46
46
  i16: Int16Array;
@@ -56,7 +56,8 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> implements
56
56
  imports: WebAssembly.Imports;
57
57
  exports: T;
58
58
  api: CoreAPI;
59
- constructor(modules?: Record<string, IWasmAPI<T>>, logger?: ILogger);
59
+ modules: IObjectOf<IWasmAPI<T>>;
60
+ constructor(modules?: IWasmAPI<T>[], logger?: ILogger);
60
61
  /**
61
62
  * Instantiates WASM module from given `src` (and optional provided extra
62
63
  * imports), then automatically calls {@link WasmBridge.init} with the
@@ -72,9 +73,10 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> implements
72
73
  */
73
74
  instantiate(src: Response | BufferSource | PromiseLike<Response | BufferSource>, imports?: WebAssembly.Imports): Promise<boolean>;
74
75
  /**
75
- * Receives the WASM module's exports, stores the for future reference and
76
- * then initializes all declared bridge child API modules. Returns false if
77
- * any of the module initializations failed.
76
+ * Receives the WASM module's combined exports, stores them for future
77
+ * reference and then initializes all declared bridge child API modules in
78
+ * their stated dependency order. Returns false if any of the module
79
+ * initializations failed.
78
80
  *
79
81
  * @remarks
80
82
  * Emits the {@link EVENT_MEMORY_CHANGED} event just before returning (and
@@ -84,11 +86,11 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> implements
84
86
  */
85
87
  init(exports: T): Promise<boolean>;
86
88
  /**
87
- * Called automatically during initialization. Initializes and/or updates
88
- * the various typed WASM memory views (e.g. after growing the WASM memory
89
- * and the previous buffer becoming detached). Unless `notify` is false,
90
- * the {@link EVENT_MEMORY_CHANGED} event will be emitted if the memory
91
- * views had to be updated.
89
+ * Called automatically during initialization and from other memory
90
+ * accessors. Initializes and/or updates the various typed WASM memory views
91
+ * (e.g. after growing the WASM memory and the previous buffer becoming
92
+ * detached). Unless `notify` is false, the {@link EVENT_MEMORY_CHANGED}
93
+ * event will be emitted if the memory views had to be updated.
92
94
  *
93
95
  * @param notify
94
96
  */
@@ -99,16 +101,16 @@ export declare class WasmBridge<T extends WasmExports = WasmExports> implements
99
101
  * API and any provided bridge API modules.
100
102
  *
101
103
  * @remarks
102
- * Since v0.4.0 each API module's imports will be in their own WASM import
103
- * object, named using the same key which was assigned to the module when
104
- * creating the WASM bridge. The bridge's core API will be named `core` and
105
- * is reserved.
104
+ * Each API module's imports will be in their own WASM import object/table,
105
+ * named using the same key which is defined by the JS side of the module
106
+ * via {@link IWasmAPI.id}. The bridge's core API is named `wasmapi` and is
107
+ * reserved.
106
108
  *
107
109
  * @example
108
110
  * The following creates a bridge with a fictional `custom` API module:
109
111
  *
110
112
  * ```ts
111
- * const bridge = new WasmBridge({ custom: new CustomAPI() });
113
+ * const bridge = new WasmBridge([new CustomAPI()]);
112
114
  *
113
115
  * // get combined imports object
114
116
  * bridge.getImports();
package/bridge.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { __decorate } from "tslib";
2
2
  import { INotifyMixin } from "@thi.ng/api/mixins/inotify";
3
+ import { topoSort } from "@thi.ng/arrays/topo-sort";
4
+ import { assert } from "@thi.ng/errors/assert";
3
5
  import { defError } from "@thi.ng/errors/deferror";
4
- import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
5
6
  import { U16, U32, U64BIG, U8 } from "@thi.ng/hex";
6
7
  import { ConsoleLogger } from "@thi.ng/logger/console";
7
8
  import { EVENT_MEMORY_CHANGED, } from "./api.js";
@@ -21,12 +22,12 @@ export const OutOfMemoryError = defError(() => "Out of memory");
21
22
  * mechanisms like JS `DataView`...
22
23
  *
23
24
  * 64bit integers are handled via JS `BigInt` and hence require the host env to
24
- * support it. No polyfill is provided.
25
+ * support it. No polyfills are provided.
25
26
  */
26
27
  let WasmBridge = class WasmBridge {
27
- constructor(modules = {}, logger = new ConsoleLogger("wasm")) {
28
- this.modules = modules;
28
+ constructor(modules = [], logger = new ConsoleLogger("wasm")) {
29
29
  this.logger = logger;
30
+ this.id = "wasmapi";
30
31
  this.utf8Decoder = new TextDecoder();
31
32
  this.utf8Encoder = new TextEncoder();
32
33
  const logN = (x) => this.logger.debug(x);
@@ -67,6 +68,11 @@ let WasmBridge = class WasmBridge {
67
68
  timer: () => performance.now(),
68
69
  epoch: () => BigInt(Date.now()),
69
70
  };
71
+ this.modules = modules.reduce((acc, x) => {
72
+ assert(acc[x.id] === undefined && x.id !== this.id, `duplicate API module ID: ${x.id}`);
73
+ acc[x.id] = x;
74
+ return acc;
75
+ }, {});
70
76
  }
71
77
  /**
72
78
  * Instantiates WASM module from given `src` (and optional provided extra
@@ -90,9 +96,10 @@ let WasmBridge = class WasmBridge {
90
96
  return this.init(wasm.instance.exports);
91
97
  }
92
98
  /**
93
- * Receives the WASM module's exports, stores the for future reference and
94
- * then initializes all declared bridge child API modules. Returns false if
95
- * any of the module initializations failed.
99
+ * Receives the WASM module's combined exports, stores them for future
100
+ * reference and then initializes all declared bridge child API modules in
101
+ * their stated dependency order. Returns false if any of the module
102
+ * initializations failed.
96
103
  *
97
104
  * @remarks
98
105
  * Emits the {@link EVENT_MEMORY_CHANGED} event just before returning (and
@@ -103,7 +110,8 @@ let WasmBridge = class WasmBridge {
103
110
  async init(exports) {
104
111
  this.exports = exports;
105
112
  this.ensureMemory(false);
106
- for (let id in this.modules) {
113
+ for (let id of topoSort(this.modules, (module) => module.dependencies)) {
114
+ assert(!!this.modules[id], `missing API module: ${id}`);
107
115
  this.logger.debug(`initializing API module: ${id}`);
108
116
  const status = await this.modules[id].init(this);
109
117
  if (!status)
@@ -113,11 +121,11 @@ let WasmBridge = class WasmBridge {
113
121
  return true;
114
122
  }
115
123
  /**
116
- * Called automatically during initialization. Initializes and/or updates
117
- * the various typed WASM memory views (e.g. after growing the WASM memory
118
- * and the previous buffer becoming detached). Unless `notify` is false,
119
- * the {@link EVENT_MEMORY_CHANGED} event will be emitted if the memory
120
- * views had to be updated.
124
+ * Called automatically during initialization and from other memory
125
+ * accessors. Initializes and/or updates the various typed WASM memory views
126
+ * (e.g. after growing the WASM memory and the previous buffer becoming
127
+ * detached). Unless `notify` is false, the {@link EVENT_MEMORY_CHANGED}
128
+ * event will be emitted if the memory views had to be updated.
121
129
  *
122
130
  * @param notify
123
131
  */
@@ -147,16 +155,16 @@ let WasmBridge = class WasmBridge {
147
155
  * API and any provided bridge API modules.
148
156
  *
149
157
  * @remarks
150
- * Since v0.4.0 each API module's imports will be in their own WASM import
151
- * object, named using the same key which was assigned to the module when
152
- * creating the WASM bridge. The bridge's core API will be named `core` and
153
- * is reserved.
158
+ * Each API module's imports will be in their own WASM import object/table,
159
+ * named using the same key which is defined by the JS side of the module
160
+ * via {@link IWasmAPI.id}. The bridge's core API is named `wasmapi` and is
161
+ * reserved.
154
162
  *
155
163
  * @example
156
164
  * The following creates a bridge with a fictional `custom` API module:
157
165
  *
158
166
  * ```ts
159
- * const bridge = new WasmBridge({ custom: new CustomAPI() });
167
+ * const bridge = new WasmBridge([new CustomAPI()]);
160
168
  *
161
169
  * // get combined imports object
162
170
  * bridge.getImports();
@@ -177,11 +185,8 @@ let WasmBridge = class WasmBridge {
177
185
  */
178
186
  getImports() {
179
187
  if (!this.imports) {
180
- this.imports = { wasmapi: this.api };
188
+ this.imports = { [this.id]: this.api };
181
189
  for (let id in this.modules) {
182
- if (this.imports[id] !== undefined) {
183
- illegalArgs(`attempt to redeclare API module ${id}`);
184
- }
185
190
  this.imports[id] = this.modules[id].getImports();
186
191
  }
187
192
  }
@@ -391,9 +396,7 @@ let WasmBridge = class WasmBridge {
391
396
  this.ensureMemory();
392
397
  maxBytes = Math.min(maxBytes, this.u8.length - addr);
393
398
  const len = this.utf8Encoder.encodeInto(str, this.u8.subarray(addr, addr + maxBytes)).written;
394
- if (len == null || len >= maxBytes + (terminate ? 0 : 1)) {
395
- illegalArgs(`error writing string to 0x${U32(addr)} (max. ${maxBytes} bytes, got at least ${str.length})`);
396
- }
399
+ assert(len != null && len < maxBytes + (terminate ? 0 : 1), `error writing string to 0x${U32(addr)} (max. ${maxBytes} bytes, got at least ${str.length})`);
397
400
  if (terminate) {
398
401
  this.u8[addr + len] = 0;
399
402
  }
@@ -402,7 +405,7 @@ let WasmBridge = class WasmBridge {
402
405
  getElementById(addr, len = 0) {
403
406
  const id = this.getString(addr, len);
404
407
  const el = document.getElementById(id);
405
- el == null && illegalArgs(`missing DOM element #${id}`);
408
+ assert(!!el, `missing DOM element #${id}`);
406
409
  return el;
407
410
  }
408
411
  /** {@inheritDoc @thi.ng/api#INotify.addListener} */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/wasm-api",
3
- "version": "0.17.3",
3
+ "version": "0.18.0",
4
4
  "description": "Generic, modular, extensible API bridge, polyglot glue code and bindings code generators for hybrid JS & WebAssembly projects",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -38,6 +38,7 @@
38
38
  "dependencies": {
39
39
  "@thi.ng/api": "^8.4.4",
40
40
  "@thi.ng/args": "^2.2.7",
41
+ "@thi.ng/arrays": "^2.4.0",
41
42
  "@thi.ng/binary": "^3.3.8",
42
43
  "@thi.ng/checks": "^3.3.2",
43
44
  "@thi.ng/compare": "^2.1.14",
@@ -141,5 +142,5 @@
141
142
  "status": "alpha",
142
143
  "year": 2022
143
144
  },
144
- "gitHead": "7c5c0cb37ff88c2fa5dd77dd78bf3a5ceb5c62c6\n"
145
+ "gitHead": "7eff3051eb395f460421727b8cf5ef79f09faaa9\n"
145
146
  }