koota-kit 0.6.0 → 0.7.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 +31 -0
- package/CONTRIBUTING.md +2 -1
- package/README.md +43 -4
- package/dist/cjs/coordination.cjs +412 -0
- package/dist/cjs/coordination.d.cts +91 -0
- package/dist/cjs/coordination.d.cts.map +1 -0
- package/dist/cjs/index.cjs +5 -1
- package/dist/cjs/index.d.cts +2 -0
- package/dist/cjs/index.d.cts.map +1 -1
- package/dist/esm/coordination.d.ts +91 -0
- package/dist/esm/coordination.d.ts.map +1 -0
- package/dist/esm/coordination.js +408 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/docs/API.md +102 -0
- package/docs/ARCHITECTURE.md +31 -0
- package/docs/contributing.md +3 -2
- package/package.json +14 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.0 (2026-10-14)
|
|
4
|
+
|
|
5
|
+
<!-- Release notes generated using configuration in .github/release.yml at main -->
|
|
6
|
+
|
|
7
|
+
## What's Changed
|
|
8
|
+
* feat: add borrowed-world coordination by @jbdevprimary in https://github.com/jbcom/koota-kit/pull/54
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
**Full Changelog**: https://github.com/jbcom/koota-kit/compare/v0.6.2...v0.7.0
|
|
12
|
+
|
|
13
|
+
## 0.6.2 (2026-10-14)
|
|
14
|
+
|
|
15
|
+
<!-- Release notes generated using configuration in .github/release.yml at main -->
|
|
16
|
+
|
|
17
|
+
## What's Changed
|
|
18
|
+
* fix(release): accept generated GitHub changelog by @jbdevprimary in https://github.com/jbcom/koota-kit/pull/52
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
**Full Changelog**: https://github.com/jbcom/koota-kit/compare/v0.6.1...v0.6.2
|
|
22
|
+
|
|
23
|
+
## 0.6.1 (2026-10-14)
|
|
24
|
+
|
|
25
|
+
<!-- Release notes generated using configuration in .github/release.yml at main -->
|
|
26
|
+
|
|
27
|
+
## What's Changed
|
|
28
|
+
* chore(deps): bump actions/setup-node from 7.0.0 to 7.1.0 in the non-major group by @dependabot[bot] in https://github.com/jbcom/koota-kit/pull/45
|
|
29
|
+
* fix(release): deduplicate changelog pull requests by @jbdevprimary in https://github.com/jbcom/koota-kit/pull/50
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
**Full Changelog**: https://github.com/jbcom/koota-kit/compare/v0.6.0...v0.6.1
|
|
33
|
+
|
|
3
34
|
## [0.6.0](https://github.com/jbcom/koota-kit/compare/v0.5.0...v0.6.0) (2026-10-10)
|
|
4
35
|
|
|
5
36
|
|
package/CONTRIBUTING.md
CHANGED
|
@@ -31,7 +31,8 @@ pnpm verify
|
|
|
31
31
|
3. Run `pnpm verify`. A change is not ready while any part of that is red.
|
|
32
32
|
4. Commit with [Conventional Commits](https://www.conventionalcommits.org):
|
|
33
33
|
`fix:`, `feat:`, `docs:`, `refactor:`, `test:`, `chore:`. Release Please uses
|
|
34
|
-
these commits to
|
|
34
|
+
these commits to select the next version; GitHub release notes list each
|
|
35
|
+
merged pull request once in the changelog.
|
|
35
36
|
5. Open a pull request describing what changed and why.
|
|
36
37
|
|
|
37
38
|
## What gets reviewed
|
package/README.md
CHANGED
|
@@ -25,6 +25,7 @@ Full documentation: **[jonbogaty.com/koota-kit](https://jonbogaty.com/koota-kit/
|
|
|
25
25
|
| Object state leaks between entities | `defineTrait` requires per-entity factories |
|
|
26
26
|
| A HUD or test consumes an event before its real system sees it | Single-consumer `drain`, non-consuming `peek` |
|
|
27
27
|
| Replaced worlds retain caches or process-global Koota IDs | `createSimWorld` / idempotent `destroySimWorld` lifecycle |
|
|
28
|
+
| Two producers publish half-updated model facts | `koota-kit/coordination` commits an immutable completed metadata projection |
|
|
28
29
|
|
|
29
30
|
## Install
|
|
30
31
|
|
|
@@ -100,6 +101,43 @@ The scratch map is for derived, non-serializable caches and short-lived
|
|
|
100
101
|
coordination state. Namespace keys by system, for example `pathfinding:grid`
|
|
101
102
|
or `combat:hits`.
|
|
102
103
|
|
|
104
|
+
### Coordinate existing model entities without another World
|
|
105
|
+
|
|
106
|
+
`koota-kit/coordination` binds to a caller-owned raw Koota `World`; it does
|
|
107
|
+
not create, reset, or destroy one. Register existing entities with stable
|
|
108
|
+
caller-authored IDs and capability labels, then publish completed producer
|
|
109
|
+
revisions as a batch. The snapshot is immutable, ordered by ID, and changes
|
|
110
|
+
only after the complete batch validates and its raw Koota trait writes succeed.
|
|
111
|
+
Producers needing one atomic cohort publish through the same binding; disjoint
|
|
112
|
+
bindings are independent cohorts.
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { bindCoordination } from "koota-kit/coordination";
|
|
116
|
+
|
|
117
|
+
const coordination = bindCoordination(sim.world);
|
|
118
|
+
const ava = sim.world.spawn(Position({ x: 4, y: 9 }));
|
|
119
|
+
const token = coordination.register(ava, {
|
|
120
|
+
id: "figure:ava",
|
|
121
|
+
capabilities: ["presence", "geometry"],
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
coordination.publish(token, {
|
|
125
|
+
revisions: { geometry: 12, pose: 8, transform: 31 },
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const completed = coordination.getSnapshot();
|
|
129
|
+
coordination.dispose(); // removes only koota-kit's coordination traits
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The three producer revisions are independent values; a coordination snapshot
|
|
133
|
+
revision only identifies a committed observer view. This entry does not copy
|
|
134
|
+
or freeze provider buffers, choose a geometry backend, evaluate a pose, or
|
|
135
|
+
answer spatial queries. Those facts belong to later producer/query adapters.
|
|
136
|
+
Call `prune()` after an entity is removed externally before reusing its stable
|
|
137
|
+
ID. Listener errors occur after a committed publication; retrieve a token with
|
|
138
|
+
`getRegistration(id)` if registration notification threw before `register`
|
|
139
|
+
could return it.
|
|
140
|
+
|
|
103
141
|
### Randomness has two jobs
|
|
104
142
|
|
|
105
143
|
- `rng.gen` defines stable world content: terrain, layouts, initial spawns.
|
|
@@ -286,10 +324,11 @@ pnpm audit --prod # registry-backed production dependency audit
|
|
|
286
324
|
|
|
287
325
|
## Releases and support
|
|
288
326
|
|
|
289
|
-
Release Please
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
327
|
+
Release Please uses Conventional Commits to select versions and GitHub's
|
|
328
|
+
merged-pull-request release notes to maintain `CHANGELOG.md`. A release tag
|
|
329
|
+
triggers a clean verification and npm publication with provenance. The latest
|
|
330
|
+
`0.x` release receives fixes; pre-1.0 APIs may evolve, with breaking changes
|
|
331
|
+
called out in the changelog.
|
|
293
332
|
|
|
294
333
|
See [CONTRIBUTING.md](./CONTRIBUTING.md) before opening a pull request. Report
|
|
295
334
|
security issues privately according to [SECURITY.md](./SECURITY.md).
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CoordinationRegistration = exports.CoordinationBinding = void 0;
|
|
4
|
+
exports.bindCoordination = bindCoordination;
|
|
5
|
+
/**
|
|
6
|
+
* Borrowed-World coordination for completed model metadata.
|
|
7
|
+
*
|
|
8
|
+
* The caller owns the Koota World and its entities. This module only adds its
|
|
9
|
+
* own marker traits and exposes a committed, immutable projection. It does
|
|
10
|
+
* not create a World, run systems, serialize entities, or read geometry.
|
|
11
|
+
*/
|
|
12
|
+
const koota_1 = require("koota");
|
|
13
|
+
/**
|
|
14
|
+
* World-held ownership markers for live bindings. Each binding appends one
|
|
15
|
+
* opaque marker and removes only that marker at disposal, so no module-global
|
|
16
|
+
* registry or per-bind Koota trait allocation is needed.
|
|
17
|
+
*/
|
|
18
|
+
exports.CoordinationBinding = (0, koota_1.trait)({ markers: () => [] });
|
|
19
|
+
/**
|
|
20
|
+
* The only trait this module places on caller-owned model entities. Its
|
|
21
|
+
* fields are a diagnostic mirror of the binding's committed projection;
|
|
22
|
+
* callers must use `getSnapshot` for an immutable completed view.
|
|
23
|
+
*/
|
|
24
|
+
exports.CoordinationRegistration = (0, koota_1.trait)({
|
|
25
|
+
marker: () => ({}),
|
|
26
|
+
generation: 0,
|
|
27
|
+
id: "",
|
|
28
|
+
geometryRevision: 0,
|
|
29
|
+
poseRevision: 0,
|
|
30
|
+
transformRevision: 0,
|
|
31
|
+
});
|
|
32
|
+
const tokenMarker = Symbol("koota-kit.coordination.token");
|
|
33
|
+
function fail(message) {
|
|
34
|
+
throw new Error(`coordination: ${message}`);
|
|
35
|
+
}
|
|
36
|
+
function isObject(value) {
|
|
37
|
+
return typeof value === "object" && value !== null;
|
|
38
|
+
}
|
|
39
|
+
function requireNonEmptyString(value, name) {
|
|
40
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
41
|
+
throw new TypeError(`coordination: ${name} must be a non-empty string.`);
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
function copyCapabilities(value) {
|
|
46
|
+
if (!Array.isArray(value)) {
|
|
47
|
+
throw new TypeError("coordination: descriptor.capabilities must be an array of non-empty strings.");
|
|
48
|
+
}
|
|
49
|
+
const capabilities = value.map((capability) => requireNonEmptyString(capability, "capability"));
|
|
50
|
+
capabilities.sort((left, right) => Number(left > right) - Number(left < right));
|
|
51
|
+
for (let index = 1; index < capabilities.length; index += 1) {
|
|
52
|
+
if (capabilities[index] === capabilities[index - 1]) {
|
|
53
|
+
throw new TypeError(`coordination: duplicate capability "${capabilities[index]}".`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return Object.freeze(capabilities);
|
|
57
|
+
}
|
|
58
|
+
function copyRevisions(value) {
|
|
59
|
+
if (!isObject(value)) {
|
|
60
|
+
throw new TypeError("coordination: completed.revisions must be an object.");
|
|
61
|
+
}
|
|
62
|
+
const names = ["geometry", "pose", "transform"];
|
|
63
|
+
const values = names.map((name) => value[name]);
|
|
64
|
+
for (const [index, revision] of values.entries()) {
|
|
65
|
+
if (!Number.isSafeInteger(revision) || revision < 0) {
|
|
66
|
+
throw new RangeError(`coordination: completed.revisions.${names[index]} must be a non-negative safe integer.`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return Object.freeze({
|
|
70
|
+
geometry: values[0],
|
|
71
|
+
pose: values[1],
|
|
72
|
+
transform: values[2],
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function copyCompleted(value) {
|
|
76
|
+
if (!isObject(value)) {
|
|
77
|
+
throw new TypeError("coordination: completed metadata must be an object.");
|
|
78
|
+
}
|
|
79
|
+
return Object.freeze({ revisions: copyRevisions(value.revisions) });
|
|
80
|
+
}
|
|
81
|
+
function sameRevisions(left, right) {
|
|
82
|
+
return (left.geometry === right.geometry &&
|
|
83
|
+
left.pose === right.pose &&
|
|
84
|
+
left.transform === right.transform);
|
|
85
|
+
}
|
|
86
|
+
function assertNoRegression(previous, next) {
|
|
87
|
+
if (!previous)
|
|
88
|
+
return;
|
|
89
|
+
for (const name of ["geometry", "pose", "transform"]) {
|
|
90
|
+
if (next.revisions[name] < previous.revisions[name]) {
|
|
91
|
+
throw new RangeError(`coordination: completed revision ${name} may not decrease.`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function makeSnapshot(revision, registrations) {
|
|
96
|
+
const entries = [...registrations]
|
|
97
|
+
.map((registration) => Object.freeze({
|
|
98
|
+
id: registration.id,
|
|
99
|
+
capabilities: registration.capabilities,
|
|
100
|
+
completed: registration.completed,
|
|
101
|
+
}))
|
|
102
|
+
.sort((left, right) => Number(left.id > right.id) - Number(left.id < right.id));
|
|
103
|
+
return Object.freeze({ revision, entries: Object.freeze(entries) });
|
|
104
|
+
}
|
|
105
|
+
function stateIsOwned(state, marker, generation) {
|
|
106
|
+
return state?.marker === marker && state.generation === generation;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Bind to a caller-owned Koota World. A live binding writes its opaque marker
|
|
110
|
+
* onto that same World, without a hidden module-global World registry. Several
|
|
111
|
+
* bindings may coexist only while they register disjoint caller entities.
|
|
112
|
+
*/
|
|
113
|
+
function bindCoordination(world) {
|
|
114
|
+
if (!world?.isInitialized) {
|
|
115
|
+
fail("a live Koota World is required.");
|
|
116
|
+
}
|
|
117
|
+
const marker = Object.freeze({});
|
|
118
|
+
const existingMarkers = world.get(exports.CoordinationBinding)?.markers ?? [];
|
|
119
|
+
const markers = [...existingMarkers, marker];
|
|
120
|
+
try {
|
|
121
|
+
if (world.has(exports.CoordinationBinding))
|
|
122
|
+
world.set(exports.CoordinationBinding, { markers });
|
|
123
|
+
else
|
|
124
|
+
world.add((0, exports.CoordinationBinding)({ markers }));
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
const currentMarkers = world.get(exports.CoordinationBinding)?.markers;
|
|
128
|
+
if (world.isInitialized && currentMarkers?.includes(marker)) {
|
|
129
|
+
const remaining = currentMarkers.filter((candidate) => candidate !== marker);
|
|
130
|
+
if (remaining.length === 0)
|
|
131
|
+
world.remove(exports.CoordinationBinding);
|
|
132
|
+
else
|
|
133
|
+
world.set(exports.CoordinationBinding, { markers: remaining });
|
|
134
|
+
}
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
let disposed = false;
|
|
138
|
+
let notifying = false;
|
|
139
|
+
let mutating = false;
|
|
140
|
+
let nextRegistrationGeneration = 0;
|
|
141
|
+
let nextSnapshotRevision = 0;
|
|
142
|
+
const registrationsById = new Map();
|
|
143
|
+
const registrationsByToken = new Map();
|
|
144
|
+
const listeners = new Set();
|
|
145
|
+
let snapshot = makeSnapshot(nextSnapshotRevision, registrationsById.values());
|
|
146
|
+
const assertActive = () => {
|
|
147
|
+
if (disposed)
|
|
148
|
+
fail("the binding has been disposed.");
|
|
149
|
+
if (notifying || mutating)
|
|
150
|
+
fail("mutating a binding while it notifies listeners is not allowed.");
|
|
151
|
+
};
|
|
152
|
+
const assertMutable = () => {
|
|
153
|
+
assertActive();
|
|
154
|
+
if (!world.isInitialized || !world.get(exports.CoordinationBinding)?.markers.includes(marker)) {
|
|
155
|
+
fail("the binding no longer owns its World marker.");
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
const isCurrent = (registration) => {
|
|
159
|
+
if (!world.isInitialized || !world.has(registration.entity))
|
|
160
|
+
return false;
|
|
161
|
+
return stateIsOwned(registration.entity.get(exports.CoordinationRegistration), marker, registration.state.generation);
|
|
162
|
+
};
|
|
163
|
+
const registrationFor = (token) => {
|
|
164
|
+
const registration = registrationsByToken.get(token);
|
|
165
|
+
if (!registration || token[tokenMarker] !== marker) {
|
|
166
|
+
fail("the registration token does not belong to this binding.");
|
|
167
|
+
}
|
|
168
|
+
if (!isCurrent(registration)) {
|
|
169
|
+
fail("the registration token is stale; call prune before re-registering its id.");
|
|
170
|
+
}
|
|
171
|
+
return registration;
|
|
172
|
+
};
|
|
173
|
+
const notify = () => {
|
|
174
|
+
const deliveries = [...listeners];
|
|
175
|
+
const errors = [];
|
|
176
|
+
notifying = true;
|
|
177
|
+
try {
|
|
178
|
+
for (const listener of deliveries) {
|
|
179
|
+
try {
|
|
180
|
+
listener(snapshot);
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
errors.push(error);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
notifying = false;
|
|
189
|
+
}
|
|
190
|
+
if (errors.length === 1)
|
|
191
|
+
throw errors[0];
|
|
192
|
+
if (errors.length > 1) {
|
|
193
|
+
const error = Object.assign(new Error("coordination: listener failures after commit."), {
|
|
194
|
+
errors: Object.freeze(errors),
|
|
195
|
+
});
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
const nextCounter = (value, name) => {
|
|
200
|
+
const next = value + 1;
|
|
201
|
+
if (!Number.isSafeInteger(next)) {
|
|
202
|
+
throw new RangeError(`coordination: ${name} exceeded the safe integer range.`);
|
|
203
|
+
}
|
|
204
|
+
return next;
|
|
205
|
+
};
|
|
206
|
+
const commit = (revision) => {
|
|
207
|
+
nextSnapshotRevision = revision;
|
|
208
|
+
snapshot = makeSnapshot(revision, registrationsById.values());
|
|
209
|
+
notify();
|
|
210
|
+
return snapshot;
|
|
211
|
+
};
|
|
212
|
+
const register = (entity, descriptor) => {
|
|
213
|
+
assertMutable();
|
|
214
|
+
if (!world.has(entity))
|
|
215
|
+
fail("entity must be live in this binding's World.");
|
|
216
|
+
if (!isObject(descriptor))
|
|
217
|
+
throw new TypeError("coordination: descriptor must be an object.");
|
|
218
|
+
const id = requireNonEmptyString(descriptor.id, "descriptor.id");
|
|
219
|
+
const capabilities = copyCapabilities(descriptor.capabilities);
|
|
220
|
+
if (registrationsById.has(id))
|
|
221
|
+
fail(`stable id "${id}" is already registered.`);
|
|
222
|
+
if (entity.has(exports.CoordinationRegistration)) {
|
|
223
|
+
fail("entity already carries a coordination registration trait.");
|
|
224
|
+
}
|
|
225
|
+
const registrationGeneration = nextCounter(nextRegistrationGeneration, "registration generation");
|
|
226
|
+
const snapshotRevision = nextCounter(nextSnapshotRevision, "snapshot revision");
|
|
227
|
+
const state = Object.freeze({
|
|
228
|
+
marker,
|
|
229
|
+
generation: registrationGeneration,
|
|
230
|
+
id,
|
|
231
|
+
geometryRevision: 0,
|
|
232
|
+
poseRevision: 0,
|
|
233
|
+
transformRevision: 0,
|
|
234
|
+
});
|
|
235
|
+
const token = Object.freeze({ [tokenMarker]: marker });
|
|
236
|
+
const registration = {
|
|
237
|
+
entity,
|
|
238
|
+
token,
|
|
239
|
+
state,
|
|
240
|
+
id,
|
|
241
|
+
capabilities,
|
|
242
|
+
completed: undefined,
|
|
243
|
+
};
|
|
244
|
+
// Every validation above happens before the Koota marker or this binding's
|
|
245
|
+
// projection changes. Koota callbacks can observe this raw trait; they are
|
|
246
|
+
// deliberately outside the atomic committed-snapshot contract.
|
|
247
|
+
mutating = true;
|
|
248
|
+
try {
|
|
249
|
+
try {
|
|
250
|
+
entity.add((0, exports.CoordinationRegistration)(state));
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
const current = entity.get(exports.CoordinationRegistration);
|
|
254
|
+
if (stateIsOwned(current, marker, registrationGeneration)) {
|
|
255
|
+
entity.remove(exports.CoordinationRegistration);
|
|
256
|
+
}
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
nextRegistrationGeneration = registrationGeneration;
|
|
260
|
+
registrationsById.set(id, registration);
|
|
261
|
+
registrationsByToken.set(token, registration);
|
|
262
|
+
commit(snapshotRevision);
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
mutating = false;
|
|
266
|
+
}
|
|
267
|
+
return token;
|
|
268
|
+
};
|
|
269
|
+
const publishBatch = (updates) => {
|
|
270
|
+
assertMutable();
|
|
271
|
+
if (!Array.isArray(updates)) {
|
|
272
|
+
throw new TypeError("coordination: publishBatch updates must be an array.");
|
|
273
|
+
}
|
|
274
|
+
const prepared = [];
|
|
275
|
+
const seen = new Set();
|
|
276
|
+
for (const update of updates) {
|
|
277
|
+
if (!isObject(update))
|
|
278
|
+
throw new TypeError("coordination: each update must be an object.");
|
|
279
|
+
const candidate = update;
|
|
280
|
+
if (seen.has(candidate.token))
|
|
281
|
+
fail("publishBatch contains a duplicate registration token.");
|
|
282
|
+
seen.add(candidate.token);
|
|
283
|
+
const registration = registrationFor(candidate.token);
|
|
284
|
+
const completed = copyCompleted(candidate.completed);
|
|
285
|
+
assertNoRegression(registration.completed, completed);
|
|
286
|
+
prepared.push({ registration, completed });
|
|
287
|
+
}
|
|
288
|
+
const changed = prepared.some(({ registration, completed }) => !registration.completed ||
|
|
289
|
+
!sameRevisions(registration.completed.revisions, completed.revisions));
|
|
290
|
+
if (!changed)
|
|
291
|
+
return snapshot;
|
|
292
|
+
const snapshotRevision = nextCounter(nextSnapshotRevision, "snapshot revision");
|
|
293
|
+
// Raw traits can expose individual mirror writes to Koota callbacks, but
|
|
294
|
+
// the committed projection changes only after every mirror write succeeds.
|
|
295
|
+
const preparedStates = prepared.map(({ registration, completed }) => ({
|
|
296
|
+
registration,
|
|
297
|
+
completed,
|
|
298
|
+
state: Object.freeze({
|
|
299
|
+
...registration.state,
|
|
300
|
+
geometryRevision: completed.revisions.geometry,
|
|
301
|
+
poseRevision: completed.revisions.pose,
|
|
302
|
+
transformRevision: completed.revisions.transform,
|
|
303
|
+
}),
|
|
304
|
+
}));
|
|
305
|
+
mutating = true;
|
|
306
|
+
try {
|
|
307
|
+
for (const { registration, state } of preparedStates) {
|
|
308
|
+
registration.entity.set(exports.CoordinationRegistration, state);
|
|
309
|
+
}
|
|
310
|
+
for (const { registration, completed } of preparedStates) {
|
|
311
|
+
registration.completed = completed;
|
|
312
|
+
}
|
|
313
|
+
return commit(snapshotRevision);
|
|
314
|
+
}
|
|
315
|
+
finally {
|
|
316
|
+
mutating = false;
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
const unregister = (token) => {
|
|
320
|
+
assertMutable();
|
|
321
|
+
const registration = registrationFor(token);
|
|
322
|
+
const snapshotRevision = nextCounter(nextSnapshotRevision, "snapshot revision");
|
|
323
|
+
mutating = true;
|
|
324
|
+
try {
|
|
325
|
+
registration.entity.remove(exports.CoordinationRegistration);
|
|
326
|
+
registrationsById.delete(registration.id);
|
|
327
|
+
registrationsByToken.delete(token);
|
|
328
|
+
return commit(snapshotRevision);
|
|
329
|
+
}
|
|
330
|
+
finally {
|
|
331
|
+
mutating = false;
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
const prune = () => {
|
|
335
|
+
assertActive();
|
|
336
|
+
const stale = [...registrationsById.values()].filter((registration) => !isCurrent(registration));
|
|
337
|
+
if (stale.length === 0)
|
|
338
|
+
return snapshot;
|
|
339
|
+
const snapshotRevision = nextCounter(nextSnapshotRevision, "snapshot revision");
|
|
340
|
+
mutating = true;
|
|
341
|
+
try {
|
|
342
|
+
for (const registration of stale) {
|
|
343
|
+
registrationsById.delete(registration.id);
|
|
344
|
+
registrationsByToken.delete(registration.token);
|
|
345
|
+
}
|
|
346
|
+
return commit(snapshotRevision);
|
|
347
|
+
}
|
|
348
|
+
finally {
|
|
349
|
+
mutating = false;
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
const getEntity = (id) => {
|
|
353
|
+
const registration = registrationsById.get(id);
|
|
354
|
+
return registration && isCurrent(registration) ? registration.entity : undefined;
|
|
355
|
+
};
|
|
356
|
+
const getRegistration = (id) => {
|
|
357
|
+
const registration = registrationsById.get(id);
|
|
358
|
+
return registration && isCurrent(registration) ? registration.token : undefined;
|
|
359
|
+
};
|
|
360
|
+
const getSnapshot = () => snapshot;
|
|
361
|
+
const subscribe = (listener) => {
|
|
362
|
+
if (disposed)
|
|
363
|
+
fail("the binding has been disposed.");
|
|
364
|
+
if (typeof listener !== "function")
|
|
365
|
+
throw new TypeError("coordination: listener must be a function.");
|
|
366
|
+
listeners.add(listener);
|
|
367
|
+
return () => listeners.delete(listener);
|
|
368
|
+
};
|
|
369
|
+
const dispose = () => {
|
|
370
|
+
if (disposed)
|
|
371
|
+
return;
|
|
372
|
+
assertActive();
|
|
373
|
+
const snapshotRevision = nextCounter(nextSnapshotRevision, "snapshot revision");
|
|
374
|
+
mutating = true;
|
|
375
|
+
try {
|
|
376
|
+
listeners.clear();
|
|
377
|
+
for (const registration of registrationsById.values()) {
|
|
378
|
+
if (isCurrent(registration))
|
|
379
|
+
registration.entity.remove(exports.CoordinationRegistration);
|
|
380
|
+
}
|
|
381
|
+
registrationsById.clear();
|
|
382
|
+
registrationsByToken.clear();
|
|
383
|
+
const markerState = world.get(exports.CoordinationBinding);
|
|
384
|
+
if (world.isInitialized && markerState?.markers.includes(marker)) {
|
|
385
|
+
const remaining = markerState.markers.filter((candidate) => candidate !== marker);
|
|
386
|
+
if (remaining.length === 0)
|
|
387
|
+
world.remove(exports.CoordinationBinding);
|
|
388
|
+
else
|
|
389
|
+
world.set(exports.CoordinationBinding, { markers: remaining });
|
|
390
|
+
}
|
|
391
|
+
nextSnapshotRevision = snapshotRevision;
|
|
392
|
+
snapshot = makeSnapshot(nextSnapshotRevision, []);
|
|
393
|
+
disposed = true;
|
|
394
|
+
}
|
|
395
|
+
finally {
|
|
396
|
+
mutating = false;
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
return Object.freeze({
|
|
400
|
+
world,
|
|
401
|
+
register,
|
|
402
|
+
publish: (token, completed) => publishBatch([{ token, completed }]),
|
|
403
|
+
publishBatch,
|
|
404
|
+
unregister,
|
|
405
|
+
prune,
|
|
406
|
+
getRegistration,
|
|
407
|
+
getEntity,
|
|
408
|
+
getSnapshot,
|
|
409
|
+
subscribe,
|
|
410
|
+
dispose,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Borrowed-World coordination for completed model metadata.
|
|
3
|
+
*
|
|
4
|
+
* The caller owns the Koota World and its entities. This module only adds its
|
|
5
|
+
* own marker traits and exposes a committed, immutable projection. It does
|
|
6
|
+
* not create a World, run systems, serialize entities, or read geometry.
|
|
7
|
+
*/
|
|
8
|
+
import { type Entity, type World } from "koota";
|
|
9
|
+
type BindingMarker = object;
|
|
10
|
+
/**
|
|
11
|
+
* World-held ownership markers for live bindings. Each binding appends one
|
|
12
|
+
* opaque marker and removes only that marker at disposal, so no module-global
|
|
13
|
+
* registry or per-bind Koota trait allocation is needed.
|
|
14
|
+
*/
|
|
15
|
+
export declare const CoordinationBinding: import("koota").Trait<{
|
|
16
|
+
markers: () => BindingMarker[];
|
|
17
|
+
}>;
|
|
18
|
+
/**
|
|
19
|
+
* The only trait this module places on caller-owned model entities. Its
|
|
20
|
+
* fields are a diagnostic mirror of the binding's committed projection;
|
|
21
|
+
* callers must use `getSnapshot` for an immutable completed view.
|
|
22
|
+
*/
|
|
23
|
+
export declare const CoordinationRegistration: import("koota").Trait<{
|
|
24
|
+
marker: () => BindingMarker;
|
|
25
|
+
generation: number;
|
|
26
|
+
id: string;
|
|
27
|
+
geometryRevision: number;
|
|
28
|
+
poseRevision: number;
|
|
29
|
+
transformRevision: number;
|
|
30
|
+
}>;
|
|
31
|
+
declare const registrationTokenBrand: unique symbol;
|
|
32
|
+
/** An opaque capability token returned only by `Coordination.register`. */
|
|
33
|
+
export type RegistrationToken = {
|
|
34
|
+
readonly [registrationTokenBrand]: never;
|
|
35
|
+
};
|
|
36
|
+
export type CompletedRevisions = Readonly<{
|
|
37
|
+
geometry: number;
|
|
38
|
+
pose: number;
|
|
39
|
+
transform: number;
|
|
40
|
+
}>;
|
|
41
|
+
export type CompletedMetadata = Readonly<{
|
|
42
|
+
revisions: CompletedRevisions;
|
|
43
|
+
}>;
|
|
44
|
+
export type CoordinationDescriptor = Readonly<{
|
|
45
|
+
/** A non-empty caller-authored identity, never a packed Koota entity ID. */
|
|
46
|
+
id: string;
|
|
47
|
+
/** Stable, sorted capability labels. Change them by unregistering first. */
|
|
48
|
+
capabilities: readonly string[];
|
|
49
|
+
}>;
|
|
50
|
+
export type CoordinationEntry = Readonly<{
|
|
51
|
+
id: string;
|
|
52
|
+
capabilities: readonly string[];
|
|
53
|
+
/** Undefined until the producer has explicitly published completed facts. */
|
|
54
|
+
completed: CompletedMetadata | undefined;
|
|
55
|
+
}>;
|
|
56
|
+
export type CoordinationSnapshot = Readonly<{
|
|
57
|
+
/** A binding-local publication generation, not a producer content revision. */
|
|
58
|
+
revision: number;
|
|
59
|
+
/** Entries sorted lexically by stable caller-authored id. */
|
|
60
|
+
entries: readonly CoordinationEntry[];
|
|
61
|
+
}>;
|
|
62
|
+
export type CoordinationUpdate = Readonly<{
|
|
63
|
+
token: RegistrationToken;
|
|
64
|
+
completed: CompletedMetadata;
|
|
65
|
+
}>;
|
|
66
|
+
export type CoordinationListener = (snapshot: CoordinationSnapshot) => void;
|
|
67
|
+
export type Coordination = Readonly<{
|
|
68
|
+
readonly world: World;
|
|
69
|
+
register: (entity: Entity, descriptor: CoordinationDescriptor) => RegistrationToken;
|
|
70
|
+
publish: (token: RegistrationToken, completed: CompletedMetadata) => CoordinationSnapshot;
|
|
71
|
+
publishBatch: (updates: readonly CoordinationUpdate[]) => CoordinationSnapshot;
|
|
72
|
+
unregister: (token: RegistrationToken) => CoordinationSnapshot;
|
|
73
|
+
/** Remove registrations whose entity or owned trait is no longer current. */
|
|
74
|
+
prune: () => CoordinationSnapshot;
|
|
75
|
+
/** Recover a current token after a listener threw during registration. */
|
|
76
|
+
getRegistration: (id: string) => RegistrationToken | undefined;
|
|
77
|
+
/** Resolve a stable identity only while its registered entity remains live. */
|
|
78
|
+
getEntity: (id: string) => Entity | undefined;
|
|
79
|
+
getSnapshot: () => CoordinationSnapshot;
|
|
80
|
+
subscribe: (listener: CoordinationListener) => () => void;
|
|
81
|
+
/** Idempotently remove this module's traits; never destroys the borrowed World. */
|
|
82
|
+
dispose: () => void;
|
|
83
|
+
}>;
|
|
84
|
+
/**
|
|
85
|
+
* Bind to a caller-owned Koota World. A live binding writes its opaque marker
|
|
86
|
+
* onto that same World, without a hidden module-global World registry. Several
|
|
87
|
+
* bindings may coexist only while they register disjoint caller entities.
|
|
88
|
+
*/
|
|
89
|
+
export declare function bindCoordination(world: World): Coordination;
|
|
90
|
+
export {};
|
|
91
|
+
//# sourceMappingURL=coordination.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coordination.d.ts","sourceRoot":"","sources":["../../src/coordination.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAS,KAAK,MAAM,EAAE,KAAK,KAAK,EAAE,MAAM,OAAO,CAAC;AAEvD,KAAK,aAAa,GAAG,MAAM,CAAC;AAW5B;;;;GAIG;AACH,eAAO,MAAM,mBAAmB;mBAAgC,aAAa,EAAE;EAAG,CAAC;AAEnF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB;kBACb,aAAa;;;;;;EAMnC,CAAC;AAEH,OAAO,CAAC,MAAM,sBAAsB,EAAE,OAAO,MAAM,CAAC;AAEpD,2EAA2E;AAC3E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,CAAC,sBAAsB,CAAC,EAAE,KAAK,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,SAAS,EAAE,kBAAkB,CAAC;CAC/B,CAAC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,4EAA4E;IAC5E,EAAE,EAAE,MAAM,CAAC;IACX,4EAA4E;IAC5E,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC,CAAC,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,6EAA6E;IAC7E,SAAS,EAAE,iBAAiB,GAAG,SAAS,CAAC;CAC1C,CAAC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C,+EAA+E;IAC/E,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;CACvC,CAAC,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IACxC,KAAK,EAAE,iBAAiB,CAAC;IACzB,SAAS,EAAE,iBAAiB,CAAC;CAC9B,CAAC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,QAAQ,EAAE,oBAAoB,KAAK,IAAI,CAAC;AAE5E,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,sBAAsB,KAAK,iBAAiB,CAAC;IACpF,OAAO,EAAE,CAAC,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,iBAAiB,KAAK,oBAAoB,CAAC;IAC1F,YAAY,EAAE,CAAC,OAAO,EAAE,SAAS,kBAAkB,EAAE,KAAK,oBAAoB,CAAC;IAC/E,UAAU,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,oBAAoB,CAAC;IAC/D,6EAA6E;IAC7E,KAAK,EAAE,MAAM,oBAAoB,CAAC;IAClC,0EAA0E;IAC1E,eAAe,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,iBAAiB,GAAG,SAAS,CAAC;IAC/D,+EAA+E;IAC/E,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAC9C,WAAW,EAAE,MAAM,oBAAoB,CAAC;IACxC,SAAS,EAAE,CAAC,QAAQ,EAAE,oBAAoB,KAAK,MAAM,IAAI,CAAC;IAC1D,mFAAmF;IACnF,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC,CAAC;AAwHH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,GAAG,YAAY,CAgT3D"}
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// ./eventLog — scratch-backed publish/drain event logs with a peek seam for
|
|
14
14
|
// observers (harnesses, tests, HUDs)
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.defineTrait = exports.defineEventLog = exports.createSchedule = exports.MASTER_SEED_BYTES = exports.isMasterSeed = exports.deriveSeed = exports.createMasterSeed = exports.substream = exports.snapshotSubstreams = exports.snapshotStream = exports.snapshotLayers = exports.shuffle = exports.restoreSubstreams = exports.restoreStream = exports.restoreLayers = exports.pick = exports.nextU32 = exports.nextInt = exports.nextFloat = exports.getSubstream = exports.createSubstreams = exports.createRng = exports.chance = exports.trait = exports.snapshotWorld = exports.restoreWorldHeader = exports.relation = exports.destroySimWorld = exports.createSimWorld = exports.createActions = exports.advanceClock = void 0;
|
|
16
|
+
exports.CoordinationRegistration = exports.CoordinationBinding = exports.bindCoordination = exports.defineTrait = exports.defineEventLog = exports.createSchedule = exports.MASTER_SEED_BYTES = exports.isMasterSeed = exports.deriveSeed = exports.createMasterSeed = exports.substream = exports.snapshotSubstreams = exports.snapshotStream = exports.snapshotLayers = exports.shuffle = exports.restoreSubstreams = exports.restoreStream = exports.restoreLayers = exports.pick = exports.nextU32 = exports.nextInt = exports.nextFloat = exports.getSubstream = exports.createSubstreams = exports.createRng = exports.chance = exports.trait = exports.snapshotWorld = exports.restoreWorldHeader = exports.relation = exports.destroySimWorld = exports.createSimWorld = exports.createActions = exports.advanceClock = void 0;
|
|
17
17
|
var world_js_1 = require("./world.cjs");
|
|
18
18
|
Object.defineProperty(exports, "advanceClock", { enumerable: true, get: function () { return world_js_1.advanceClock; } });
|
|
19
19
|
Object.defineProperty(exports, "createActions", { enumerable: true, get: function () { return world_js_1.createActions; } });
|
|
@@ -51,3 +51,7 @@ var eventLog_js_1 = require("./eventLog.cjs");
|
|
|
51
51
|
Object.defineProperty(exports, "defineEventLog", { enumerable: true, get: function () { return eventLog_js_1.defineEventLog; } });
|
|
52
52
|
var index_js_1 = require("./traits/index.cjs");
|
|
53
53
|
Object.defineProperty(exports, "defineTrait", { enumerable: true, get: function () { return index_js_1.defineTrait; } });
|
|
54
|
+
var coordination_js_1 = require("./coordination.cjs");
|
|
55
|
+
Object.defineProperty(exports, "bindCoordination", { enumerable: true, get: function () { return coordination_js_1.bindCoordination; } });
|
|
56
|
+
Object.defineProperty(exports, "CoordinationBinding", { enumerable: true, get: function () { return coordination_js_1.CoordinationBinding; } });
|
|
57
|
+
Object.defineProperty(exports, "CoordinationRegistration", { enumerable: true, get: function () { return coordination_js_1.CoordinationRegistration; } });
|
package/dist/cjs/index.d.cts
CHANGED
|
@@ -10,4 +10,6 @@ export { defineEventLog } from "./eventLog.cjs";
|
|
|
10
10
|
export type { EventLog } from "./eventLog.cjs";
|
|
11
11
|
export { defineTrait } from "./traits/index.cjs";
|
|
12
12
|
export type { SafeSchema } from "./traits/index.cjs";
|
|
13
|
+
export { bindCoordination, CoordinationBinding, CoordinationRegistration, } from "./coordination.cjs";
|
|
14
|
+
export type { CompletedMetadata, CompletedRevisions, Coordination, CoordinationDescriptor, CoordinationEntry, CoordinationListener, CoordinationSnapshot, CoordinationUpdate, RegistrationToken, } from "./coordination.cjs";
|
|
13
15
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/cjs/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAcA,OAAO,EACL,YAAY,EACZ,aAAa,EACb,cAAc,EACd,eAAe,EACf,QAAQ,EACR,kBAAkB,EAClB,aAAa,EACb,KAAK,GACN,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE5E,OAAO,EACL,MAAM,EACN,SAAS,EACT,gBAAgB,EAChB,YAAY,EACZ,SAAS,EACT,OAAO,EACP,OAAO,EACP,IAAI,EACJ,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,OAAO,EACP,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,SAAS,GACV,MAAM,UAAU,CAAC;AAClB,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,QAAQ,EACR,SAAS,EACT,iBAAiB,EACjB,UAAU,EACV,kBAAkB,GACnB,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC1F,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAEzD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvE,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAcA,OAAO,EACL,YAAY,EACZ,aAAa,EACb,cAAc,EACd,eAAe,EACf,QAAQ,EACR,kBAAkB,EAClB,aAAa,EACb,KAAK,GACN,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE5E,OAAO,EACL,MAAM,EACN,SAAS,EACT,gBAAgB,EAChB,YAAY,EACZ,SAAS,EACT,OAAO,EACP,OAAO,EACP,IAAI,EACJ,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,OAAO,EACP,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,SAAS,GACV,MAAM,UAAU,CAAC;AAClB,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,QAAQ,EACR,SAAS,EACT,iBAAiB,EACjB,UAAU,EACV,kBAAkB,GACnB,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC1F,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAEzD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvE,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,EACZ,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,mBAAmB,CAAC"}
|