@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/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`, so a default set through one
64
- (`OIMDefaults.ENTITY_WRITER.setValue(createOIMEntityWriterReplacing())`) never reaches a
65
- service built by the other, which keeps writing in place. Objects built by
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 the `OIMDefaults` statics or a React context.
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 | 98686 B | 24049 B |
94
- | One entity store slot: `OIMDB`, `createOIMSlotCollectionService`, `createOIMSlotContentEntityService`, `writeSlotContentByKey`, `getSlotByKey` | 35379 B | 9243 B |
95
- | The same, plus one effect and `db.flush()` | 44020 B | 11357 B |
96
- | The same, plus one index: `createOIMSlotCollectionIndexService` | 64398 B | 16048 B |
97
- | An ordered bucket with commands: `OIMSlotContentPositionalArrayBucketService`, `OIMBucketCommands` | 48515 B | 12218 B |
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 three layers
102
+ ## The domains
103
103
 
104
- `src/index.ts` is the entire public surface, and it is divided in three:
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
- | Layer | What is in it |
107
+ | Domain | Contents |
107
108
  |---|---|
108
- | `src/engine/` | The primitives, each a stateless service over a plain struct: `OIMChannelService.INSTANCE` over a `TOIMChannelSlot` (frames, the revision), `OIMEffectService.INSTANCE` (effects, handed the `db` on every call), `OIMSlotOwnerService` (the owner of every slot a collection filed: holds, release, emptiness, columns), the column owners (abstract `OIMColumnSlotOwner`, `OIMPushColumnSlotOwner` written by the collection, `OIMPullColumnSlotOwner` derived by a computed), the content services (`OIMSlotContentEntityService` for entities, `OIMComputedValueService` for computeds), the key stores and key tree walks, the release policies, the unused-slot collector (`OIMUnusedSlotCollectorService`) over the collector slot, the wheels (`OIMSlotReleaseWheel`, `OIMSlotRefetchWheel`), `TOIMStoreSlot`, `TOIMComputedSlot`, `TOIMEffect`. |
109
- | `src/facade/` | What a caller holds. The collection services `OIMSlotCollectionService` (keyed) and `OIMSingleSlotService` (one slot, no key), each with its `slotContentService`; the bucket content services `OIMSlotContentSetBucketService`, `OIMSlotContentPositionalArrayBucketService`, `OIMSlotContentSortedArrayBucketService` and `OIMBucketCommands`; the auto-index `OIMSlotCollectionIndexService` and `OIMSlotCollectionSortedArrayIndexService`; `OIMComputedService`, `OIMSortedComputedService`, `OIMBucketMemoComputedService`. |
110
- | `src/factories/` | `OIMDB`, `OIMDefaults`, `OIMDefaultValue`, and the doors that build a service: the base ones, `createOIMSlotCollectionService`, `createOIMSingleSlotService` and `createOIMSlotContentEntityService`, and the convenience ones over them, `createOIMSlotCollectionService`, `createOIMSingleSlotService`, `createOIMSlotCollectionService`, `createOIMSingleSlotService`, `createOIMSlotCollectionIndexService`, `createOIMSlotCollectionSortedArrayIndexService`, `createOIMComputedService`, `createOIMSortedComputedService`, `createOIMBucketMemoComputedService`, `createOIMEntityWriterInPlace`, `createOIMEntityWriterReplacing`. |
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, createOIMEntityWriterInPlace, 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 } from '@oimdb/core';
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(db, { slotContentService: createOIMSlotContentEntityService<Card>(db) });
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, [slot], [], () => {
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?>(db, { entityWriter?, slotEmptinessPolicy? }?)
207
+ createOIMSlotContentEntityService<Card, TKey?, TColumnName?>({ entityWriter?, slotEmptinessPolicy? }?)
196
208
  new OIMSlotContentSetBucketService<TBucketMemberSlot, TKey?>({ bucketCommands? })
197
209
  new OIMSlotContentPositionalArrayBucketService<TBucketMemberSlot, TKey?>({ bucketCommands?, bucketMemberSlotsArrayService? })
198
- createOIMSlotCollectionService(db, { slotContentService, slotStoreService?, slotReleasePolicy?, slotSubscriptionListener?, columnSlotStoreService? | columnNames? })
199
- createOIMSingleSlotService(db, { slotContentService, slotReleasePolicy?, slotSubscriptionListener?, columnSlotStoreService? | columnNames? })
200
- createOIMSlotCollectionIndexService(db, { entityService, columns | keyOf, slotContentService?, bucketSlotStoreService?, slotReleasePolicy?, columnSlotStoreService? | columnNames? })
201
- createOIMSlotCollectionSortedArrayIndexService(db, { entityService, sortColumns, columns | keyOf, bucketSlotStoreService?, slotReleasePolicy?, columnSlotStoreService? | columnNames? })
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 `db.slotStamp`. Build the content service first when it is shared:
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>(db);
220
- const sameShapeCardService = createOIMSlotCollectionService(db, { slotContentService: cardContentService });
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/factories/tests/which-answer-a-collection-the-root-mints-gets.test.ts`.
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(db, { slotContentService: createOIMSlotContentEntityService<Card>(db), slotStoreService: new OIMSlotMapStoreService<TOIMEntitySlot<Card, TOIMStoreKey>>(), columnSlotStoreService: OIMDefaults.COLUMN_SLOT_STORE_SERVICE.getValue() });
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 `db.entityWriter`, then to
281
- `OIMDefaults.ENTITY_WRITER`; `slotEmptinessPolicy` to `OIMDefaults.ENTITY_SLOT_EMPTINESS`;
282
- `slotReleasePolicy` to `db.slotReleasePolicy`, then to nothing, which is the default of "a slot
283
- with a value stays for good" described under **The injected seams**; the column store to
284
- `OIMDefaults.COLUMN_SLOT_STORE_SERVICE`.
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` is where a default that would otherwise be an object lives. It is a
294
- class of static `OIMDefaultValue` fields and nothing else; you never construct one.
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
- | Accessor | Type | What it hands back |
310
+ | Field | Type | Built-in default |
297
311
  |---|---|---|
298
- | `OIMDefaults.COLUMN_SLOT_STORE_SERVICE` | `IOIMSlotStoreService<string, TOIMColumnSlot>` | a map-driven store service for column slots; the factories pass it to every service they build without `columnNames` |
299
- | `OIMDefaults.COMPUTED_COLUMN_SLOT_STORE_SERVICE` | `IOIMSlotStoreService<string, TOIMComputedColumnSlot>` | a map-driven store service for the column slots of computed services, keyed or single; the computed factories pass it to every one they build without `columnNames` |
300
- | `OIMDefaults.KEY_TREE_WALK_SCALAR` | `OIMScalarKeyTreeWalk` | the chain-of-values walk |
301
- | `OIMDefaults.KEY_TREE_WALK_COMPOSITE` | `OIMCompositeKeyTreeWalk` | the chain-of-identities walk |
302
- | `OIMDefaults.STANDALONE_SLOT_OWNER` | `OIMSlotOwner` | the owner of a slot nobody filed |
303
- | `OIMDefaults.ENTITY_WRITER` | `IOIMEntityWriter` | `createOIMEntityWriterInPlace()` |
304
- | `OIMDefaults.ENTITY_SLOT_EMPTINESS` | `IOIMSlotEmptinessPolicy<TOIMEntitySlot>` | "empty means `slot.entity === undefined`" |
305
- | `OIMDefaults.INDEX_KEYS_SERVICE` | `IOIMIndexKeysService` | an `OIMAppendingIndexKeysService`; see [Index keys strategy](#index-keys-strategy) |
306
- | `OIMDefaults.STORE_SLOT_LISTENER` | `IOIMStoreSlotListener` | `OIMNoStoreSlotListener`, which hears nothing. It is not a constructor option: every service reads it when built and calls `onStoreSlotCreated(storeSlot)` for each store slot it creates. Set your own with `setValue` before the first service is built; afterwards `setValue` throws |
307
-
308
- **Nothing is constructed at module load.** Every entry is an `OIMDefaultValue`
309
- whose `getValue()` mints on its first call (`??=`) and hands back the same instance
310
- for the rest of the process, so a consumer that never reaches for a key tree walk
311
- never builds one.
312
-
313
- Each has a matching `setValue(value)`, which is how a process replaces a default
314
- everywhere at once. **`setValue` throws once `getValue` has handed the first one
315
- out**, naming the property: `OIMDefaults.ENTITY_WRITER has already been handed
316
- out`. Whoever took the default keeps it, so a late replacement would leave two
317
- implementations live in one process, with the older one inside every object built
318
- before the call.
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, [entitySlot], [], body); // subscribe: the effect is the reference
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, slotOwnerService)` | the identity is going. Give back what its content held |
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(slotEditable: TOIMSlotEditable<TSlot>): void; }
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`" (`OIMDefaults.ENTITY_SLOT_EMPTINESS`); a bucket
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 `OIMDB` take ONE through `slotReleasePolicy`; a door given none takes
480
- `db.slotReleasePolicy`. With neither, the slot owner is built with `OIMSlotAtEmptyReleasePolicy`:
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/engine/collector/tests/which-release-marker-each-collector-may-move.test.ts`.
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 releasingDb = new OIMDB({
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/engine/collector/tests/what-a-stale-index-takes-with-it.test.ts`,
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/engine/collector/tests/what-a-named-arena-keeps-cached.test.ts`.
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>(db, …)`, then `createStoreSlot(db)` | `createOIMSlotCollectionService<TEntity>(db, { keyOf, … })`, then `createStoreSlot(db)` |
733
- | bucket | `createOIMSingleSlotService<TMember>(db, { slotContentService?, … })`, then `createStoreSlot(db)` | `createOIMSlotCollectionService<TMember>(db, { slotContentService?, … })`, then `createStoreSlot(db)` |
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[]>>(OIMDefaults.KEY_TREE_WALK_SCALAR.getValue())
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[]>>(OIMDefaults.KEY_TREE_WALK_COMPOSITE.getValue())
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(db, { slotContentService: createOIMSlotContentEntityService<Tally, TScalarChain>(db), slotStoreService: new OIMSlotTreeStoreService<TOIMStoreKey, TOIMEntitySlot<Tally, TScalarChain>>(
823
- OIMDefaults.KEY_TREE_WALK_SCALAR.getValue(),
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(db, { slotContentService: createOIMSlotContentEntityService<User>(db) });
853
+ const userService = createOIMSlotCollectionService({ slotContentService: createOIMSlotContentEntityService<User>() });
846
854
  const users = userService.createStoreSlot(db);
847
- const groupService = createOIMSlotCollectionService(db, { slotContentService: createOIMSlotContentEntityService<Group>(db) });
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(db, { slotContentService: createOIMSlotContentEntityService<Enrolment, TChain>(db), slotStoreService: new OIMSlotTreeStoreService<TOIMEntitySlot, TOIMEntitySlot<Enrolment, TChain>>(
855
- OIMDefaults.KEY_TREE_WALK_COMPOSITE.getValue(),
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
- `OIMDefaults.KEY_TREE_WALK_SCALAR.getValue()` instead of using a cast.
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(db, { slotContentService: createOIMSlotContentEntityService<Card>(db, { entityWriter: createOIMEntityWriterInPlace({ entityInserter: oimInsertEntityWithCopy }) }) });
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
- `TOIMSlotEditable<TSlot>` is the type that strips `readonly` from the field
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
- createOIMEntityWriterInPlace(options?) // the stored object keeps its identity
1028
- createOIMEntityWriterReplacing(options?) // every write that changed something produces a new object
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; `oimPutEntityColumnsGuarded` keeps both guards, at a measured cost on every put, for
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
- (`createOIMEntityWriterInPlace({ putEntityColumns: oimPutEntityColumnsGuarded })`). Each returns the object
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/facade/entity/tests/what-a-put-that-drops-a-column-costs.test.ts`.
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
- `db.entityWriter`), so there is no `if (inPlace)` anywhere on any write path; the object already
1072
- is the answer. The default is `createOIMEntityWriterInPlace()`. Pass `createOIMEntityWriterReplacing()` when
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) waits: the slot goes to the unused-slot collector and is released once the
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.createEffectImmediate(db, roots, computeds, body)` is the door that runs
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, [slot], [], () => {
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.** `createEffectWithSourceTracking(db, body)`
1157
- takes no source lists: the body gets an `OIMEffectSourceTracker`, and what it reads through
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 `TOIMEffectBodyWithSourceTracking`.
1167
+ is what that run depends on. The body's type is `TOIMSourceTrackingEffectBody`.
1161
1168
 
1162
1169
  ```typescript
1163
- effectService.createEffectWithSourceTracking(db, effectSourceTracker => {
1164
- const isFirst = effectSourceTracker.readSourceSlot(slot).entity?.rank === 1;
1165
- console.log(isFirst ? effectSourceTracker.readSourceSlot(slot).entity?.title : 'not first');
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'>(db, { keyOf, columnNames: ['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 `OIMDefaults.COLUMN_SLOT_STORE_SERVICE.getValue()`.
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, [slot], [], () => {
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/engine/channel/tests/a-handler-that-wants-one-frame-and-not-every-frame.test.ts` measures the
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(db, { slotContentService: new OIMSlotContentSetBucketService<CardSlot>() });
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>(db)` with no `slotContentService` is the bucket collection to
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(db, { slotContentService: new OIMSlotContentPositionalArrayBucketService<CardSlot>({ bucketCommands: new OIMBucketCommands({ maxCommandsPerSlot: undefined }) }) });
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, [bucket], [], () => {
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(db, {
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, [spades], [], () => render(Array.from(indexService.slotContentService.getBucketMemberSlots(spades))));
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
- (`OIMContentChangeListenerService.INSTANCE.addContentChangeListener(cards, …)`), and the entity
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 `db.slotReleasePolicy`
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(db, { entityService, columns: ['deck', 'suit'] })` is keyed by the
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(db, {
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: [{ column: 'rank', direction: 'asc' }],
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, [], [spades], () => { console.log(spades.computedValueService.getValueForSlot(spades).map((entitySlot) => entitySlot.entity?.title)); });
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 `{ column, direction: 'asc' | 'desc' }`.
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(db, {
1731
+ const cardsByDeckService = createOIMSlotCollectionSortedArrayIndexService({
1725
1732
  entityService: cardService,
1726
1733
  columns: ['deck'],
1727
- sortColumns: [{ column: 'rank', direction: 'asc' }, { column: 'id', direction: 'desc' }],
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(db, { entityService, columns? | keyOf?, sortColumns, slotReleasePolicy?, bucketSlotStoreService? })`
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(db, {
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, [rankOfSpades], [], () => {});
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 `OIMDefaults.INDEX_KEYS_SERVICE.getValue()`).
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(db, { slotContentService: new OIMSlotContentPositionalArrayBucketService<CardSlot>({ bucketCommands: new OIMBucketCommands({ maxCommandsPerSlot: undefined }) }) });
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, [], [totalSlot], () => { console.log(totalSlot.computedValueService.getValueForSlot(totalSlot)); });
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 not released on the spot. It 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, [], [topColumn], () => { console.log(topColumn.value); });
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, [], [deck], () => render(deck.computedValueService.getValueForSlot(deck)));
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(db, { slotContentService: createOIMSlotContentEntityService<Settings>(db), columnNames: ['theme', 'fontSize'] });
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, [settingsSlot], [], () => { console.log(settingsSlot.entity?.theme); });
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 `OIMDefaults`, and `columnNames` names the
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, [entitySlot], [], body));
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
- **218 names. This list is `src/index.ts`; nothing else is exported.**
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
- `OIMContentChangeListenerService`, `IOIMContentChangeListener`, `IOIMStoreSlotListener`, `OIMNoStoreSlotListener`,
2084
+ `OIMSlotContentChangeListenerService`, `IOIMSlotContentChangeListener`, `IOIMStoreSlotListener`, `OIMNoStoreSlotListener`,
2080
2085
  `IOIMSlotSubscriptionListener`, `OIMNoSlotSubscriptionListener`.
2081
2086
 
2082
- **Slots and their owners** — `TOIMSlot`, `TOIMStoreSlot`, `TOIMSingleSlotStoreSlot`, `TOIMSlotEditable`, `TOIMKeyedSlot`,
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`, `createOIMEntityWriterInPlace`,
2092
- `createOIMEntityWriterReplacing`, `TOIMEntityWriterOptions`, `TOIMEntityInserter`,
2096
+ `TOIMEntitySlot`, `TOIMEntityContentSlot`, `createOIMSlotContentEntityService`; `IOIMEntityWriter`, `createOIMInPlaceEntityWriter`,
2097
+ `createOIMReplacingEntityWriter`, `TOIMEntityWriterOptions`, `TOIMEntityInserter`,
2093
2098
  `oimInsertEntityWithCopy`, `oimInsertEntityWithoutCopy`, `oimInsertEntityWithSanitizedCopy`, `oimPatchEntityColumns`,
2094
- `oimPatchEntityColumn`, `oimPatchEntityColumnsGuarded`, `oimPatchEntityColumnGuarded`, `oimPutEntityColumns`,
2095
- `oimPutEntityColumnsGuarded`, `TOIMEntityColumnName`, `TOIMEntityColumnValue`.
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`, `TOIMEffectBodyWithSourceTracking`,
2113
- `OIMEffectSourceTracker` (**type only**), `TOIMComputedSlot`, `TOIMComputedSources`, `TOIMDerivation`, `TOIMComputedStoreSlot`,
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** — the statics of `OIMDefaults`: `COLUMN_SLOT_STORE_SERVICE`,
2134
- `COMPUTED_COLUMN_SLOT_STORE_SERVICE`, `KEY_TREE_WALK_SCALAR`, `KEY_TREE_WALK_COMPOSITE`,
2135
- `STANDALONE_SLOT_OWNER`, `ENTITY_WRITER`, `ENTITY_SLOT_EMPTINESS`, `INDEX_KEYS_SERVICE`,
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