motely-wasm 19.1.1 → 19.3.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.
Files changed (36) hide show
  1. package/README.md +143 -198
  2. package/dist/bcl/cancellation.d.mts +4 -7
  3. package/dist/bcl/cancellation.mjs +5 -10
  4. package/dist/bcl/collection.d.mts +1 -4
  5. package/dist/bcl/collection.mjs +1 -4
  6. package/dist/bcl/dictionary.d.mts +25 -0
  7. package/dist/bcl/dictionary.mjs +47 -0
  8. package/dist/bcl/event.d.mts +1 -1
  9. package/dist/bcl/event.mjs +1 -1
  10. package/dist/bcl/index.d.mts +1 -0
  11. package/dist/bcl/index.mjs +1 -0
  12. package/dist/bcl/list.d.mts +5 -8
  13. package/dist/bcl/list.mjs +9 -12
  14. package/dist/bin/motely-wasm.wasm +0 -0
  15. package/dist/dotnet/dotnet.native.js +1 -1
  16. package/dist/generated/imports.g.mjs +12 -2
  17. package/dist/generated/instances.g.mjs +111 -847
  18. package/dist/generated/modules/index.g.d.mts +1 -1222
  19. package/dist/generated/modules/index.g.mjs +1 -1975
  20. package/dist/generated/modules/motely/analysis.g.d.mts +49 -0
  21. package/dist/generated/modules/motely/analysis.g.mjs +6 -0
  22. package/dist/generated/modules/motely/enums.g.d.mts +136 -0
  23. package/dist/generated/modules/motely/enums.g.mjs +262 -0
  24. package/dist/generated/modules/motely/filters/jaml.g.d.mts +30 -0
  25. package/dist/generated/modules/motely/filters/jaml.g.mjs +48 -0
  26. package/dist/generated/modules/motely/filters.g.d.mts +8 -0
  27. package/dist/generated/modules/motely/filters.g.mjs +6 -0
  28. package/dist/generated/modules/motely/wasm.g.d.mts +36 -0
  29. package/dist/generated/modules/motely/wasm.g.mjs +42 -0
  30. package/dist/generated/modules/motely.g.d.mts +141 -0
  31. package/dist/generated/modules/motely.g.mjs +139 -0
  32. package/dist/generated/resources.g.mjs +1 -15
  33. package/dist/generated/serializer.g.mjs +0 -270
  34. package/package.json +4 -4
  35. package/motely-item-formats.d.ts +0 -535
  36. package/motely-item-formats.mjs +0 -525
package/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # motely-wasm
2
2
 
3
- WebAssembly package for [Motely](https://github.com/OptimusPi/MotelyJAML) the Balatro seed search engine, with filters written in JAML.
4
-
5
- The package ships `jaml.schema.json` (JSON Schema for the JAML filter format) at the package root, so editors can wire it up for autocomplete / validation without an extra fetch.
3
+ Balatro seed finder and per-seed analyzer, compiled to WebAssembly via [Bootsharp](https://bootsharp.com). Runs in browsers and Node/Deno/Bun.
6
4
 
7
5
  ## Install
8
6
 
@@ -10,255 +8,202 @@ The package ships `jaml.schema.json` (JSON Schema for the JAML filter format) at
10
8
  npm install motely-wasm
11
9
  ```
12
10
 
13
- ## Quick start
14
-
15
- ```js
16
- import bootsharp, { Motely } from "motely-wasm";
11
+ ## Module layout
17
12
 
18
- // Boot the .NET WASM runtime no arguments needed.
19
- // The WASM binary is embedded in the package as base64 (single self-contained ESM).
20
- await bootsharp.boot();
13
+ The root barrel (`motely-wasm`) re-exports only the Bootsharp runtime (`boot`, `getStatus`, `manifest`, …). The Motely API and enums live in generated submodules and **must be imported directly** via the package's subpath exports:
21
14
 
22
- // A JAML filter — see https://github.com/OptimusPi/MotelyJAML for the language.
23
- const jaml = `
24
- name: WeeMonday
25
- deck: Erratic
26
- stake: Black
27
- must:
28
- - joker: WeeJoker
29
- antes: [1]
30
- `;
31
-
32
- // Validate before searching — returns "valid" or an error message.
33
- const status = Motely.validateJaml(jaml);
34
- if (status !== "valid") throw new Error(status);
15
+ ```js
16
+ import bootsharp from "motely-wasm";
17
+ import { Program as Motely } from "motely-wasm/motely/wasm";
18
+ import * as enums from "motely-wasm/motely/enums";
19
+ ```
35
20
 
36
- // One callback set per WASM load — subscribe after boot, before .start().
37
- Motely.onScoredResult.subscribe(r => console.log("match:", r.seed, r.score));
38
- Motely.onProgress.subscribe(p => console.log(`${p.percentComplete.toFixed(1)}%`));
21
+ Additional submodules:
39
22
 
40
- const search = Motely.fromJaml(jaml).withSequentialSearch().start();
23
+ | Specifier | Contents |
24
+ |---|---|
25
+ | `motely-wasm/motely/wasm` | `Program` — all search/analyze/JAML functions |
26
+ | `motely-wasm/motely/enums` | `MotelyDeck`, `MotelyStake`, `MotelyBossBlind`, … |
27
+ | `motely-wasm/motely/analysis` | `MotelyJamlyzerResult`, `MotelySeedAnalysis`, … |
28
+ | `motely-wasm/motely/filters/jaml` | `JamlConfig`, `IJamlClause`, `JamlAesthetic` |
29
+ | `motely-wasm/motely/filters` | `JamlSearchPlan` |
41
30
 
42
- await search.waitForCompletionAsync();
43
- console.log("done:", search.totalSeedsSearched, "searched,", search.matchingSeeds, "matched");
44
- ```
31
+ TypeScript types ship alongside each module as `.d.mts`.
45
32
 
46
- ## Booting
33
+ ## Boot
47
34
 
48
- `bootsharp.boot()` initializes the .NET WASM runtime. Call it once before any `Motely.*` API.
35
+ Binaries are sideloaded (published to `dist/bin/` as separate files). Pass the wasm bytes directly `boot()` accepts either raw bytes or a URL root.
49
36
 
50
- The WASM binary is **embedded** in the package as base64 — no separate files to serve, no CDN config, no `bin/` directory. One import, one `boot()`, done. Works in browser, Node, Web Workers, v0 previews, MCP apps, GitHub Pages — anywhere.
37
+ ### Node
51
38
 
52
39
  ```js
40
+ import { readFile } from "node:fs/promises";
41
+ import { resolve, dirname } from "node:path";
42
+ import { fileURLToPath } from "node:url";
53
43
  import bootsharp from "motely-wasm";
44
+ import { Program as Motely } from "motely-wasm/motely/wasm";
45
+ import * as enums from "motely-wasm/motely/enums";
54
46
 
55
- if (bootsharp.getStatus() === bootsharp.BootStatus.Standby) {
56
- await bootsharp.boot();
57
- }
47
+ // [Import] callbacks must be assigned BEFORE boot.
48
+ Motely.reportWasmError = (message) => console.error("[WASM ERROR]", message);
49
+ Motely.jimmolateProbe = () => false; // required even when not using Jimmolate
58
50
 
59
- console.log(bootsharp.getStatus()); // BootStatus.Booted
51
+ // import.meta.resolve("motely-wasm") → <pkg>/dist/index.mjs; dirname is already <pkg>/dist.
52
+ const distDir = dirname(fileURLToPath(import.meta.resolve("motely-wasm")));
53
+ const wasm = await readFile(resolve(distDir, "bin", bootsharp.manifest.wasm));
54
+ await bootsharp.boot({ wasm });
60
55
  ```
61
56
 
62
- **Publish gate (repo):** after `dotnet publish Motely.Wasm -c Release`, run
63
- `node Motely.Wasm/motely.test.mjs` and `node Motely.Wasm/pack-consumer-smoke.mjs`.
64
-
65
- ## JAML API
57
+ ### Browser
66
58
 
67
59
  ```js
68
- import { Motely } from "motely-wasm";
69
-
70
- // Validate a JAML filter string — returns "valid" on success, an error message on failure.
71
- const status = Motely.validateJaml(jaml);
72
-
73
- // Human-readable explanation of what a JAML filter does.
74
- const explanation = Motely.explainJaml(jaml);
75
-
76
- // Inspect the search plan (tally column count, CSV header, labels).
77
- const plan = Motely.createPlan(jaml);
60
+ import bootsharp from "motely-wasm";
61
+ import { Program as Motely } from "motely-wasm/motely/wasm";
78
62
 
79
- // Analyze specific seeds against a JAML filter.
80
- const result = Motely.analyzeJamlSeeds(jaml, ["ABCD1234", "XYZ99"]);
63
+ Motely.reportWasmError = (message) => console.error("[WASM ERROR]", message);
64
+ Motely.jimmolateProbe = () => false;
81
65
 
82
- // Engine version string.
83
- console.log(Motely.version());
66
+ // Pass the URL root where dist/bin/ is served.
67
+ await bootsharp.boot("/assets/motely-wasm/dist/bin");
84
68
  ```
85
69
 
86
- ## JAML schema
70
+ ## Core API
87
71
 
88
- `motely-wasm` ships `jaml.schema.json` at the package root point your editor at it for
89
- autocomplete and inline validation while writing filters:
72
+ All methods are on the `Motely` (i.e. `Program`) namespace.
90
73
 
91
- ```json
92
- {
93
- "$schema": "node_modules/motely-wasm/jaml.schema.json"
94
- }
95
- ```
96
-
97
- Or with a `# yaml-language-server` comment at the top of any `.jaml` / `.yml` filter file:
74
+ ### Parse a JAML filter
98
75
 
99
- ```yaml
100
- # yaml-language-server: $schema=node_modules/motely-wasm/jaml.schema.json
101
- name: WeeMonday
102
- deck: Erratic
103
- stake: Black
76
+ ```js
77
+ const cfg = Motely.parseJaml(`
78
+ name: My Filter
79
+ deck: Red
80
+ stake: White
104
81
  must:
105
- - joker: WeeJoker
106
- antes: [1]
82
+ - joker: Triboulet
83
+ antes: [1, 2, 3]
84
+ `);
107
85
  ```
108
86
 
109
- JAML is a YAML dialect (JSON-compatible). A filter is a flat document:
87
+ `parseJaml` throws on invalid YAML. Use `jamlToJson` / `jsonToJaml` to convert between representations. Use `explainJaml` to get a human-readable summary of a parsed config.
110
88
 
111
- | Key | Purpose | Example |
112
- |---|---|---|
113
- | `name` | Human label | `WeeMonday` |
114
- | `deck` | Starting deck | `Erratic`, `Red`, `Ghost`, … |
115
- | `stake` | Difficulty floor | `White`, `Black`, `Gold`, … |
116
- | `must` | All clauses must match | list of clause objects |
117
- | `mustNot` | All clauses must NOT match | list of clause objects |
118
- | `should` | Scored clauses (use with `score:`) | list of clause objects |
89
+ ### Search
119
90
 
120
- Each clause object names a target type (`joker`, `tarot`, `planet`, `voucher`, `tag`, `boss`) plus
121
- optional filters (`antes`, `sources`, `min`, `score`):
91
+ All `run*Search` methods return an `IMotelySearch` call `.start()` then listen to events, or call `.runSearchUntilCompletion()` / `await .runSearchAsync()` for synchronous / async use.
122
92
 
123
- ```yaml
124
- must:
125
- - joker: WeeJoker # specific item name, or "Any"
126
- antes: [1, 2] # which antes to check (omit = all)
127
- sources:
128
- shopItems: [0, 1] # shop slot indices
129
- boosterPacks: [0] # pack slot indices
130
- - tag: NegativeTag
131
- antes: [1]
132
- - voucher: Telescope
133
- antes: [1, 2]
134
- min: 2 # appear at least N times across all listed antes
93
+ ```js
94
+ // Subscribe to results before starting.
95
+ Motely.onSeedMatch.subscribe((seed) => console.log("match:", seed));
96
+ Motely.onProgress.subscribe((p) => console.log(`${p.percentComplete.toFixed(1)}%`));
97
+
98
+ // Sequential: scan all seeds in batch range [0, 1).
99
+ const search = Motely.runSequentialSearch(cfg, 0n, 1n);
100
+ await search.runSearchAsync();
101
+
102
+ // List: check specific seeds.
103
+ cfg.seeds = ["PIFREAK1", "DEADBEEF"];
104
+ const listSearch = Motely.runSeedListSearch(cfg);
105
+ listSearch.runSearchUntilCompletion();
106
+ console.log(listSearch.matchingSeeds); // bigint
107
+
108
+ // Scored: emits onScoredResult instead of onSeedMatch.
109
+ Motely.onScoredResult.subscribe((r) => console.log(r.seed, r.score));
110
+ const scored = Motely.runSequentialSearch(cfg, 0n, 1n);
111
+ await scored.runSearchAsync();
135
112
  ```
136
113
 
137
- Use `Motely.validateJaml(jaml)` to check a filter string at runtime; `Motely.explainJaml(jaml)`
138
- returns a human-readable plan; `Motely.createPlan(jaml)` returns the scoring structure.
114
+ ### Analyze a seed
139
115
 
140
- ## Running a search
141
-
142
- Use `Motely.createSearchSettings()`, `Motely.createNativeSearchSettings(name)`, or
143
- `Motely.fromJaml(jaml)` — then chain modes. Callbacks are registered once per WASM load on `Motely`.
116
+ `jamlyzer` runs the JAMLyzer on all seeds in `cfg.seeds` and returns per-ante detail: boss blind, voucher, tags, shop queue, booster packs.
144
117
 
145
118
  ```js
146
- import { Motely } from "motely-wasm";
147
-
148
- Motely.onSeedMatch.subscribe(seed => { /* */ });
149
- Motely.onScoredResult.subscribe(r => { /* … */ });
150
- Motely.onProgress.subscribe(p => { /* … */ });
151
-
152
- const search = Motely.fromJaml(jaml).withSequentialSearch().start();
119
+ cfg.seeds = ["PIFREAK1"];
120
+ const result = Motely.jamlyzer(cfg);
121
+ for (const { seed, analysis } of result.seeds) {
122
+ for (const ante of analysis.antes) {
123
+ console.log(`ante ${ante.ante} boss: ${enums.MotelyBossBlind[ante.boss]}`);
124
+ for (const { item, matched } of ante.shopQueue) {
125
+ console.log(" shop item", item.value, matched ? "(matched)" : "");
126
+ }
127
+ }
128
+ }
129
+ ```
153
130
 
154
- // Async (yields between batches — good on the main thread or in a Worker)
155
- await search.waitForCompletionAsync();
131
+ ### Seed context (low-level)
156
132
 
157
- // or synchronous (blocks until done only inside a Worker)
158
- // search.runSearchUntilCompletion();
133
+ For custom logic, get a `MotelySingleSearchContext` and query PRNG streams directly:
159
134
 
160
- console.log(search.isCompleted, search.totalSeedsSearched, search.matchingSeeds);
161
- search.cancel(); // stop early
135
+ ```js
136
+ const ctx = Motely.seedContext("PIFREAK1", enums.MotelyDeck.Red, enums.MotelyStake.White);
137
+ const voucher = ctx.getAnteFirstVoucher(1);
138
+ console.log("ante 1 voucher:", enums.MotelyVoucher[voucher]);
162
139
  ```
163
140
 
164
- ## Events
141
+ ## Jimmolate
165
142
 
166
- Callbacks are registered on `Motely` once per `bootsharp.boot()` not on each settings
167
- chain. Every search started from that WASM load shares the same handlers; run one search at
168
- a time or use separate worker boots if you need isolated callbacks.
143
+ Jimmolate is a custom JS scalar predicate that runs on every seed that passes the base JAML filter. Wire it up and call `enableJimmolate()` after boot:
169
144
 
170
- | Event | Payload |
171
- |---|---|
172
- | `Motely.onSeedMatch` | `string` matching seed |
173
- | `Motely.onScoredResult` | `{ seed, score, tallies }` |
174
- | `Motely.onProgress` | `MotelyProgress` — `percentComplete`, `seedsSearched`, `matchingSeeds`, `seedsPerMillisecond`, `elapsedMilliseconds` |
175
- | `Motely.onFileChanges` | `Change[]` — fires when files change under a directory mounted via `Motely.mountRoot` (browser File System Access API, requires `Bootsharp.FileSystem`). Ignore if your app doesn't mount local directories. |
145
+ ```js
146
+ // Must be set BEFORE boot.
147
+ Motely.jimmolateProbe = (ctx) => {
148
+ // ctx is MotelySingleSearchContext same API as seedContext().
149
+ return ctx.getSeed().startsWith("PI");
150
+ };
176
151
 
177
- Subscribe and unsubscribe:
152
+ await bootsharp.boot({ wasm });
153
+ Motely.enableJimmolate();
178
154
 
179
- ```js
180
- const handler = r => console.log(r.seed);
181
- Motely.onScoredResult.subscribe(handler);
182
- Motely.onScoredResult.unsubscribe(handler);
155
+ cfg.seeds = ["PIFREAK1", "XYZABCDE"];
156
+ Motely.onSeedMatch.subscribe((s) => console.log("jimmolate match:", s));
157
+ Motely.runPassthroughListSearch(cfg.seeds).runSearchUntilCompletion();
183
158
  ```
184
159
 
185
- ## Submodule exports
160
+ Use `runPassthroughListSearch` to skip the JAML filter entirely and let Jimmolate do all the culling.
186
161
 
187
- | Import path | Contents |
188
- |---|---|
189
- | `motely-wasm` | Default export: `boot`, `getStatus`, `BootStatus`. Named export: `Motely` (main API) |
190
- | `motely-wasm/motely` | `IMotelySearch`, `SearchSettings`, `MotelyProgress`, `MotelyScoredSeedResult`, `MotelyStreamKind` |
191
- | `motely-wasm/motely/enums` | All Balatro enums — `MotelyItemType`, `MotelyItemTypeCategory`, `MotelyJokerRarity`, `MotelyItemEdition`, `MotelyItemSeal`, `MotelyItemEnhancement`, `MotelyTag`, `MotelyVoucher`, `MotelyBoosterPack`, `MotelyDeck`, `MotelyStake`, `MotelyBossBlind`, etc. |
192
- | `motely-wasm/motely/filters` | `JamlAesthetic`, `JamlSearchPlan` |
193
- | `motely-wasm/motely/analysis` | `MotelyJamlyzerResult`, `MotelySeedAnalysis` |
194
- | `motely-wasm/bootsharp/file-system` | File-system interop (browser OPFS) — `PermissionMode`, `IFileMounter` |
162
+ ## JAML quick reference
195
163
 
196
- ## Using in a Web Worker
164
+ ```yaml
165
+ name: string # optional display name
166
+ deck: Red # MotelyDeck value (case-insensitive)
167
+ stake: White # MotelyStake value
168
+ seeds: [] # pre-populate for list searches
169
+
170
+ must: # all clauses must match
171
+ - joker: Triboulet
172
+ antes: [1, 2] # ante numbers (1–8)
173
+ min: 1 # default 1
174
+ max: 2 # optional upper bound
175
+
176
+ should: # scoring; doesn't filter, raises score
177
+ - voucher: Telescope
178
+ antes: [1, 2]
179
+ score: 10
197
180
 
198
- The WASM runtime is single-threaded. For a non-blocking UI, boot a runtime inside a
199
- Worker and drive it with messages. This mirrors the proven setup in the `jaml-ui`
200
- package's `searchWorker.ts`.
181
+ mustNot: # any match rejects the seed
182
+ - boss: TheHook
183
+ antes: [1]
184
+ ```
201
185
 
202
- ```js
203
- // search-worker.js
204
- import bootsharp, { Motely } from "motely-wasm";
205
-
206
- let currentSearch = null;
207
-
208
- self.onmessage = async ({ data }) => {
209
- if (data.type === "stop") {
210
- currentSearch?.cancel();
211
- self.postMessage({ type: "cancelled" });
212
- return;
213
- }
214
- if (data.type !== "start") return;
215
-
216
- try {
217
- if (bootsharp.getStatus() === bootsharp.BootStatus.Standby) {
218
- await bootsharp.boot();
219
- }
186
+ Clause types: `joker`, `voucher`, `boss`, `tag`, `spectral`, `tarot`, `planet`, `pack`.
187
+ Sources can be narrowed via `sources: { shopItems: [0, 1], boosterPacks: [0] }`.
220
188
 
221
- const onResult = r =>
222
- self.postMessage({ type: "result", seed: r.seed, score: r.score });
223
- const onProgress = p =>
224
- self.postMessage({ type: "progress", percent: p.percentComplete });
225
- Motely.onScoredResult.subscribe(onResult);
226
- Motely.onProgress.subscribe(onProgress);
227
-
228
- try {
229
- currentSearch = Motely.createSearch(data.jaml)
230
- .withThreadCount(1)
231
- .withSequentialSearch()
232
- .start();
233
- await currentSearch.waitForCompletionAsync();
234
- self.postMessage({
235
- type: "complete",
236
- total: Number(currentSearch.totalSeedsSearched),
237
- matched: Number(currentSearch.matchingSeeds),
238
- });
239
- } finally {
240
- Motely.onScoredResult.unsubscribe(onResult);
241
- Motely.onProgress.unsubscribe(onProgress);
242
- currentSearch = null;
243
- }
244
- } catch (error) {
245
- self.postMessage({ type: "error", message: String(error?.message ?? error) });
246
- }
247
- };
189
+ ## Enums
248
190
 
249
- self.postMessage({ type: "ready" });
250
- ```
191
+ All enum values are available in the `enums` module:
192
+
193
+ - `MotelyDeck` — Red, Blue, Yellow, Green, Black, Magic, Nebula, Ghost, Abandoned, Checkered, Zodiac, Painted, Anaglyph, Plasma, Erratic
194
+ - `MotelyStake` — White, Red, Green, Black, Blue, Purple, Orange, Gold
195
+ - `MotelyBossBlind`, `MotelyVoucher`, `MotelyTag`, `MotelyBoosterPack`
196
+
197
+ ## Utility
251
198
 
252
199
  ```js
253
- // main thread
254
- const worker = new Worker(new URL("./search-worker.js", import.meta.url), { type: "module" });
200
+ // Convert between JAML text and JSON.
201
+ const json = Motely.jamlToJson(jamlText);
202
+ const jaml = Motely.jsonToJaml(json);
255
203
 
256
- worker.onmessage = ({ data }) => {
257
- if (data.type === "result") console.log("match:", data.seed, data.score);
258
- if (data.type === "progress") console.log(`${data.percent.toFixed(1)}%`);
259
- if (data.type === "complete") console.log("done:", data.total, data.matched);
260
- };
204
+ // Human-readable description of a parsed config.
205
+ const explanation = Motely.explainJaml(cfg);
261
206
 
262
- worker.postMessage({ type: "start", jaml });
263
- // worker.postMessage({ type: "stop" }); // cancel early
207
+ // List available native (non-JAML) filter names.
208
+ const names = Motely.nativeFilterNames();
264
209
  ```
@@ -1,14 +1,11 @@
1
1
  import { Event } from "./event.mjs";
2
- /** JavaScript counterpart of the C# <c>System.Threading.CancellationToken</c> specialization.
3
- * Hand-rolled instances are constructed in user code and passed across the interop boundary;
4
- * instances received from C# are wrapped into generated proxies that expose the same shape. */
2
+ /** A cancellation token compatible with C# `CancellationToken`. */
5
3
  export declare class CancellationToken {
6
- private cancelled;
7
- /** Fires when the token is cancelled. */
4
+ /** Occurs when the token is cancelled. */
8
5
  readonly onCancellationRequested: Event<[]>;
9
6
  /** Whether cancellation has been requested. */
10
7
  get isCancellationRequested(): boolean;
11
- /** Signal cancellation: flips <see cref="isCancellationRequested"/> and broadcasts
12
- * <see cref="onCancellationRequested"/>. Idempotent. */
8
+ private cancelled;
9
+ /** Signal cancellation. */
13
10
  cancel(): void;
14
11
  }
@@ -1,17 +1,12 @@
1
1
  import { Event } from "./event.mjs";
2
- /** JavaScript counterpart of the C# <c>System.Threading.CancellationToken</c> specialization.
3
- * Hand-rolled instances are constructed in user code and passed across the interop boundary;
4
- * instances received from C# are wrapped into generated proxies that expose the same shape. */
2
+ /** A cancellation token compatible with C# `CancellationToken`. */
5
3
  export class CancellationToken {
6
- cancelled = false;
7
- /** Fires when the token is cancelled. */
4
+ /** Occurs when the token is cancelled. */
8
5
  onCancellationRequested = new Event();
9
6
  /** Whether cancellation has been requested. */
10
- get isCancellationRequested() {
11
- return this.cancelled;
12
- }
13
- /** Signal cancellation: flips <see cref="isCancellationRequested"/> and broadcasts
14
- * <see cref="onCancellationRequested"/>. Idempotent. */
7
+ get isCancellationRequested() { return this.cancelled; }
8
+ cancelled = false;
9
+ /** Signal cancellation. */
15
10
  cancel() {
16
11
  if (this.cancelled)
17
12
  return;
@@ -1,7 +1,4 @@
1
- /** JavaScript counterpart of the C# <c>Bootsharp.Collection&lt;T&gt;</c> specialization of
2
- * <c>System.Collections.Generic.ICollection&lt;T&gt;</c>. Hand-rolled instances are constructed in
3
- * user code and passed across the interop boundary; instances received from C# are wrapped into
4
- * generated proxies that expose the same shape. */
1
+ /** A collection of items compatible with C# `ICollection<T>`. */
5
2
  export declare class Collection<T> {
6
3
  protected readonly items: T[];
7
4
  constructor(items?: Iterable<T>);
@@ -1,7 +1,4 @@
1
- /** JavaScript counterpart of the C# <c>Bootsharp.Collection&lt;T&gt;</c> specialization of
2
- * <c>System.Collections.Generic.ICollection&lt;T&gt;</c>. Hand-rolled instances are constructed in
3
- * user code and passed across the interop boundary; instances received from C# are wrapped into
4
- * generated proxies that expose the same shape. */
1
+ /** A collection of items compatible with C# `ICollection<T>`. */
5
2
  export class Collection {
6
3
  items;
7
4
  constructor(items) {
@@ -0,0 +1,25 @@
1
+ /** A dictionary of key-value pairs compatible with C# `IDictionary<TKey, TValue>`. */
2
+ export declare class Dictionary<TKey, TValue> {
3
+ protected readonly map: Map<TKey, TValue>;
4
+ constructor(entries?: Iterable<[TKey, TValue]>);
5
+ /** Number of key-value pairs in the dictionary. */
6
+ get count(): number;
7
+ /** Associates the specified value with the specified key. */
8
+ add(key: TKey, value: TValue): void;
9
+ /** Whether the dictionary contains the specified key. */
10
+ containsKey(key: TKey): boolean;
11
+ /** Removes the value with the specified key from the dictionary.
12
+ * @returns true when the key was removed; false when it wasn't found. */
13
+ remove(key: TKey): boolean;
14
+ /** Removes all key-value pairs from the dictionary. */
15
+ clear(): void;
16
+ /** Returns the value associated with the specified key. */
17
+ getAt(key: TKey): TValue;
18
+ /** Associates the specified value with the specified key. */
19
+ setAt(key: TKey, value: TValue): void;
20
+ /** Returns a fresh array with a snapshot of the current keys. */
21
+ getKeys(): TKey[];
22
+ /** Returns a fresh array with a snapshot of the current values. */
23
+ getValues(): TValue[];
24
+ [Symbol.iterator](): IterableIterator<[TKey, TValue]>;
25
+ }
@@ -0,0 +1,47 @@
1
+ /** A dictionary of key-value pairs compatible with C# `IDictionary<TKey, TValue>`. */
2
+ export class Dictionary {
3
+ map;
4
+ constructor(entries) {
5
+ this.map = new Map(entries);
6
+ }
7
+ /** Number of key-value pairs in the dictionary. */
8
+ get count() {
9
+ return this.map.size;
10
+ }
11
+ /** Associates the specified value with the specified key. */
12
+ add(key, value) {
13
+ this.map.set(key, value);
14
+ }
15
+ /** Whether the dictionary contains the specified key. */
16
+ containsKey(key) {
17
+ return this.map.has(key);
18
+ }
19
+ /** Removes the value with the specified key from the dictionary.
20
+ * @returns true when the key was removed; false when it wasn't found. */
21
+ remove(key) {
22
+ return this.map.delete(key);
23
+ }
24
+ /** Removes all key-value pairs from the dictionary. */
25
+ clear() {
26
+ this.map.clear();
27
+ }
28
+ /** Returns the value associated with the specified key. */
29
+ getAt(key) {
30
+ return this.map.get(key);
31
+ }
32
+ /** Associates the specified value with the specified key. */
33
+ setAt(key, value) {
34
+ this.map.set(key, value);
35
+ }
36
+ /** Returns a fresh array with a snapshot of the current keys. */
37
+ getKeys() {
38
+ return Array.from(this.map.keys());
39
+ }
40
+ /** Returns a fresh array with a snapshot of the current values. */
41
+ getValues() {
42
+ return Array.from(this.map.values());
43
+ }
44
+ [Symbol.iterator]() {
45
+ return this.map[Symbol.iterator]();
46
+ }
47
+ }
@@ -20,7 +20,7 @@ export type EventOptions = {
20
20
  /** Custom warnings handler; by default <code>console.warn</code> is used. */
21
21
  warn?: (message: string) => void;
22
22
  };
23
- /** Allows attaching handlers and broadcasting events. */
23
+ /** Allows attaching handlers and broadcasting events; compatible with C# events. */
24
24
  export declare class Event<T extends unknown[]> implements EventBroadcaster<T>, EventSubscriber<T> {
25
25
  private readonly handlers;
26
26
  private readonly warn;
@@ -1,4 +1,4 @@
1
- /** Allows attaching handlers and broadcasting events. */
1
+ /** Allows attaching handlers and broadcasting events; compatible with C# events. */
2
2
  export class Event {
3
3
  handlers = new Map();
4
4
  warn;
@@ -1,4 +1,5 @@
1
1
  export * from "./event.mjs";
2
2
  export * from "./collection.mjs";
3
3
  export * from "./list.mjs";
4
+ export * from "./dictionary.mjs";
4
5
  export * from "./cancellation.mjs";
@@ -1,4 +1,5 @@
1
1
  export * from "./event.mjs";
2
2
  export * from "./collection.mjs";
3
3
  export * from "./list.mjs";
4
+ export * from "./dictionary.mjs";
4
5
  export * from "./cancellation.mjs";
@@ -1,17 +1,14 @@
1
1
  import { Collection } from "./collection.mjs";
2
- /** JavaScript counterpart of the C# <c>System.Collections.Generic.IList&lt;T&gt;</c> specialization.
3
- * Extends <see cref="Collection"/> with index-based access; like the collection, hand-rolled
4
- * instances are passed across the interop boundary, while instances received from C# are wrapped
5
- * into generated proxies that expose the same shape. */
2
+ /** A list of items compatible with C# `IList<T>`. */
6
3
  export declare class List<T> extends Collection<T> {
4
+ /** Returns the item at the specified index. */
5
+ getAt(index: number): T;
6
+ /** Assigns the specified item at the specified index. */
7
+ setAt(index: number, item: T): void;
7
8
  /** Returns the index of the first occurrence of the specified item, or -1 when not found. */
8
9
  indexOf(item: T): number;
9
10
  /** Inserts the specified item at the specified index. */
10
11
  insert(index: number, item: T): void;
11
12
  /** Removes the item at the specified index. */
12
13
  removeAt(index: number): void;
13
- /** Returns the item at the specified index. */
14
- getAt(index: number): T;
15
- /** Assigns the specified item at the specified index. */
16
- setAt(index: number, item: T): void;
17
14
  }
package/dist/bcl/list.mjs CHANGED
@@ -1,9 +1,14 @@
1
1
  import { Collection } from "./collection.mjs";
2
- /** JavaScript counterpart of the C# <c>System.Collections.Generic.IList&lt;T&gt;</c> specialization.
3
- * Extends <see cref="Collection"/> with index-based access; like the collection, hand-rolled
4
- * instances are passed across the interop boundary, while instances received from C# are wrapped
5
- * into generated proxies that expose the same shape. */
2
+ /** A list of items compatible with C# `IList<T>`. */
6
3
  export class List extends Collection {
4
+ /** Returns the item at the specified index. */
5
+ getAt(index) {
6
+ return this.items[index];
7
+ }
8
+ /** Assigns the specified item at the specified index. */
9
+ setAt(index, item) {
10
+ this.items[index] = item;
11
+ }
7
12
  /** Returns the index of the first occurrence of the specified item, or -1 when not found. */
8
13
  indexOf(item) {
9
14
  return this.items.indexOf(item);
@@ -16,12 +21,4 @@ export class List extends Collection {
16
21
  removeAt(index) {
17
22
  this.items.splice(index, 1);
18
23
  }
19
- /** Returns the item at the specified index. */
20
- getAt(index) {
21
- return this.items[index];
22
- }
23
- /** Assigns the specified item at the specified index. */
24
- setAt(index, item) {
25
- this.items[index] = item;
26
- }
27
24
  }
Binary file