@oimdb/core 4.1.0 → 4.2.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 +47 -0
- package/README.md +164 -161
- package/dist/index.cjs +926 -722
- package/dist/index.d.cts +206 -137
- package/dist/index.d.ts +206 -137
- package/dist/index.js +916 -716
- package/llms.txt +314 -275
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,9 +60,9 @@ still on CommonJS, which is why both are here.
|
|
|
60
60
|
throws.** `src/index.ts` registers its copy under `Symbol.for('@oimdb/core')` on
|
|
61
61
|
`globalThis`, and a second copy that finds another one there throws
|
|
62
62
|
`@oimdb/core is loaded twice in this process`. Without this check the split would be silent:
|
|
63
|
-
each copy keeps its own `OIMDefaults
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
each copy keeps its own built-in defaults and its own `OIMDefaults` class, so an `OIMDefaults` built
|
|
64
|
+
with one copy's class is a stranger to the other copy's factories, and two services built by the two copies
|
|
65
|
+
share no default instance. Objects built by
|
|
66
66
|
either copy carry their own methods, so a channel, a service and an effect
|
|
67
67
|
from different copies still deliver to each other; only the defaults diverge.
|
|
68
68
|
|
|
@@ -75,7 +75,7 @@ first sentence on its own points at the one thing that is not wrong.
|
|
|
75
75
|
|
|
76
76
|
The mechanism is one line: a runtime loads a package once per format, so
|
|
77
77
|
`import` in one file and `require` in another holds two complete copies of every
|
|
78
|
-
class and every module-level value, such as
|
|
78
|
+
class and every module-level value, such as `OIMDefaults.INSTANCE` or a React context.
|
|
79
79
|
|
|
80
80
|
The rule is one line too: pick `import` or `require` for `@oimdb/core`, and use the
|
|
81
81
|
same one everywhere in the process, including inside every `@oimdb` package
|
|
@@ -90,24 +90,36 @@ What each consumer ships, bundled with esbuild (minified, tree-shaken, ESM).
|
|
|
90
90
|
<!-- bundle-size:begin -->
|
|
91
91
|
| Consumer | Minified | Minified + gzip |
|
|
92
92
|
|---|---:|---:|
|
|
93
|
-
| Everything `@oimdb/core` exports |
|
|
94
|
-
| One entity store slot: `OIMDB`, `createOIMSlotCollectionService`, `createOIMSlotContentEntityService`, `writeSlotContentByKey`, `getSlotByKey` |
|
|
95
|
-
| The same, plus one effect and `db.flush()` |
|
|
96
|
-
| The same, plus one index: `createOIMSlotCollectionIndexService` |
|
|
97
|
-
| An ordered bucket with commands: `OIMSlotContentPositionalArrayBucketService`, `OIMBucketCommands` |
|
|
93
|
+
| Everything `@oimdb/core` exports | 102669 B | 24555 B |
|
|
94
|
+
| One entity store slot: `OIMDB`, `createOIMSlotCollectionService`, `createOIMSlotContentEntityService`, `writeSlotContentByKey`, `getSlotByKey` | 35413 B | 9161 B |
|
|
95
|
+
| The same, plus one effect and `db.flush()` | 45582 B | 11543 B |
|
|
96
|
+
| The same, plus one index: `createOIMSlotCollectionIndexService` | 65835 B | 16192 B |
|
|
97
|
+
| An ordered bucket with commands: `OIMSlotContentPositionalArrayBucketService`, `OIMBucketCommands` | 48469 B | 12117 B |
|
|
98
98
|
<!-- bundle-size:end -->
|
|
99
99
|
|
|
100
100
|
---
|
|
101
101
|
|
|
102
|
-
## The
|
|
102
|
+
## The domains
|
|
103
103
|
|
|
104
|
-
`src
|
|
104
|
+
`src/` holds one folder per domain and `index.ts`, the entire public surface; a factory lives in
|
|
105
|
+
the domain of what it builds. Where a file goes is in `packages/core/src/README.md`.
|
|
105
106
|
|
|
106
|
-
|
|
|
107
|
+
| Domain | Contents |
|
|
107
108
|
|---|---|
|
|
108
|
-
| `
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
109
|
+
| `slot/` | `OIMSlotOwner`, `OIMSlotOwnerService` (type-only export), `OIMSlotCollectionService`, `OIMWritableSlotCollectionService`, `OIMSingleSlotService`, `OIMWritableSingleSlotService`, `OIMSlotContentChangeListenerService`, `createOIMSlotCollectionService`, `createOIMSingleSlotService`, `TOIMSlot`, `TOIMEditable` |
|
|
110
|
+
| `store/` | `TOIMStoreSlot`, the slot store services |
|
|
111
|
+
| `channel/` | `OIMChannelService`, `TOIMChannelSlot` |
|
|
112
|
+
| `effect/` | `OIMEffectService`, `TOIMEffect` |
|
|
113
|
+
| `computed/` | `OIMComputedValueService`, `OIMComputedService`, `OIMSingleComputedService`, `OIMSortedComputedService`, `OIMBucketMemoComputedService`, the source-tracking computed services, their factories, `TOIMComputedSlot` |
|
|
114
|
+
| `entity/` | `OIMSlotContentEntityService`, `createOIMSlotContentEntityService`, `createOIMInPlaceEntityWriter`, `createOIMReplacingEntityWriter` |
|
|
115
|
+
| `column/` | abstract `OIMColumnSlotOwner` with `OIMPushColumnSlotOwner` and `OIMPullColumnSlotOwner`, `OIMColumnSlotService` |
|
|
116
|
+
| `bucket/` | `OIMSlotContentBucketService` (abstract), `OIMSlotContentSetBucketService`, `OIMSlotContentArrayBucketService` (abstract), `OIMSlotContentPositionalArrayBucketService`, `OIMSlotContentSortedArrayBucketService`, `OIMBucketCommands` |
|
|
117
|
+
| `index/` | `OIMSlotCollectionIndexService`, `OIMSlotCollectionSortedArrayIndexService`, their factories, the index keys services |
|
|
118
|
+
| `key/` | the key tree walks |
|
|
119
|
+
| `collector/` | the release policies, `OIMSlotReleaseWheel`, `OIMSlotRefetchWheel`, `OIMUnusedSlotCollectorService` |
|
|
120
|
+
| `schedule/` | `OIMFlushSchedulerService` |
|
|
121
|
+
| `error/` | the error policies |
|
|
122
|
+
| `db/` | `OIMDB`, `OIMDefaults`, `OIMDefaultValue`, the default constants |
|
|
111
123
|
|
|
112
124
|
**A service holds no data; a store slot is one instance.** A service is built once per
|
|
113
125
|
type, and `cardService.createStoreSlot(db)` returns a `TOIMStoreSlot`, a plain object whose
|
|
@@ -127,14 +139,14 @@ release, subscribe), never through the service.
|
|
|
127
139
|
## The shortest thing that delivers
|
|
128
140
|
|
|
129
141
|
```typescript setup
|
|
130
|
-
import { OIMBucketCommands, OIMChannelService, OIMDB, OIMDefaults, OIMEffectService, OIMLogAndContinueErrorPolicy, OIMSlotContentPositionalArrayBucketService, OIMBucketUniqueMemberSlotsArrayService, OIMSlotContentSetBucketService, OIMSlotAfterIdleReleasePolicy, OIMSlotAfterWriteReleasePolicy, OIMSlotEarliestReleasePolicy, createOIMSingleSlotService, createOIMSingleComputedService, OIMSlotMapStoreService, OIMSingleSlotStoreService, OIMSlotTreeStoreService, OIMUnusedSlotCollectorService, createOIMUnusedSlotCollectorSlot, oimInsertEntityWithCopy, oimIsSlotReleased, createOIMBucketMemoComputedService, createOIMComputedService, createOIMSortedComputedService,
|
|
142
|
+
import { OIMBucketCommands, OIMChannelService, OIMDB, OIMDefaults, OIMEffectService, OIMLogAndContinueErrorPolicy, OIMSlotContentPositionalArrayBucketService, OIMBucketUniqueMemberSlotsArrayService, OIMSlotContentSetBucketService, OIMSlotAfterIdleReleasePolicy, OIMSlotAfterWriteReleasePolicy, OIMSlotEarliestReleasePolicy, createOIMSingleSlotService, createOIMSingleComputedService, OIMSlotMapStoreService, OIMSingleSlotStoreService, OIMSlotTreeStoreService, OIMUnusedSlotCollectorService, createOIMUnusedSlotCollectorSlot, oimInsertEntityWithCopy, oimIsSlotReleased, createOIMBucketMemoComputedService, createOIMComputedService, createOIMSortedComputedService, createOIMInPlaceEntityWriter, createOIMSlotCollectionIndexService, createOIMSlotCollectionSortedArrayIndexService, createOIMSlotCollectionService, createOIMSlotContentEntityService, oimOrderedListCommandsOfTransition, oimSameSlotSequence, oimSameValueZeroGuard, type TOIMComputedSlot, type TOIMBucketSlotsArrayStore, type TOIMBucketSlot, type TOIMBucketSlotsSetStore, type TOIMDisposer, type TOIMEntityColumnName, type TOIMEntitySlot, type TOIMOrderedListCommand, type TOIMSingleSlotStore, type TOIMSlotStoreTree, type TOIMStoreKey, createOIMReplacingEntityWriter, OIMScalarKeyTreeWalk, OIMCompositeKeyTreeWalk } from '@oimdb/core';
|
|
131
143
|
|
|
132
144
|
type Card = { id: string; deck: string; title: string; rank: number };
|
|
133
145
|
type CardSlot = TOIMEntitySlot<Card>;
|
|
134
146
|
|
|
135
147
|
const db = new OIMDB();
|
|
136
148
|
const effectService = OIMEffectService.INSTANCE;
|
|
137
|
-
const cardService = createOIMSlotCollectionService(
|
|
149
|
+
const cardService = createOIMSlotCollectionService({ slotContentService: createOIMSlotContentEntityService<Card>() });
|
|
138
150
|
const cards = cardService.createStoreSlot(db);
|
|
139
151
|
const slot = cardService.getSlotByKey(cards, 'c1');
|
|
140
152
|
const byRank = (leftBucketMemberSlot: TOIMEntitySlot<Card>, rightBucketMemberSlot: TOIMEntitySlot<Card>): number =>
|
|
@@ -145,9 +157,9 @@ const byRank = (leftBucketMemberSlot: TOIMEntitySlot<Card>, rightBucketMemberSlo
|
|
|
145
157
|
cardService.writeSlotContentByKey(cards, 'c1', { id: 'c1', deck: 'spades', title: 'Ace', rank: 1 });
|
|
146
158
|
|
|
147
159
|
let seen = '';
|
|
148
|
-
const effect = effectService.createEffect(db,
|
|
160
|
+
const effect = effectService.createEffect(db, () => {
|
|
149
161
|
seen = slot.entity?.title ?? '';
|
|
150
|
-
});
|
|
162
|
+
}, [slot]);
|
|
151
163
|
|
|
152
164
|
cardService.slotContentService.updateEntity(slot, { title: 'Ace of Spades' });
|
|
153
165
|
db.flush(); // seen === 'Ace of Spades'
|
|
@@ -192,15 +204,15 @@ take no `db`: a computed reaches its channel through the store slot it derives f
|
|
|
192
204
|
the `db` handed to `createComputedStoreSlot`.
|
|
193
205
|
|
|
194
206
|
```typescript signature
|
|
195
|
-
createOIMSlotContentEntityService<Card, TKey?, TColumnName?>(
|
|
207
|
+
createOIMSlotContentEntityService<Card, TKey?, TColumnName?>({ entityWriter?, slotEmptinessPolicy? }?)
|
|
196
208
|
new OIMSlotContentSetBucketService<TBucketMemberSlot, TKey?>({ bucketCommands? })
|
|
197
209
|
new OIMSlotContentPositionalArrayBucketService<TBucketMemberSlot, TKey?>({ bucketCommands?, bucketMemberSlotsArrayService? })
|
|
198
|
-
createOIMSlotCollectionService(
|
|
199
|
-
createOIMSingleSlotService(
|
|
200
|
-
createOIMSlotCollectionIndexService(
|
|
201
|
-
createOIMSlotCollectionSortedArrayIndexService(
|
|
202
|
-
createOIMComputedService<TKey, TValue, TComputedStore?>({ createSources?, derive?, sameValueGuard?, initialValue?, slotReleasePolicy?, computedSlotStoreService?, columnSlotStoreService? | columnNames? }) // a non-Map store: computedSlotStoreService required
|
|
203
|
-
createOIMSingleComputedService<TValue>({ createSources?, derive?, sameValueGuard?, initialValue?, slotReleasePolicy?, columnSlotStoreService? | columnNames? })
|
|
210
|
+
createOIMSlotCollectionService({ slotContentService, slotStoreService?, slotReleasePolicy?, slotSubscriptionListener?, columnSlotStoreService? | columnNames? })
|
|
211
|
+
createOIMSingleSlotService({ slotContentService, slotReleasePolicy?, slotSubscriptionListener?, columnSlotStoreService? | columnNames? })
|
|
212
|
+
createOIMSlotCollectionIndexService({ entityService, columns | keyOf, slotContentService?, bucketSlotStoreService?, slotReleasePolicy?, columnSlotStoreService? | columnNames? })
|
|
213
|
+
createOIMSlotCollectionSortedArrayIndexService({ entityService, sortColumns, columns | keyOf, bucketSlotStoreService?, slotReleasePolicy?, columnSlotStoreService? | columnNames? })
|
|
214
|
+
createOIMComputedService<TKey, TValue, TComputedStore?>({ createSources?, derive?, sameValueGuard?, releaseValue?, initialValue?, slotReleasePolicy?, computedSlotStoreService?, columnSlotStoreService? | columnNames? }) // a non-Map store: computedSlotStoreService required
|
|
215
|
+
createOIMSingleComputedService<TValue>({ createSources?, derive?, sameValueGuard?, releaseValue?, initialValue?, slotReleasePolicy?, columnSlotStoreService? | columnNames? })
|
|
204
216
|
createOIMSortedComputedService({ indexService, sortColumns?, comparator?, slotReleasePolicy?, computedSlotStoreService?, columnSlotStoreService? })
|
|
205
217
|
createOIMBucketMemoComputedService({ bucketCollectionService, slotReleasePolicy?, computedSlotStoreService?, columnSlotStoreService? })
|
|
206
218
|
|
|
@@ -213,11 +225,11 @@ computedService.createComputedStoreSlot(db, derive?, createSources?)
|
|
|
213
225
|
|
|
214
226
|
**The content service is where everything per type is fixed, once:** the entity writer and the emptiness rule.
|
|
215
227
|
The collection service adds the store service, the release policy and the column options, and builds the slot
|
|
216
|
-
owner from them and `
|
|
228
|
+
owner from them and `defaults.slotStamp`. Build the content service first when it is shared:
|
|
217
229
|
|
|
218
230
|
```typescript
|
|
219
|
-
const cardContentService = createOIMSlotContentEntityService<Card>(
|
|
220
|
-
const sameShapeCardService = createOIMSlotCollectionService(
|
|
231
|
+
const cardContentService = createOIMSlotContentEntityService<Card>();
|
|
232
|
+
const sameShapeCardService = createOIMSlotCollectionService({ slotContentService: cardContentService });
|
|
221
233
|
const sameShapeCards = sameShapeCardService.createStoreSlot(db);
|
|
222
234
|
```
|
|
223
235
|
|
|
@@ -233,7 +245,7 @@ reaches for a default of its own.
|
|
|
233
245
|
The doors are free functions, not methods on `OIMDB`, so a consumer who wires
|
|
234
246
|
nothing does not import the bucket machinery. This is pinned by
|
|
235
247
|
`tests/what-an-import-retains.test.ts` (the root retains no door) and by
|
|
236
|
-
`src/
|
|
248
|
+
`src/db/tests/which-answer-a-collection-the-root-mints-gets.test.ts`.
|
|
237
249
|
|
|
238
250
|
**A bucket's content service decides how it holds its slots.** `createOIMSlotCollectionService` takes it
|
|
239
251
|
as `slotContentService`. With none, it builds an `OIMSlotContentPositionalArrayBucketService`
|
|
@@ -267,7 +279,7 @@ There is no argument that turns delivery off. A store slot is created on a `db`
|
|
|
267
279
|
unused-slot collector a read hands its mint to is that channel slot's `collectorSlot`.
|
|
268
280
|
|
|
269
281
|
```typescript
|
|
270
|
-
const deliveringCardService = createOIMSlotCollectionService(
|
|
282
|
+
const deliveringCardService = createOIMSlotCollectionService({ slotContentService: createOIMSlotContentEntityService<Card>(), slotStoreService: new OIMSlotMapStoreService<TOIMEntitySlot<Card, TOIMStoreKey>>() });
|
|
271
283
|
const deliveringCards = deliveringCardService.createStoreSlot(db);
|
|
272
284
|
```
|
|
273
285
|
|
|
@@ -277,11 +289,11 @@ and that constructor builds the type's `OIMSlotOwnerService`. The owner asks the
|
|
|
277
289
|
through `IOIMSlotContentService` (`isSlotEmpty`, `releaseSlotContent`, `hasReference`); the collection
|
|
278
290
|
service mints through the content service's `createSlot`.
|
|
279
291
|
|
|
280
|
-
`keyOf` is the one required option. `entityWriter` falls back to `
|
|
281
|
-
|
|
282
|
-
`slotReleasePolicy` to `
|
|
283
|
-
with a value stays for good" described under **The injected seams**; the column store
|
|
284
|
-
`
|
|
292
|
+
`keyOf` is the one required option. `entityWriter` falls back to `defaults.entityWriter`, then to
|
|
293
|
+
the in-place writer; `slotEmptinessPolicy` to `defaults.entitySlotEmptinessPolicy`, then to "the entity
|
|
294
|
+
is `undefined`"; `slotReleasePolicy` to `defaults.slotReleasePolicy`, then to nothing, which is the
|
|
295
|
+
default of "a slot with a value stays for good" described under **The injected seams**; the column store
|
|
296
|
+
to `defaults.columnSlotStoreService`, then to a shared map-driven store.
|
|
285
297
|
|
|
286
298
|
A single entity is the same content service under a `void` key, built by
|
|
287
299
|
`createOIMSingleSlotService`, and it reports the same way.
|
|
@@ -290,38 +302,34 @@ A single entity is the same content service under a `void` key, built by
|
|
|
290
302
|
|
|
291
303
|
## Every optional dependency has a default
|
|
292
304
|
|
|
293
|
-
`OIMDefaults`
|
|
294
|
-
|
|
305
|
+
`OIMDefaults` holds the strategies a factory falls back on when its own options leave them out. Build
|
|
306
|
+
one with the strategies you want shared and pass it as the `defaults` option of every factory that
|
|
307
|
+
should use them; a factory given none uses `OIMDefaults.INSTANCE`, which is empty. A field left
|
|
308
|
+
undefined falls back to the built-in default.
|
|
295
309
|
|
|
296
|
-
|
|
|
310
|
+
| Field | Type | Built-in default |
|
|
297
311
|
|---|---|---|
|
|
298
|
-
| `
|
|
299
|
-
| `
|
|
300
|
-
| `
|
|
301
|
-
| `
|
|
302
|
-
| `
|
|
303
|
-
| `
|
|
304
|
-
| `
|
|
305
|
-
| `
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
Setting a default before the first read works, and so does setting it twice before then. Each
|
|
321
|
-
property is tracked on its own, so calling `OIMDefaults.COLUMN_SLOT_STORE_SERVICE.getValue()` does not close
|
|
322
|
-
`OIMDefaults.KEY_TREE_WALK_SCALAR`. Set every default you mean to replace at startup, before
|
|
323
|
-
any `OIMDB` or service exists.
|
|
324
|
-
`src/factories/tests/what-a-default-replaced-too-late-does.test.ts` pins this.
|
|
312
|
+
| `slotStamp` | `IOIMSlotStamp<TOIMSlot>` | none: no stamp |
|
|
313
|
+
| `slotReleasePolicy` | `IOIMSlotReleasePolicy` | none: a slot with a value stays for good |
|
|
314
|
+
| `entityWriter` | `IOIMEntityWriter` | `createOIMInPlaceEntityWriter()` |
|
|
315
|
+
| `entitySlotEmptinessPolicy` | `IOIMSlotEmptinessPolicy<TOIMEntityContentSlot>` | "empty means `slot.entity === undefined`" |
|
|
316
|
+
| `indexKeysService` | `IOIMIndexKeysService` | an `OIMAppendingIndexKeysService`; see [Index keys strategy](#index-keys-strategy) |
|
|
317
|
+
| `columnSlotStoreService` | `IOIMSlotStoreService<string, TOIMColumnSlot>` | a shared map-driven store service for column slots |
|
|
318
|
+
| `computedColumnSlotStoreService` | `IOIMSlotStoreService<string, TOIMComputedColumnSlot>` | a shared map-driven store service for the column slots of computed services |
|
|
319
|
+
| `storeSlotListener` | `IOIMStoreSlotListener` | `OIMNoStoreSlotListener`, which hears nothing; a service calls `onStoreSlotCreated(storeSlot)` for each store slot it creates |
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
const replacingDefaults = new OIMDefaults({ entityWriter: createOIMReplacingEntityWriter() });
|
|
323
|
+
const replacingCardService = createOIMSlotCollectionService({
|
|
324
|
+
defaults: replacingDefaults,
|
|
325
|
+
slotContentService: createOIMSlotContentEntityService<Card>({ defaults: replacingDefaults }),
|
|
326
|
+
});
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**The built-in defaults cost nothing until used.** Each is an `OIMDefaultValue` whose `getValue()`
|
|
330
|
+
builds it on its first call and hands back the same instance for the rest of the process, and
|
|
331
|
+
`OIMDefaults` itself imports none of them, so a bundle carries only the defaults its factories read.
|
|
332
|
+
`src/db/tests/which-answer-a-collection-the-root-mints-gets.test.ts` pins which door reads which.
|
|
325
333
|
|
|
326
334
|
**One module names every default, so calling one `getValue()` retains all of them.** A
|
|
327
335
|
service drags the map-driven store service and both key tree walks into a
|
|
@@ -346,7 +354,7 @@ const body = (): void => {};
|
|
|
346
354
|
cardService.writeSlotContentByKey(cards, card.id, card); // write: the value is the reference
|
|
347
355
|
cardService.writeSlotContentByKey(cards, key, card); // write: the same door, by key
|
|
348
356
|
const entitySlot = cardService.holdSlotByKey(cards, key); // hold: a counted reference; give it back with entitySlot.owner.unholdSlot(entitySlot)
|
|
349
|
-
effectService.createEffect(db,
|
|
357
|
+
effectService.createEffect(db, body, [entitySlot]); // subscribe: the effect is the reference
|
|
350
358
|
const rankColumn = cardService.columnSlotService.holdColumn(entitySlot, 'rank'); // hold, on a column
|
|
351
359
|
cardService.columnSlotService.unholdColumn(rankColumn); // give the column back
|
|
352
360
|
cardService.getSlotByKey(cards, key); // read: the collector's hold is the reference
|
|
@@ -446,7 +454,7 @@ about a slot through `IOIMSlotContentService`, never through an override:
|
|
|
446
454
|
|---|---|
|
|
447
455
|
| `createSlot(key, storeSlot, slotOwner)` / `createSingleSlot(storeSlot, slotOwner)` | a key (or a single) is minted, by the collection service |
|
|
448
456
|
| `isSlotEmpty(slot)` | emptiness is settled after a write |
|
|
449
|
-
| `releaseSlotContent(slot
|
|
457
|
+
| `releaseSlotContent(slot)` | the identity is going. Give back what its content held. Throws on a held slot |
|
|
450
458
|
| `hasReference(slot)` | something outside `holdCount` and `effects` may keep it |
|
|
451
459
|
|
|
452
460
|
The content service's write doors reach the owner through `slot.owner`; the owner files and unfiles
|
|
@@ -457,12 +465,12 @@ through the store service.
|
|
|
457
465
|
```typescript signature
|
|
458
466
|
interface IOIMSlotEmptinessPolicy<TSlot> { isEmpty(slot: TSlot): boolean; }
|
|
459
467
|
|
|
460
|
-
interface IOIMSlotStamp<TSlot> { stamp(
|
|
468
|
+
interface IOIMSlotStamp<TSlot> { stamp(editableSlot: TOIMEditable<TSlot>): void; }
|
|
461
469
|
```
|
|
462
470
|
|
|
463
471
|
**`IOIMSlotEmptinessPolicy`** decides what counts as empty. The answer is written once
|
|
464
472
|
per write into the EMPTY flag of `slot.slotFlags` and never recomputed at a read. An entity content
|
|
465
|
-
service defaults to "the entity is `undefined`" (`
|
|
473
|
+
service defaults to "the entity is `undefined`" (or `defaults.entitySlotEmptinessPolicy`); a bucket
|
|
466
474
|
content service answers "`getBucketMemberSlotCount(bucketSlot) === 0`"; a computed answers
|
|
467
475
|
`false`.
|
|
468
476
|
|
|
@@ -476,8 +484,8 @@ settled, and it chains: each stamp takes the `next` one and calls it first. Use
|
|
|
476
484
|
for stamps that have nothing to do with release; release has its own policy.
|
|
477
485
|
|
|
478
486
|
**`IOIMSlotReleasePolicy`** decides whether and when a slot goes, and does no
|
|
479
|
-
work itself. A door and `
|
|
480
|
-
`
|
|
487
|
+
work itself. A door and an `OIMDefaults` take ONE through `slotReleasePolicy`; a door given none takes
|
|
488
|
+
`defaults.slotReleasePolicy`. With neither, the slot owner is built with `OIMSlotAtEmptyReleasePolicy`:
|
|
481
489
|
a slot with a value stays for good. A computed service defaults to
|
|
482
490
|
`OIMSlotAtUnholdReleasePolicy`. Two readonly flags on the policy, `releasesWrittenSlots` and
|
|
483
491
|
`schedulesReleaseOnWrite`, are read once by the owner at construction, so a policy that never
|
|
@@ -496,7 +504,7 @@ A slot goes on the SOONEST of those that are set, and a hold outranks them all.
|
|
|
496
504
|
**One number cannot carry both.** A write arriving after the subscriber left
|
|
497
505
|
would overwrite the idle policy's verdict, and the last subscriber leaving after a
|
|
498
506
|
write would overwrite the write policy's. Both directions are pinned by
|
|
499
|
-
`src/
|
|
507
|
+
`src/collector/tests/which-release-marker-each-collector-may-move.test.ts`.
|
|
500
508
|
|
|
501
509
|
**The POLICY answers `isSlotReleasable(slot)`**, and only for a slot that is not
|
|
502
510
|
empty and has nothing left keeping it (no holds, no references). `true` releases
|
|
@@ -554,7 +562,7 @@ leaves at the end of the frame. A window above zero is filed in that policy's ow
|
|
|
554
562
|
queue.
|
|
555
563
|
|
|
556
564
|
```typescript
|
|
557
|
-
const
|
|
565
|
+
const releasingDefaults = new OIMDefaults({
|
|
558
566
|
slotReleasePolicy: new OIMSlotEarliestReleasePolicy([
|
|
559
567
|
new OIMSlotAfterWriteReleasePolicy({ keepAfterWriteMs: 60_000 }),
|
|
560
568
|
new OIMSlotAfterIdleReleasePolicy({ keepAfterIdleMs: 30_000 }),
|
|
@@ -612,7 +620,7 @@ does not re-file it. Two consequences follow:
|
|
|
612
620
|
that those entities' own expired windows already answer for, so the entities go in the
|
|
613
621
|
same sweep.
|
|
614
622
|
|
|
615
|
-
Pinned by `src/
|
|
623
|
+
Pinned by `src/collector/tests/what-a-stale-index-takes-with-it.test.ts`,
|
|
616
624
|
`…/what-an-identity-nobody-subscribed-to-costs-after-a-while.test.ts` and
|
|
617
625
|
`…/what-a-subscribed-row-costs-the-release-clock.test.ts`.
|
|
618
626
|
|
|
@@ -703,7 +711,7 @@ gives them back when released, so the arena holds the top and the chain holds
|
|
|
703
711
|
itself. Use one arena per screen, per component or per job: a scope's cache pins what
|
|
704
712
|
that scope read and nothing else.
|
|
705
713
|
|
|
706
|
-
Pinned by `src/
|
|
714
|
+
Pinned by `src/collector/tests/what-a-named-arena-keeps-cached.test.ts`.
|
|
707
715
|
|
|
708
716
|
Neither policy means *unread*: no read is reported to the clock. A subscriber
|
|
709
717
|
never loses data mid-read, because an effect holds every root it reaches, so the
|
|
@@ -729,8 +737,8 @@ a store slot that holds one slot under no key, or many, a store slot that files
|
|
|
729
737
|
|
|
730
738
|
| kind | single | collection |
|
|
731
739
|
|---|---|---|
|
|
732
|
-
| entity | `createOIMSingleSlotService<TEntity>(
|
|
733
|
-
| bucket | `createOIMSingleSlotService<TMember>(
|
|
740
|
+
| entity | `createOIMSingleSlotService<TEntity>(…)`, then `createStoreSlot(db)` | `createOIMSlotCollectionService<TEntity>({ keyOf, … })`, then `createStoreSlot(db)` |
|
|
741
|
+
| bucket | `createOIMSingleSlotService<TMember>({ slotContentService?, … })`, then `createStoreSlot(db)` | `createOIMSlotCollectionService<TMember>({ slotContentService?, … })`, then `createStoreSlot(db)` |
|
|
734
742
|
| computed | `createOIMComputedService<void, TValue, TOIMSingleSlotStore<TOIMComputedSlot<TValue, void>>>({ computedSlotStoreService: new OIMSingleSlotStoreService<…>(), … })`, then `createComputedStoreSlot(db, derive?, createSources?)` | `createOIMComputedService<TKey, TValue>({ … })`, then `createComputedStoreSlot(db, derive?, createSources?)` |
|
|
735
743
|
|
|
736
744
|
A single is not a second content API. Its content service is the same class with `TKey = void`,
|
|
@@ -794,10 +802,10 @@ argument, not a subclass.
|
|
|
794
802
|
new OIMSlotMapStoreService<TOIMEntitySlot<Card, TOIMStoreKey>>()
|
|
795
803
|
|
|
796
804
|
// A composite key of VALUES, descended one composite key part at a time.
|
|
797
|
-
new OIMSlotTreeStoreService<TOIMStoreKey, TOIMKeyedSlot<readonly TOIMStoreKey[]>>(
|
|
805
|
+
new OIMSlotTreeStoreService<TOIMStoreKey, TOIMKeyedSlot<readonly TOIMStoreKey[]>>(OIMScalarKeyTreeWalk.INSTANCE)
|
|
798
806
|
|
|
799
807
|
// A composite key of IDENTITIES, descended the same way — and holding each.
|
|
800
|
-
new OIMSlotTreeStoreService<TOIMEntitySlot, TOIMKeyedSlot<readonly TOIMEntitySlot[]>>(
|
|
808
|
+
new OIMSlotTreeStoreService<TOIMEntitySlot, TOIMKeyedSlot<readonly TOIMEntitySlot[]>>(OIMCompositeKeyTreeWalk.INSTANCE)
|
|
801
809
|
```
|
|
802
810
|
|
|
803
811
|
There are two stores, one per shape of address, and no store is ever asked at run time
|
|
@@ -819,8 +827,8 @@ type TScalarChain = readonly TOIMStoreKey[];
|
|
|
819
827
|
type Tally = { deck: string; day: string; count: number };
|
|
820
828
|
type TTallyStore = TOIMSlotStoreTree<TOIMEntitySlot<Tally, TScalarChain>, TOIMStoreKey>;
|
|
821
829
|
|
|
822
|
-
const tallyService = createOIMSlotCollectionService(
|
|
823
|
-
|
|
830
|
+
const tallyService = createOIMSlotCollectionService({ slotContentService: createOIMSlotContentEntityService<Tally, TScalarChain>(), slotStoreService: new OIMSlotTreeStoreService<TOIMStoreKey, TOIMEntitySlot<Tally, TScalarChain>>(
|
|
831
|
+
OIMScalarKeyTreeWalk.INSTANCE,
|
|
824
832
|
) });
|
|
825
833
|
const tallies = tallyService.createStoreSlot(db);
|
|
826
834
|
tallyService.writeSlotContentByKey(tallies, ((t) => [t.deck, t.day])({ deck: 'spades', day: 'mon', count: 1 }), { deck: 'spades', day: 'mon', count: 1 });
|
|
@@ -842,17 +850,17 @@ type Enrolment = {
|
|
|
842
850
|
};
|
|
843
851
|
type TChain = readonly TOIMEntitySlot[];
|
|
844
852
|
|
|
845
|
-
const userService = createOIMSlotCollectionService(
|
|
853
|
+
const userService = createOIMSlotCollectionService({ slotContentService: createOIMSlotContentEntityService<User>() });
|
|
846
854
|
const users = userService.createStoreSlot(db);
|
|
847
|
-
const groupService = createOIMSlotCollectionService(
|
|
855
|
+
const groupService = createOIMSlotCollectionService({ slotContentService: createOIMSlotContentEntityService<Group>() });
|
|
848
856
|
const groups = groupService.createStoreSlot(db);
|
|
849
857
|
const alice = userService.writeSlotContentByKey(users, 'alice', { id: 'alice', name: 'Alice' });
|
|
850
858
|
const admins = groupService.writeSlotContentByKey(groups, 'admins', { id: 'admins', name: 'Admins' });
|
|
851
859
|
|
|
852
860
|
type TEnrolmentStore = TOIMSlotStoreTree<TOIMEntitySlot<Enrolment, TChain>, TOIMEntitySlot>;
|
|
853
861
|
|
|
854
|
-
const enrolmentService = createOIMSlotCollectionService(
|
|
855
|
-
|
|
862
|
+
const enrolmentService = createOIMSlotCollectionService({ slotContentService: createOIMSlotContentEntityService<Enrolment, TChain>(), slotStoreService: new OIMSlotTreeStoreService<TOIMEntitySlot, TOIMEntitySlot<Enrolment, TChain>>(
|
|
863
|
+
OIMCompositeKeyTreeWalk.INSTANCE,
|
|
856
864
|
) });
|
|
857
865
|
const enrolments = enrolmentService.createStoreSlot(db);
|
|
858
866
|
enrolmentService.writeSlotContentByKey(enrolments, ((m) => [m.user, m.group])({ user: alice, group: admins, since: 2026 }), { user: alice, group: admins, since: 2026 });
|
|
@@ -871,7 +879,7 @@ Both walks throw `an empty key is not a key` on `[]`.
|
|
|
871
879
|
**A scalar is not a composite key part of a composite-walk key.** It is a compile
|
|
872
880
|
error, and past a cast it throws inside the mint (`Cannot create property
|
|
873
881
|
'holdCount' on string`) with the edge already in the tree. Hand the store
|
|
874
|
-
`
|
|
882
|
+
`OIMScalarKeyTreeWalk.INSTANCE` instead of using a cast.
|
|
875
883
|
|
|
876
884
|
**Compare a chain with the strategy that matches it.** `oimSameSlotSequence` walks identities by
|
|
877
885
|
reference. Using it on a chain of values is the `NaN` defect: it answers
|
|
@@ -913,7 +921,7 @@ made once at construction and costs nothing per write:
|
|
|
913
921
|
| `oimInsertEntityWithSanitizedCopy` | a one-level copy with an own `__proto__` key removed | the same; pick it when you insert untrusted JSON (`JSON.parse` of other users' data) |
|
|
914
922
|
|
|
915
923
|
```typescript
|
|
916
|
-
const copiedCardService = createOIMSlotCollectionService(
|
|
924
|
+
const copiedCardService = createOIMSlotCollectionService({ slotContentService: createOIMSlotContentEntityService<Card>({ entityWriter: createOIMInPlaceEntityWriter({ entityInserter: oimInsertEntityWithCopy }) }) });
|
|
917
925
|
```
|
|
918
926
|
|
|
919
927
|
**A copy is one level deep, and no deeper.** A nested object or
|
|
@@ -948,7 +956,7 @@ cardService.slotContentService.markEntityChanged(slot, ['title']);
|
|
|
948
956
|
**The hand mutation needs no cast.** `TOIMEntitySlot` declares
|
|
949
957
|
`readonly entity: TEntity | undefined`, and `readonly` forbids replacing the
|
|
950
958
|
object, not writing its columns. You still have to narrow the `| undefined`.
|
|
951
|
-
`
|
|
959
|
+
`TOIMEditable<TSlot>` is the type that strips `readonly` from the field
|
|
952
960
|
itself, which is what assigning `slot.entity` needs.
|
|
953
961
|
|
|
954
962
|
**The list is the whole point, and leaving it out gives it back.** Named columns
|
|
@@ -1024,8 +1032,8 @@ interface IOIMEntityWriter {
|
|
|
1024
1032
|
readonly clearEntity: (slot: TOIMEntityContentSlot) => void;
|
|
1025
1033
|
}
|
|
1026
1034
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1035
|
+
createOIMInPlaceEntityWriter(options?) // the stored object keeps its identity
|
|
1036
|
+
createOIMReplacingEntityWriter(options?) // every write that changed something produces a new object
|
|
1029
1037
|
// TOIMEntityWriterOptions = { sameValueGuard?: TOIMSameValueGuard; entityInserter?: TOIMEntityInserter; putEntityColumns?: typeof oimPutEntityColumns }
|
|
1030
1038
|
```
|
|
1031
1039
|
|
|
@@ -1044,9 +1052,9 @@ A writer of your own can reuse the column walks that both shipped writers run,
|
|
|
1044
1052
|
`oimPatchEntityColumns`, `oimPatchEntityColumn` (one column, behind `patchColumn`) and
|
|
1045
1053
|
`oimPutEntityColumns`. The whole-entity put is itself the writers' `putEntityColumns` option:
|
|
1046
1054
|
`oimPutEntityColumns` is the default and skips neither `__proto__` keys nor inherited stored
|
|
1047
|
-
columns; `
|
|
1055
|
+
columns; `oimGuardedPutEntityColumns` keeps both guards, at a measured cost on every put, for
|
|
1048
1056
|
values that can carry a `__proto__` key or are class instances
|
|
1049
|
-
(`
|
|
1057
|
+
(`createOIMInPlaceEntityWriter({ putEntityColumns: oimGuardedPutEntityColumns })`). Each returns the object
|
|
1050
1058
|
`targetOf` handed back with the moved columns written into it, or `undefined`
|
|
1051
1059
|
when nothing moved, so the writer decides only where the result goes. The
|
|
1052
1060
|
[docs](https://oimdb.org/docs/core/writing#writing-your-own-writer) build one that
|
|
@@ -1058,7 +1066,7 @@ does not have are cleared to `undefined`, in one call, and the names it returns
|
|
|
1058
1066
|
only the columns the patch names. `writeSlotContentByKey` of a key that has a value and
|
|
1059
1067
|
`writeSlotContent(slot, entity)` are PUT, so a whole-entity write that drops a column writes
|
|
1060
1068
|
the slot to the channel once, and the replacing writer builds one new object, not
|
|
1061
|
-
two. This is counted in `src/
|
|
1069
|
+
two. This is counted in `src/entity/tests/what-a-put-that-drops-a-column-costs.test.ts`.
|
|
1062
1070
|
|
|
1063
1071
|
Neither writer `delete`s. A column that a PUT drops or `deleteColumns` takes away is
|
|
1064
1072
|
written `undefined`, into the stored object in place or into a spread copy when
|
|
@@ -1068,8 +1076,8 @@ replacing, so the entity keeps its hidden class: the key stays in
|
|
|
1068
1076
|
does not report it, and taking the entity away does not wake its readers.
|
|
1069
1077
|
|
|
1070
1078
|
The choice is made once, when the entity content service is built (`entityWriter`, else
|
|
1071
|
-
`
|
|
1072
|
-
is the answer. The default is `
|
|
1079
|
+
`defaults.entityWriter`), so there is no `if (inPlace)` anywhere on any write path; the object already
|
|
1080
|
+
is the answer. The default is `createOIMInPlaceEntityWriter()`. Pass `createOIMReplacingEntityWriter()` when
|
|
1073
1081
|
something downstream compares by reference.
|
|
1074
1082
|
|
|
1075
1083
|
---
|
|
@@ -1107,8 +1115,7 @@ the next read; a computed with no sources derives on its first read. A throw nev
|
|
|
1107
1115
|
for frames: the next write after an aborted frame asks for one. A throw in one
|
|
1108
1116
|
release of a sweep leaves the rest of that slice on the release clock for the next
|
|
1109
1117
|
sweep. A release that falls inside a flush (an effect body disposing another
|
|
1110
|
-
effect)
|
|
1111
|
-
flush is over. Code that keeps
|
|
1118
|
+
effect) happens at once: nothing holds the slot, so nothing hears it go. Code that keeps
|
|
1112
1119
|
derived state (an index's `keyOf`, an emptiness check) is outside the
|
|
1113
1120
|
policy: a throw there leaves state consistent and is thrown in either mode. An
|
|
1114
1121
|
index whose `keyOf` throws on an entity files that entity under no key, finishes the rest
|
|
@@ -1117,7 +1124,7 @@ of the write or batch, then rethrows; the next write it accepts files the entity
|
|
|
1117
1124
|
**Subscribing is building an effect.** There is no `subscribe` helper over this,
|
|
1118
1125
|
and there must not be one. An effect is stamped with the channel clock at birth,
|
|
1119
1126
|
so `effectService.createEffect` hears only what moves after it and never delivers a first
|
|
1120
|
-
value. `effectService.
|
|
1127
|
+
value. `effectService.createAndRunEffect(db, roots, computeds, body)` is the door that runs
|
|
1121
1128
|
the body once at creation. An effect is a plain `TOIMEffect` object with no methods (its
|
|
1122
1129
|
channel is `effect.channelSlot`); `OIMEffectService.INSTANCE` does everything to it. There is
|
|
1123
1130
|
one effect service for the whole process, and every door is handed the `db` (any
|
|
@@ -1125,14 +1132,14 @@ one effect service for the whole process, and every door is handed the `db` (any
|
|
|
1125
1132
|
|
|
1126
1133
|
```typescript
|
|
1127
1134
|
// Sources are named in two lists: root slots, and derivations.
|
|
1128
|
-
const effect = effectService.createEffect(db, [slot], [totalService.computedSingleSlotService.getSingleSlot(total)]
|
|
1135
|
+
const effect = effectService.createEffect(db, () => {}, [slot], [totalService.computedSingleSlotService.getSingleSlot(total)]);
|
|
1129
1136
|
effectService.disposeEffect(effect);
|
|
1130
1137
|
// The second list is typed `readonly TOIMComputedSlot[]` — pass slots taken from
|
|
1131
1138
|
// a computed service's `computedCollectionService` straight in.
|
|
1132
1139
|
|
|
1133
1140
|
// One column of one slot, woken when that named column moves and by nothing else.
|
|
1134
1141
|
// It takes the COLUMN SLOT, which the column owner mints and holds.
|
|
1135
|
-
const onRank = effectService.createEffect(db, [cardService.columnSlotService.holdColumn(slot, 'rank')]
|
|
1142
|
+
const onRank = effectService.createEffect(db, () => {}, [cardService.columnSlotService.holdColumn(slot, 'rank')]);
|
|
1136
1143
|
```
|
|
1137
1144
|
|
|
1138
1145
|
The body's type is `TOIMEffectBody`, which is `() => (() => void) | void`. **A returned
|
|
@@ -1142,10 +1149,10 @@ so a body that opened something on each run has somewhere to close it.
|
|
|
1142
1149
|
```typescript
|
|
1143
1150
|
const poll = (): void => {};
|
|
1144
1151
|
|
|
1145
|
-
effectService.createEffect(db,
|
|
1152
|
+
effectService.createEffect(db, () => {
|
|
1146
1153
|
const timer = setInterval(poll, 1000);
|
|
1147
1154
|
return () => clearInterval(timer);
|
|
1148
|
-
});
|
|
1155
|
+
}, [slot]);
|
|
1149
1156
|
```
|
|
1150
1157
|
|
|
1151
1158
|
The return is checked with `typeof` rather than for truthiness, because an
|
|
@@ -1153,16 +1160,16 @@ expression-bodied arrow hands back whatever the expression evaluated to
|
|
|
1153
1160
|
(`() => set.add(key)` returns the `Set`), and anything that is not a function is
|
|
1154
1161
|
discarded.
|
|
1155
1162
|
|
|
1156
|
-
**An effect can also find its sources as it reads.** `
|
|
1157
|
-
takes no source lists: the body gets an `
|
|
1163
|
+
**An effect can also find its sources as it reads.** `createSourceTrackingEffect(db, body)`
|
|
1164
|
+
takes no source lists: the body gets an `OIMSourceTracker`, and what it reads through
|
|
1158
1165
|
`readSourceSlot(slot)` (which files the slot and returns it) and
|
|
1159
1166
|
`readSourceComputedValue(computedSlot)` (which files the derivation and returns its value)
|
|
1160
|
-
is what that run depends on. The body's type is `
|
|
1167
|
+
is what that run depends on. The body's type is `TOIMSourceTrackingEffectBody`.
|
|
1161
1168
|
|
|
1162
1169
|
```typescript
|
|
1163
|
-
effectService.
|
|
1164
|
-
const isFirst =
|
|
1165
|
-
console.log(isFirst ?
|
|
1170
|
+
effectService.createSourceTrackingEffect(db, sourceTracker => {
|
|
1171
|
+
const isFirst = sourceTracker.readSourceSlot(slot).entity?.rank === 1;
|
|
1172
|
+
console.log(isFirst ? sourceTracker.readSourceSlot(slot).entity?.title : 'not first');
|
|
1166
1173
|
});
|
|
1167
1174
|
```
|
|
1168
1175
|
|
|
@@ -1226,13 +1233,13 @@ frozen object per set of names on that service; get it once per view, not per en
|
|
|
1226
1233
|
and `unholdColumn(columnSlot)` gives it back. A write wakes it once if it changes any of the
|
|
1227
1234
|
group's columns and not at all if it changes only others.
|
|
1228
1235
|
|
|
1229
|
-
**`columnNames` picks the column slot store.** `createOIMSlotCollectionService<Card, 'rank' | 'title'>(
|
|
1236
|
+
**`columnNames` picks the column slot store.** `createOIMSlotCollectionService<Card, 'rank' | 'title'>({ keyOf, columnNames: ['rank', 'title'] })`
|
|
1230
1237
|
keeps each entity's column slots in an array by column number instead of a `Map` per entity,
|
|
1231
1238
|
which makes an entity's column store cheaper to create and give back; that is what a scrolling view pays.
|
|
1232
1239
|
`holdColumn` and `getColumnGroup` are typed on the named columns, and a slot for any other
|
|
1233
1240
|
column throws. `columnNames` is on the options of every door — entity, bucket, index, computed —
|
|
1234
1241
|
keyed or single. Without `columnNames` the store is the `columnSlotStoreService` option, and the
|
|
1235
|
-
doors default it to the shared map-driven
|
|
1242
|
+
doors default it to the shared map-driven a shared map-driven store.
|
|
1236
1243
|
|
|
1237
1244
|
### Frame rules
|
|
1238
1245
|
|
|
@@ -1287,10 +1294,10 @@ doors default it to the shared map-driven `OIMDefaults.COLUMN_SLOT_STORE_SERVICE
|
|
|
1287
1294
|
const paint = (card: Card | undefined): void => {};
|
|
1288
1295
|
const measureAndScroll = (): void => {};
|
|
1289
1296
|
|
|
1290
|
-
effectService.createEffect(db,
|
|
1297
|
+
effectService.createEffect(db, () => {
|
|
1291
1298
|
paint(slot.entity);
|
|
1292
1299
|
OIMChannelService.INSTANCE.subscribeOnceAfterFrame(db.defaultChannelSlot, () => { measureAndScroll(); });
|
|
1293
|
-
});
|
|
1300
|
+
}, [slot]);
|
|
1294
1301
|
```
|
|
1295
1302
|
|
|
1296
1303
|
The handler is told after **one** frame, the first that ends after the call, and is then gone.
|
|
@@ -1303,7 +1310,7 @@ inside its effect and is answered at the end of that same frame.
|
|
|
1303
1310
|
|
|
1304
1311
|
It is **not** `subscribeAfterEachFrame` plus a disposal. The effect runs again
|
|
1305
1312
|
next frame and registers again, so a standing handler accumulates one more per run:
|
|
1306
|
-
`src/
|
|
1313
|
+
`src/channel/tests/a-handler-that-wants-one-frame-and-not-every-frame.test.ts` measures the
|
|
1307
1314
|
one-shot at 1, 2, 3 calls over three frames against the standing handler's 1, 3,
|
|
1308
1315
|
6. The one-shot handler also comes off *before* its body runs, so a handler that throws is not left
|
|
1309
1316
|
standing for the next frame.
|
|
@@ -1374,7 +1381,7 @@ computeds), and the bucket's type parameter is that slot type,
|
|
|
1374
1381
|
`getEntitiesByKey` is there because its members are entity slots.
|
|
1375
1382
|
|
|
1376
1383
|
```typescript
|
|
1377
|
-
const byDeckService = createOIMSlotCollectionService(
|
|
1384
|
+
const byDeckService = createOIMSlotCollectionService({ slotContentService: new OIMSlotContentSetBucketService<CardSlot>() });
|
|
1378
1385
|
const byDeck = byDeckService.createStoreSlot(db);
|
|
1379
1386
|
|
|
1380
1387
|
const aceSlot = cardService.writeSlotContentByKey(cards, 'c1', { id: 'c1', deck: 'red', title: 'Ace', rank: 1 });
|
|
@@ -1483,7 +1490,7 @@ disagree. With the unique one `hasBucketMemberSlot` answers from the `Set`; with
|
|
|
1483
1490
|
|
|
1484
1491
|
### Positions the caller decides, and the commands that record them
|
|
1485
1492
|
|
|
1486
|
-
`createOIMSlotCollectionService<CardSlot>(
|
|
1493
|
+
`createOIMSlotCollectionService<CardSlot>()` with no `slotContentService` is the bucket collection to
|
|
1487
1494
|
take when the positions are yours: its default content service is
|
|
1488
1495
|
`OIMSlotContentPositionalArrayBucketService` with an `OIMBucketCommands` recording over it.
|
|
1489
1496
|
|
|
@@ -1529,17 +1536,17 @@ from the bucket's for good.
|
|
|
1529
1536
|
consumer patches instead of rebuilding:
|
|
1530
1537
|
|
|
1531
1538
|
```typescript
|
|
1532
|
-
const pageService = createOIMSlotCollectionService(
|
|
1539
|
+
const pageService = createOIMSlotCollectionService({ slotContentService: new OIMSlotContentPositionalArrayBucketService<CardSlot>({ bucketCommands: new OIMBucketCommands({ maxCommandsPerSlot: undefined }) }) });
|
|
1533
1540
|
const page = pageService.createStoreSlot(db);
|
|
1534
1541
|
const bucket = pageService.holdSlotByKey(page, 'p1');
|
|
1535
1542
|
|
|
1536
1543
|
const rebuildFrom = (slots: Iterable<CardSlot>): void => {};
|
|
1537
1544
|
const applyToTheRenderedList = (commands: readonly TOIMOrderedListCommand[]): void => {};
|
|
1538
1545
|
|
|
1539
|
-
effectService.createEffect(db,
|
|
1546
|
+
effectService.createEffect(db, () => {
|
|
1540
1547
|
if (bucket.isWholeListRecorded) rebuildFrom(pageService.slotContentService.getBucketMemberSlots(bucket));
|
|
1541
1548
|
else applyToTheRenderedList(bucket.commands ?? []);
|
|
1542
|
-
});
|
|
1549
|
+
}, [bucket]);
|
|
1543
1550
|
```
|
|
1544
1551
|
|
|
1545
1552
|
The content service already writes the bucket slot on every mutation, so the consumer
|
|
@@ -1584,7 +1591,7 @@ per-bucket state, so **one instance may serve two bucket collections**.
|
|
|
1584
1591
|
### The other strategy: an index that fills itself
|
|
1585
1592
|
|
|
1586
1593
|
```typescript
|
|
1587
|
-
const indexService = createOIMSlotCollectionIndexService(
|
|
1594
|
+
const indexService = createOIMSlotCollectionIndexService({
|
|
1588
1595
|
entityService: cardService,
|
|
1589
1596
|
keyOf: (card: Card) => card.deck, // which key an entity belongs to
|
|
1590
1597
|
});
|
|
@@ -1593,7 +1600,7 @@ const index = indexService.createIndexStoreSlot(cards);
|
|
|
1593
1600
|
const render = (slots: unknown[]): void => {};
|
|
1594
1601
|
|
|
1595
1602
|
const spades = indexService.holdSlotByKey(index, 'spades'); // the hold mints the bucket and fills it
|
|
1596
|
-
effectService.createEffect(db,
|
|
1603
|
+
effectService.createEffect(db, () => render(Array.from(indexService.slotContentService.getBucketMemberSlots(spades))), [spades]);
|
|
1597
1604
|
```
|
|
1598
1605
|
|
|
1599
1606
|
It **is** a bucket collection (`OIMSlotCollectionIndexService` extends `OIMSlotCollectionService`), and
|
|
@@ -1609,7 +1616,7 @@ index's buckets cannot be written from outside. The default content service is
|
|
|
1609
1616
|
new OIMSlotContentSetBucketService()` for buckets that keep no positions. Passing
|
|
1610
1617
|
`entityService` and then the entity store slot is the whole wiring: `createIndexStoreSlot(cards)`
|
|
1611
1618
|
registers a content-change listener on that store slot
|
|
1612
|
-
(`
|
|
1619
|
+
(`OIMSlotContentChangeListenerService.INSTANCE.addContentChangeListener(cards, …)`), and the entity
|
|
1613
1620
|
service knows nothing about indexes.
|
|
1614
1621
|
`disposeIndexStoreSlot(index)` takes that listener off, empties the buckets and gives back every
|
|
1615
1622
|
entity held. After that, `getSlotByKey` and `holdSlotByKey` throw, but the two give-backs
|
|
@@ -1635,14 +1642,14 @@ Know these four things before choosing it:
|
|
|
1635
1642
|
door (`getSlotByKey`, `holdSlotByKey`, `getSlotsByKeys`, `holdSlotsByKeys`) is filled from the source
|
|
1636
1643
|
entities written before it, without a flush, so minting inside an effect body does not
|
|
1637
1644
|
throw. `getSlotsByKeys` / `holdSlotsByKeys` fill all new buckets in ONE pass over the source. How long the bucket lives then is the
|
|
1638
|
-
collection's ordinary business: the `slotReleasePolicy` option, or `
|
|
1645
|
+
collection's ordinary business: the `slotReleasePolicy` option, or `defaults.slotReleasePolicy`
|
|
1639
1646
|
when you name none; with neither, the bucket is kept for good. `OIMSlotAfterIdleReleasePolicy`
|
|
1640
1647
|
cools it on the queue, and `OIMSlotAtUnholdReleasePolicy.INSTANCE` ends it at
|
|
1641
1648
|
the last give-back. The index injects no lifetime of its own.
|
|
1642
1649
|
- **The key comes from `columns`, `keyOf`, or both**, the same pair as the sorter's
|
|
1643
1650
|
`sortColumns` / `comparator`. The key type is inferred from the columns (`TOIMIndexKey`):
|
|
1644
1651
|
`{ columns: ['deck'] }` is keyed by that one column's value and refuses an array;
|
|
1645
|
-
`createOIMSlotCollectionIndexService(
|
|
1652
|
+
`createOIMSlotCollectionIndexService({ entityService, columns: ['deck', 'suit'] })` is keyed by the
|
|
1646
1653
|
tuple of their values and builds the bucket collection on a tree-driven store
|
|
1647
1654
|
(`OIMSlotTreeStoreService`) by itself, while `createOIMSlotCollectionIndexService` without a
|
|
1648
1655
|
`bucketSlotStoreService` refuses two columns. When the index asks
|
|
@@ -1662,7 +1669,7 @@ Know these four things before choosing it:
|
|
|
1662
1669
|
### A sorted list is a computed over an index
|
|
1663
1670
|
|
|
1664
1671
|
```typescript
|
|
1665
|
-
const indexService = createOIMSlotCollectionIndexService(
|
|
1672
|
+
const indexService = createOIMSlotCollectionIndexService({
|
|
1666
1673
|
entityService: cardService,
|
|
1667
1674
|
keyOf: (card: Card) => card.deck,
|
|
1668
1675
|
});
|
|
@@ -1670,12 +1677,12 @@ const index = indexService.createIndexStoreSlot(cards);
|
|
|
1670
1677
|
|
|
1671
1678
|
const byRankInDeckService = createOIMSortedComputedService({
|
|
1672
1679
|
indexService,
|
|
1673
|
-
sortColumns: [{
|
|
1680
|
+
sortColumns: [{ columnName: 'rank', direction: 'asc' }],
|
|
1674
1681
|
});
|
|
1675
1682
|
const byRankInDeck = byRankInDeckService.createSortedStoreSlot(index);
|
|
1676
1683
|
|
|
1677
1684
|
const spades = byRankInDeckService.computedCollectionService.holdSlotByKey(byRankInDeck, 'spades');
|
|
1678
|
-
effectService.createEffect(db,
|
|
1685
|
+
effectService.createEffect(db, () => { console.log(spades.computedValueService.getValueForSlot(spades).map((entitySlot) => entitySlot.entity?.title)); }, [], [spades]);
|
|
1679
1686
|
spades.owner.unholdSlot(spades);
|
|
1680
1687
|
```
|
|
1681
1688
|
|
|
@@ -1688,7 +1695,7 @@ with that store slot first. A key's value, read with
|
|
|
1688
1695
|
`computedSlot.computedValueService.getValueForSlot(computedSlot)`, is that key's entity slots,
|
|
1689
1696
|
sorted. `indexService` is the `OIMSlotCollectionIndexService` whose buckets it sorts.
|
|
1690
1697
|
|
|
1691
|
-
- `sortColumns` is `readonly TOIMSortColumn<TEntity>[]`, each `{
|
|
1698
|
+
- `sortColumns` is `readonly TOIMSortColumn<TEntity>[]`, each `{ columnName, direction: 'asc' | 'desc' }`.
|
|
1692
1699
|
`column` is a `TOIMSortableColumnName<TEntity>`, meaning a column whose values are strings,
|
|
1693
1700
|
numbers or bigints. **Columns decide which writes re-sort.**
|
|
1694
1701
|
- `comparator` is `(left: TEntity, right: TEntity) => number`. **It decides the order.**
|
|
@@ -1721,17 +1728,17 @@ The other strategy keeps the index's own buckets in order, so a read needs no co
|
|
|
1721
1728
|
The consumer picks one at construction.
|
|
1722
1729
|
|
|
1723
1730
|
```typescript
|
|
1724
|
-
const cardsByDeckService = createOIMSlotCollectionSortedArrayIndexService(
|
|
1731
|
+
const cardsByDeckService = createOIMSlotCollectionSortedArrayIndexService({
|
|
1725
1732
|
entityService: cardService,
|
|
1726
1733
|
columns: ['deck'],
|
|
1727
|
-
sortColumns: [{
|
|
1734
|
+
sortColumns: [{ columnName: 'rank', direction: 'asc' }, { columnName: 'id', direction: 'desc' }],
|
|
1728
1735
|
});
|
|
1729
1736
|
const cardsByDeck = cardsByDeckService.createIndexStoreSlot(cards);
|
|
1730
1737
|
const spades = cardsByDeckService.holdSlotByKey(cardsByDeck, 'spades');
|
|
1731
1738
|
Array.from(cardsByDeckService.slotContentService.getBucketMemberSlots(spades));
|
|
1732
1739
|
```
|
|
1733
1740
|
|
|
1734
|
-
`createOIMSlotCollectionSortedArrayIndexService(
|
|
1741
|
+
`createOIMSlotCollectionSortedArrayIndexService({ entityService, columns? | keyOf?, sortColumns, slotReleasePolicy?, bucketSlotStoreService? })`
|
|
1735
1742
|
returns an `OIMSlotCollectionSortedArrayIndexService`, whose content service is an
|
|
1736
1743
|
`OIMSlotContentSortedArrayBucketService`. `sortColumns` is the same `readonly TOIMSortColumn<TEntity>[]`
|
|
1737
1744
|
that `createOIMSortedComputedService` takes.
|
|
@@ -1760,14 +1767,14 @@ bucket any-column slot doors (`getAnyBucketColumnByKey(indexStoreSlot, key)`,
|
|
|
1760
1767
|
the positional ones of the array-based kinds included.
|
|
1761
1768
|
|
|
1762
1769
|
```typescript
|
|
1763
|
-
const decksService = createOIMSlotCollectionIndexService(
|
|
1770
|
+
const decksService = createOIMSlotCollectionIndexService({
|
|
1764
1771
|
entityService: cardService,
|
|
1765
1772
|
keyOf: (card: Card) => card.deck,
|
|
1766
1773
|
});
|
|
1767
1774
|
const decks = decksService.createIndexStoreSlot(cards);
|
|
1768
1775
|
|
|
1769
1776
|
const rankOfSpades = decksService.holdBucketColumnByKey(decks, 'spades', 'rank');
|
|
1770
|
-
const onRankInSpades = effectService.createEffect(db,
|
|
1777
|
+
const onRankInSpades = effectService.createEffect(db, () => {}, [rankOfSpades]);
|
|
1771
1778
|
effectService.disposeEffect(onRankInSpades);
|
|
1772
1779
|
decksService.columnSlotService.unholdColumn(rankOfSpades);
|
|
1773
1780
|
```
|
|
@@ -1779,7 +1786,7 @@ leaves its old bucket in O(1). Only an index writes it, through its `IOIMIndexKe
|
|
|
1779
1786
|
(`get(slot, position)`, `set(slot, position, indexKey)`), at the position its index store
|
|
1780
1787
|
slot was given (`TOIMIndexStoreSlot.indexKeysPosition`, allocated from the source store slot's
|
|
1781
1788
|
`indexKeysPositions`). Choose the service with the `indexKeysService` option of `OIMDB`; every
|
|
1782
|
-
index the factories build on that `db` takes it (default `
|
|
1789
|
+
index the factories build on that `db` takes it (default `OIMAppendingIndexKeysService.INSTANCE`).
|
|
1783
1790
|
|
|
1784
1791
|
- `OIMAppendingIndexKeysService` (default): a disposed index's position is never
|
|
1785
1792
|
reused, and its stale entry stays on each entity slot. A slot's `indexKeys` grows to the highest
|
|
@@ -1796,7 +1803,7 @@ Creating indexes that way is expensive; better not to.
|
|
|
1796
1803
|
### Rendering an ordered bucket
|
|
1797
1804
|
|
|
1798
1805
|
```typescript setup
|
|
1799
|
-
const orderedBucketsService = createOIMSlotCollectionService(
|
|
1806
|
+
const orderedBucketsService = createOIMSlotCollectionService({ slotContentService: new OIMSlotContentPositionalArrayBucketService<CardSlot>({ bucketCommands: new OIMBucketCommands({ maxCommandsPerSlot: undefined }) }) });
|
|
1800
1807
|
const orderedBuckets = orderedBucketsService.createStoreSlot(db);
|
|
1801
1808
|
```
|
|
1802
1809
|
|
|
@@ -1875,7 +1882,7 @@ this engine (`createEffect`'s second argument, and `sourceComputeds` in
|
|
|
1875
1882
|
it is. Inside `derive`, `computedSlot.value` is the previous value:
|
|
1876
1883
|
|
|
1877
1884
|
```typescript
|
|
1878
|
-
effectService.createEffect(db,
|
|
1885
|
+
effectService.createEffect(db, () => { console.log(totalSlot.computedValueService.getValueForSlot(totalSlot)); }, [], [totalSlot]);
|
|
1879
1886
|
```
|
|
1880
1887
|
|
|
1881
1888
|
### One derivation per key
|
|
@@ -1908,9 +1915,7 @@ A derivation is never empty: `OIMSlotContentComputedService.isSlotEmpty` answers
|
|
|
1908
1915
|
so a filed derivation goes only when nothing holds it,
|
|
1909
1916
|
nothing watches it, and the grace is `0`. That is what
|
|
1910
1917
|
`OIMSlotAtUnholdReleasePolicy.INSTANCE` answers, and it is the default when no `slotReleasePolicy` is given.
|
|
1911
|
-
A derivation let go of while the channel is flushing is
|
|
1912
|
-
handed to the channel's unused-slot collector and released when the flush ends, the same
|
|
1913
|
-
deferral that every other non-empty slot gets.
|
|
1918
|
+
A derivation let go of while the channel is flushing is released on the spot, like every other slot.
|
|
1914
1919
|
`createOIMComputedService` takes a `slotReleasePolicy` as an optional key of its options object.
|
|
1915
1920
|
|
|
1916
1921
|
`createOIMComputedService` is `OIMComputedService` with its generics
|
|
@@ -1941,7 +1946,7 @@ const cardSummary = cardSummaryService.createComputedStoreSlot(
|
|
|
1941
1946
|
);
|
|
1942
1947
|
const summarySingleSlotService = cardSummaryService.computedSingleSlotService;
|
|
1943
1948
|
const topColumn = summarySingleSlotService.columnSlotService.holdColumn(summarySingleSlotService.getSingleSlot(cardSummary), 'top');
|
|
1944
|
-
const onTop = effectService.createEffect(db,
|
|
1949
|
+
const onTop = effectService.createEffect(db, () => { console.log(topColumn.value); }, [], [topColumn]);
|
|
1945
1950
|
effectService.disposeEffect(onTop);
|
|
1946
1951
|
summarySingleSlotService.columnSlotService.unholdColumn(topColumn);
|
|
1947
1952
|
```
|
|
@@ -1959,7 +1964,7 @@ const order = orderService.createBucketMemoStoreSlot(orderedBuckets);
|
|
|
1959
1964
|
const render = (slots: readonly CardSlot[]): void => {};
|
|
1960
1965
|
|
|
1961
1966
|
const deck = orderService.computedCollectionService.holdSlotByKey(order, 'spades'); // mints and holds the bucket too
|
|
1962
|
-
effectService.createEffect(db,
|
|
1967
|
+
effectService.createEffect(db, () => render(deck.computedValueService.getValueForSlot(deck)), [], [deck]);
|
|
1963
1968
|
```
|
|
1964
1969
|
|
|
1965
1970
|
Its value is the bucket's slots in the bucket's order, produced on the read that
|
|
@@ -1981,14 +1986,14 @@ A sorted list is the other derivation of this kind:
|
|
|
1981
1986
|
```typescript
|
|
1982
1987
|
type Settings = { theme: string; fontSize: number };
|
|
1983
1988
|
|
|
1984
|
-
const settingsService = createOIMSingleSlotService(
|
|
1989
|
+
const settingsService = createOIMSingleSlotService({ slotContentService: createOIMSlotContentEntityService<Settings>(), columnNames: ['theme', 'fontSize'] });
|
|
1985
1990
|
const settings = settingsService.createStoreSlot(db);
|
|
1986
1991
|
const settingsContentService = settingsService.slotContentService;
|
|
1987
1992
|
settingsService.writeSingleSlotContent(settings, { theme: 'dark', fontSize: 14 });
|
|
1988
1993
|
const settingsSlot = settingsService.holdSingleSlot(settings);
|
|
1989
1994
|
|
|
1990
|
-
effectService.createEffect(db,
|
|
1991
|
-
effectService.createEffect(db, [settingsService.columnSlotService.holdColumn(settingsSlot, 'theme')]
|
|
1995
|
+
effectService.createEffect(db, () => { console.log(settingsSlot.entity?.theme); }, [settingsSlot]);
|
|
1996
|
+
effectService.createEffect(db, () => {}, [settingsService.columnSlotService.holdColumn(settingsSlot, 'theme')]);
|
|
1992
1997
|
|
|
1993
1998
|
settingsContentService.updateEntity(settingsSlot, { theme: 'light' });
|
|
1994
1999
|
settingsContentService.deleteEntityColumns(settingsSlot, ['fontSize']); // the thing a patch cannot say
|
|
@@ -1999,7 +2004,7 @@ db.flush();
|
|
|
1999
2004
|
A standalone record is a single service: `createOIMSingleSlotService` is
|
|
2000
2005
|
`createOIMSingleSlotService` over `createOIMSlotContentEntityService`, with the store fixed to
|
|
2001
2006
|
`OIMSingleSlotStoreService` and no `keyOf`. Every option is optional:
|
|
2002
|
-
`entityWriter` and `slotEmptinessPolicy` fall back to `
|
|
2007
|
+
`entityWriter` and `slotEmptinessPolicy` fall back to `defaults`, then to the built-in defaults, and `columnNames` names the
|
|
2003
2008
|
columns and files their slots in the ordinal store. The entity is
|
|
2004
2009
|
written through `slotContentService.writeSlotContentByKey(settings, entity)`, an ordinary write announced
|
|
2005
2010
|
to whoever subscribed, not handed to the options or to `createStoreSlot(db)`, which mints
|
|
@@ -2036,7 +2041,7 @@ const body = (): void => {};
|
|
|
2036
2041
|
const entitySlot = cardService.holdSlotByKey(cards, 'c1');
|
|
2037
2042
|
|
|
2038
2043
|
const disposers: TOIMDisposer[] = [];
|
|
2039
|
-
disposers.push(effectService.subscribeEffect(db,
|
|
2044
|
+
disposers.push(effectService.subscribeEffect(db, body, [entitySlot]));
|
|
2040
2045
|
disposers.push(() => { entitySlot.owner.unholdSlot(entitySlot); });
|
|
2041
2046
|
for (let i = disposers.length - 1; i >= 0; i--) disposers[i]();
|
|
2042
2047
|
```
|
|
@@ -2048,7 +2053,7 @@ a throwing disposer does not stop the rest. The engine ships no scope to do that
|
|
|
2048
2053
|
|
|
2049
2054
|
## Export reference
|
|
2050
2055
|
|
|
2051
|
-
**
|
|
2056
|
+
**230 names. This list is `src/index.ts`; nothing else is exported.**
|
|
2052
2057
|
|
|
2053
2058
|
**The channel slot** — `OIMChannelService`, `TOIMChannelSlot`, `TOIMChannelSlotOptions`, `createOIMChannelSlot`,
|
|
2054
2059
|
`IOIMChannelMap`, `TOIMChannelFrame`, `TOIMChannelClock`, `TOIMChannelScheduler`, `OIMChannelQueue` (**type only**),
|
|
@@ -2076,10 +2081,10 @@ a throwing disposer does not stop the rest. The engine ships no scope to do that
|
|
|
2076
2081
|
`TOIMSlotOwnerServiceOptions`, `IOIMSlotContentService`, `IOIMSlotContentWriter`, `IOIMKeyedSlotCreator`, `IOIMSingleSlotCreator`,
|
|
2077
2082
|
`IOIMSlotFilingService`, `TOIMSlotContentServiceKey`, `TOIMSlotContentServiceSlot`, `TOIMSlotContentServiceSingleSlot`,
|
|
2078
2083
|
`TOIMSlotContentServiceContent`, `TOIMSlotContentServicePatch`,
|
|
2079
|
-
`
|
|
2084
|
+
`OIMSlotContentChangeListenerService`, `IOIMSlotContentChangeListener`, `IOIMStoreSlotListener`, `OIMNoStoreSlotListener`,
|
|
2080
2085
|
`IOIMSlotSubscriptionListener`, `OIMNoSlotSubscriptionListener`.
|
|
2081
2086
|
|
|
2082
|
-
**Slots and their owners** — `TOIMSlot`, `TOIMStoreSlot`, `TOIMSingleSlotStoreSlot`, `
|
|
2087
|
+
**Slots and their owners** — `TOIMSlot`, `TOIMStoreSlot`, `TOIMSingleSlotStoreSlot`, `TOIMEditable`, `TOIMKeyedSlot`,
|
|
2083
2088
|
`TOIMIndexableSlot`, `OIMSlotOwner`, `IOIMSlotOwner`, `IOIMSlotEmptinessPolicy`, `IOIMSlotStamp`, `TOIMSameValueGuard`,
|
|
2084
2089
|
`oimSameValueZeroGuard`, `oimIsSlotEmpty`, `oimIsSlotReleased`, `oimHoldSlots`, `oimUnholdSlotsIfAlive`,
|
|
2085
2090
|
`OIM_FROZEN_EMPTY_SLOTS`.
|
|
@@ -2088,11 +2093,11 @@ a throwing disposer does not stop the rest. The engine ships no scope to do that
|
|
|
2088
2093
|
`OIMColumnSlotOwner`, `OIMPushColumnSlotOwner` (**type only**), `OIMPullColumnSlotOwner`, `TOIMColumnSlotOwnerOptions`.
|
|
2089
2094
|
|
|
2090
2095
|
**Entities** — `OIMSlotContentEntityService`, `TOIMSlotContentEntityServiceOptions`, `TOIMEntitySlotOwnerService`,
|
|
2091
|
-
`TOIMEntitySlot`, `TOIMEntityContentSlot`, `createOIMSlotContentEntityService`; `IOIMEntityWriter`, `
|
|
2092
|
-
`
|
|
2096
|
+
`TOIMEntitySlot`, `TOIMEntityContentSlot`, `createOIMSlotContentEntityService`; `IOIMEntityWriter`, `createOIMInPlaceEntityWriter`,
|
|
2097
|
+
`createOIMReplacingEntityWriter`, `TOIMEntityWriterOptions`, `TOIMEntityInserter`,
|
|
2093
2098
|
`oimInsertEntityWithCopy`, `oimInsertEntityWithoutCopy`, `oimInsertEntityWithSanitizedCopy`, `oimPatchEntityColumns`,
|
|
2094
|
-
`oimPatchEntityColumn`, `
|
|
2095
|
-
`
|
|
2099
|
+
`oimPatchEntityColumn`, `oimGuardedPatchEntityColumns`, `oimGuardedPatchEntityColumn`, `oimPutEntityColumns`,
|
|
2100
|
+
`oimGuardedPutEntityColumns`, `TOIMEntityColumnName`, `TOIMEntityColumnValue`.
|
|
2096
2101
|
|
|
2097
2102
|
**Buckets** — `OIMSlotContentBucketService`, `OIMSlotContentArrayBucketService`,
|
|
2098
2103
|
`OIMSlotContentPositionalArrayBucketService`, `OIMSlotContentSetBucketService`,
|
|
@@ -2109,8 +2114,8 @@ a throwing disposer does not stop the rest. The engine ships no scope to do that
|
|
|
2109
2114
|
`IOIMIndexKeysService`, `TOIMIndexKeysPositions`, `OIMIndexKeysService`, `OIMAppendingIndexKeysService`,
|
|
2110
2115
|
`OIMReclaimingIndexKeysService`.
|
|
2111
2116
|
|
|
2112
|
-
**The graph** — `OIMEffectService`, `IOIMEffectService`, `TOIMEffect`, `TOIMEffectBody`, `
|
|
2113
|
-
`
|
|
2117
|
+
**The graph** — `OIMEffectService`, `IOIMEffectService`, `TOIMEffect`, `TOIMEffectBody`, `TOIMSourceTrackingEffectBody`,
|
|
2118
|
+
`OIMSourceTracker` (**type only**), `TOIMComputedSlot`, `TOIMComputedSources`, `TOIMComputedValueReleaser`, `OIMSourceTrackingComputedService`, `OIMSingleSourceTrackingComputedService`, `createOIMSourceTrackingComputedService`, `createOIMSingleSourceTrackingComputedService`, `TOIMSourceTrackingComputedSlot`, `TOIMSourceTrackingComputedContentSlot`, `TOIMSourceTrackingComputedDerive`, `OIMSlotContentSourceTrackingComputedService` (**type only**), `TOIMSourceTrackingComputedDerivationOptions`, `TOIMComputedServiceFactoryOptions`, `TOIMComputedDerivation`, `TOIMComputedStoreSlot`,
|
|
2114
2119
|
`OIMComputedValueService`, `IOIMComputedValueService`, `TOIMComputedValueServiceOptions`, `TOIMComputedContentSlot`,
|
|
2115
2120
|
`OIMSlotContentComputedService` (**type only**), `TOIMSlotContentComputedServiceOptions`, `oimRefreshSourceComputeds` (brings every computed in a `sourceComputeds` list current; call it before a `derive` that runs outside an effect, as `useOIMValue` does).
|
|
2116
2121
|
|
|
@@ -2125,16 +2130,14 @@ a throwing disposer does not stop the rest. The engine ships no scope to do that
|
|
|
2125
2130
|
`OIMSingleSlotStoreService`, `TOIMSingleSlotStore`, `TOIMDefaultSlotStore`, `TOIMSlotStoreTree`, `IOIMKeyTreeWalk`, `TOIMCompositeKeyNode`,
|
|
2126
2131
|
`OIMKeyTreeWalk`, `OIMCompositeKeyTreeWalk`, `OIMScalarKeyTreeWalk`.
|
|
2127
2132
|
|
|
2128
|
-
**The root and its defaults** — `OIMDB`, `TOIMDBOptions`, `OIMDefaults`, `OIMDefaultValue`, `TOIMFactoryOptions`.
|
|
2133
|
+
**The root and its defaults** — `OIMDB`, `TOIMDBOptions`, `OIMDefaults`, `TOIMDefaultsOptions`, `OIMDefaultValue`, `TOIMFactoryOptions`.
|
|
2129
2134
|
|
|
2130
2135
|
**Comparison and diffing** — `oimSameSlotSequence`, `oimSameKeySequence`, `oimOrderedListCommandsOfTransition`,
|
|
2131
2136
|
`TOIMOrderedListCommand`.
|
|
2132
2137
|
|
|
2133
|
-
**Shared stateless defaults** —
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
`STORE_SLOT_LISTENER`. Each is minted on its first `getValue()` instead of at module load, so a
|
|
2137
|
-
consumer that names none of them constructs none of them. The cost of the single door is described under **Every optional
|
|
2138
|
+
**Shared stateless defaults** — `OIMDefaults` (an instance passed as `defaults`, `OIMDefaults.INSTANCE`
|
|
2139
|
+
the empty one) and `OIMDefaultValue`, the lazy holder of each built-in default, which is built on its
|
|
2140
|
+
first `getValue()` instead of at module load, so a consumer that reaches none of them constructs none of them. The cost of the single door is described under **Every optional
|
|
2138
2141
|
dependency has a default** above.
|
|
2139
2142
|
|
|
2140
2143
|
That is the whole of `src/index.ts`. If a name is not on this list, it is not
|