@oimdb/core 4.1.0 → 4.3.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 +59 -0
- package/README.md +218 -171
- package/dist/index.cjs +1073 -722
- package/dist/index.d.cts +267 -141
- package/dist/index.d.ts +267 -141
- package/dist/index.js +1056 -716
- package/llms.txt +365 -299
- 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 | 105563 B | 25051 B |
|
|
94
|
+
| One entity store slot: `OIMDB`, `createOIMSlotCollectionService`, `createOIMSlotContentEntityService`, `writeSlotContentByKey`, `getSlotByKey` | 36382 B | 9314 B |
|
|
95
|
+
| The same, plus one effect and `db.flush()` | 46546 B | 11711 B |
|
|
96
|
+
| The same, plus one index: `createOIMSlotCollectionIndexService` | 67554 B | 16458 B |
|
|
97
|
+
| An ordered bucket with commands: `OIMSlotContentPositionalArrayBucketService`, `OIMBucketCommands` | 49435 B | 12310 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`, the store aggregates and their factories, `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, `OIMSlotCollectionIndexStoreAggregate`, 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'
|
|
@@ -157,9 +169,8 @@ effectService.disposeEffect(effect);
|
|
|
157
169
|
|
|
158
170
|
`OIMDB` is the shared state and nothing else: one `clock` (`{ revision, epoch }`), one
|
|
159
171
|
`defaultChannelSlot` (a `TOIMChannelSlot`, the struct a channel's state lives in), one
|
|
160
|
-
`collectorSlot` (the unused-slot collector's slot and the release wheels, made on first use)
|
|
161
|
-
|
|
162
|
-
which it keeps as the default that every door below reads. It is an `IOIMChannelMap`, so
|
|
172
|
+
`collectorSlot` (the unused-slot collector's slot and the release wheels, made on first use). It holds
|
|
173
|
+
no defaults: those are an `OIMDefaults` passed to each door as `defaults`. It is an `IOIMChannelMap`, so
|
|
163
174
|
anything that takes a `channelMap` takes the `db`. `flush()` is
|
|
164
175
|
`OIMChannelService.INSTANCE.flush(db.defaultChannelSlot)`: it runs the pending frame, then the
|
|
165
176
|
unused-slot collector, then the after-each-flush handlers, in that order. It builds nothing.
|
|
@@ -183,8 +194,8 @@ Every mutation goes through a domain neuron. See
|
|
|
183
194
|
[With cnstra](https://oimdb.org/docs/guides/with-cnstra).
|
|
184
195
|
|
|
185
196
|
The doors are free functions, one module each, and each builds a **service**, once per
|
|
186
|
-
type; an instance is the store slot that service creates.
|
|
187
|
-
`db
|
|
197
|
+
type; an instance is the store slot that service creates. Every door takes ONE options object and no
|
|
198
|
+
`db`; the `db` goes to `createStoreSlot(db)`. Every collection is `createOIMSlotCollectionService` over a content
|
|
188
199
|
service, and every single is `createOIMSingleSlotService`; an array key needs a tree store
|
|
189
200
|
(`slotStoreService: new OIMSlotTreeStoreService(…)`, or `bucketSlotStoreService` on an index), because a
|
|
190
201
|
map-driven store compares arrays by reference. See [The six shapes](#the-six-shapes). The computed doors
|
|
@@ -192,15 +203,15 @@ take no `db`: a computed reaches its channel through the store slot it derives f
|
|
|
192
203
|
the `db` handed to `createComputedStoreSlot`.
|
|
193
204
|
|
|
194
205
|
```typescript signature
|
|
195
|
-
createOIMSlotContentEntityService<Card, TKey?, TColumnName?>(
|
|
206
|
+
createOIMSlotContentEntityService<Card, TKey?, TColumnName?>({ entityWriter?, slotEmptinessPolicy? }?)
|
|
196
207
|
new OIMSlotContentSetBucketService<TBucketMemberSlot, TKey?>({ bucketCommands? })
|
|
197
208
|
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? })
|
|
209
|
+
createOIMSlotCollectionService({ slotContentService, slotStoreService?, slotReleasePolicy?, slotSubscriptionListener?, columnSlotStoreService? | columnNames? })
|
|
210
|
+
createOIMSingleSlotService({ slotContentService, slotReleasePolicy?, slotSubscriptionListener?, columnSlotStoreService? | columnNames? })
|
|
211
|
+
createOIMSlotCollectionIndexService({ entityService, columns | keyOf, slotContentService?, bucketSlotStoreService?, slotReleasePolicy?, columnSlotStoreService? | columnNames? })
|
|
212
|
+
createOIMSlotCollectionSortedArrayIndexService({ entityService, sortColumns, columns | keyOf, bucketSlotStoreService?, slotReleasePolicy?, columnSlotStoreService? | columnNames? })
|
|
213
|
+
createOIMComputedService<TKey, TValue, TComputedStore?>({ createSources?, derive?, sameValueGuard?, releaseValue?, initialValue?, slotReleasePolicy?, computedSlotStoreService?, columnSlotStoreService? | columnNames? }) // a non-Map store: computedSlotStoreService required
|
|
214
|
+
createOIMSingleComputedService<TValue>({ createSources?, derive?, sameValueGuard?, releaseValue?, initialValue?, slotReleasePolicy?, columnSlotStoreService? | columnNames? })
|
|
204
215
|
createOIMSortedComputedService({ indexService, sortColumns?, comparator?, slotReleasePolicy?, computedSlotStoreService?, columnSlotStoreService? })
|
|
205
216
|
createOIMBucketMemoComputedService({ bucketCollectionService, slotReleasePolicy?, computedSlotStoreService?, columnSlotStoreService? })
|
|
206
217
|
|
|
@@ -213,27 +224,68 @@ computedService.createComputedStoreSlot(db, derive?, createSources?)
|
|
|
213
224
|
|
|
214
225
|
**The content service is where everything per type is fixed, once:** the entity writer and the emptiness rule.
|
|
215
226
|
The collection service adds the store service, the release policy and the column options, and builds the slot
|
|
216
|
-
owner from them and `
|
|
227
|
+
owner from them and `defaults.slotStamp`. Build the content service first when it is shared:
|
|
217
228
|
|
|
218
229
|
```typescript
|
|
219
|
-
const cardContentService = createOIMSlotContentEntityService<Card>(
|
|
220
|
-
const sameShapeCardService = createOIMSlotCollectionService(
|
|
230
|
+
const cardContentService = createOIMSlotContentEntityService<Card>();
|
|
231
|
+
const sameShapeCardService = createOIMSlotCollectionService({ slotContentService: cardContentService });
|
|
221
232
|
const sameShapeCards = sameShapeCardService.createStoreSlot(db);
|
|
222
233
|
```
|
|
223
234
|
|
|
235
|
+
### A store aggregate: the store slot with its store methods
|
|
236
|
+
|
|
237
|
+
A **store aggregate** is a store slot together with the methods of its service that take that
|
|
238
|
+
store slot first, so a call no longer passes it. `service.createStoreAggregate(storeSlot)` wraps a
|
|
239
|
+
store slot you already have; `createOIMSlotCollectionStoreAggregate(db, options)` and
|
|
240
|
+
`createOIMSingleSlotStoreAggregate(db, options)` build the service, the store slot and the aggregate
|
|
241
|
+
in one call, with the options of `createOIMSlotCollectionService` / `createOIMSingleSlotService`.
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
import { createOIMSlotCollectionStoreAggregate, createOIMSingleSlotStoreAggregate } from '@oimdb/core';
|
|
245
|
+
|
|
246
|
+
const deckStoreAggregate = createOIMSlotCollectionStoreAggregate(db, {
|
|
247
|
+
slotContentService: createOIMSlotContentEntityService<Card>(),
|
|
248
|
+
});
|
|
249
|
+
const aceSlot = deckStoreAggregate.writeSlotContentByKey('c9', { id: 'c9', deck: 'hearts', title: 'Ace', rank: 1 });
|
|
250
|
+
deckStoreAggregate.slotContentService.updateEntity(aceSlot, { rank: 14 });
|
|
251
|
+
deckStoreAggregate.getSlotByKey('c9') === aceSlot; // true
|
|
252
|
+
|
|
253
|
+
const cardStoreAggregate = cardService.createStoreAggregate(cards);
|
|
254
|
+
cardStoreAggregate.storeSlot === cards; // true: hooks, indexes and computeds still take the store slot
|
|
255
|
+
|
|
256
|
+
const focusStoreAggregate = createOIMSingleSlotStoreAggregate(db, {
|
|
257
|
+
slotContentService: createOIMSlotContentEntityService<{ isFocused: boolean }>(),
|
|
258
|
+
});
|
|
259
|
+
focusStoreAggregate.writeSingleSlotContent({ isFocused: true });
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
| Aggregate | Over | Methods |
|
|
263
|
+
|---|---|---|
|
|
264
|
+
| `OIMSlotCollectionStoreAggregate` | `OIMSlotCollectionService` | `findSlotByKey`, `hasSlotContentByKey`, `getSlotByKey`, `holdSlotByKey`, `getSlots`, `getSlotCount`, `openBatch`, `closeBatch`, `writeInBatch` |
|
|
265
|
+
| `OIMWritableSlotCollectionStoreAggregate` | `OIMWritableSlotCollectionService` | the above and `writeSlotContentByKey` |
|
|
266
|
+
| `OIMSingleSlotStoreAggregate` | `OIMSingleSlotService` | `findSingleSlot`, `hasSingleSlotContent`, `getSingleSlot`, `holdSingleSlot` |
|
|
267
|
+
| `OIMWritableSingleSlotStoreAggregate` | `OIMWritableSingleSlotService` | the above, `writeSingleSlotContent` and `clearSingleSlotContent` |
|
|
268
|
+
| `OIMSlotCollectionIndexStoreAggregate` | `OIMSlotCollectionIndexService` | the collection methods, `getEntitiesByKey`, `getSlotsByKeys`, `holdSlotsByKeys`, `getBucketColumnByKey`, `holdBucketColumnByKey`, `getAnyBucketColumnByKey`, `holdAnyBucketColumnByKey`, `disposeIndexStoreSlot` |
|
|
269
|
+
|
|
270
|
+
Every aggregate also carries `storeSlot`, `slotService` (typed as the service it came from) and
|
|
271
|
+
`slotContentService`. A method that takes a slot rather than the store slot stays on its service.
|
|
272
|
+
Each aggregate method calls the service with the store slot; the service and the store slot stay
|
|
273
|
+
usable directly. `createOIMSlotCollectionStoreAggregate` builds a new service per call, so for
|
|
274
|
+
many store slots of one type build the service once and call `createStoreAggregate` on it.
|
|
275
|
+
|
|
224
276
|
The bucket doors take the type of the bucket member slots, `TBucketMemberSlot extends TOIMSlot`
|
|
225
277
|
(default `TOIMEntitySlot`). A bucket holds bucket member slots of any slot type (entity slots,
|
|
226
278
|
bucket slots, computeds), and an index's `getEntitiesByKey` is there because its members are
|
|
227
279
|
entity slots.
|
|
228
280
|
|
|
229
|
-
Each door
|
|
230
|
-
|
|
231
|
-
reaches for a default of its own.
|
|
281
|
+
Each door resolves every optional dependency as its own option, then `defaults` (or
|
|
282
|
+
`OIMDefaults.INSTANCE`), then the built-in `OIMDefaultValue`, and **always passes the result**, so
|
|
283
|
+
there is one answer to each question and no class reaches for a default of its own.
|
|
232
284
|
|
|
233
285
|
The doors are free functions, not methods on `OIMDB`, so a consumer who wires
|
|
234
286
|
nothing does not import the bucket machinery. This is pinned by
|
|
235
287
|
`tests/what-an-import-retains.test.ts` (the root retains no door) and by
|
|
236
|
-
`src/
|
|
288
|
+
`src/db/tests/which-answer-a-collection-the-root-mints-gets.test.ts`.
|
|
237
289
|
|
|
238
290
|
**A bucket's content service decides how it holds its slots.** `createOIMSlotCollectionService` takes it
|
|
239
291
|
as `slotContentService`. With none, it builds an `OIMSlotContentPositionalArrayBucketService`
|
|
@@ -255,8 +307,8 @@ live** (`slotStoreService`, the strategy every store slot of the service files i
|
|
|
255
307
|
with), **how a key comes out of an entity** (`keyOf`), **how a write lands** (`entityWriter`),
|
|
256
308
|
**what counts as empty** (`slotEmptinessPolicy`), **when a slot goes** (`slotReleasePolicy`), and
|
|
257
309
|
**where column slots live** (`columnSlotStoreService`, or `columnNames` for the ordinal store; the
|
|
258
|
-
slot owner makes its own column owner over it). Everything but `keyOf` falls back to
|
|
259
|
-
to a lazy `OIMDefaultValue`
|
|
310
|
+
slot owner makes its own column owner over it). Everything but `keyOf` falls back to `defaults`, then
|
|
311
|
+
to a lazy `OIMDefaultValue` (see [`OIMDefaults`](#every-optional-dependency-has-a-default)).
|
|
260
312
|
|
|
261
313
|
---
|
|
262
314
|
|
|
@@ -267,7 +319,7 @@ There is no argument that turns delivery off. A store slot is created on a `db`
|
|
|
267
319
|
unused-slot collector a read hands its mint to is that channel slot's `collectorSlot`.
|
|
268
320
|
|
|
269
321
|
```typescript
|
|
270
|
-
const deliveringCardService = createOIMSlotCollectionService(
|
|
322
|
+
const deliveringCardService = createOIMSlotCollectionService({ slotContentService: createOIMSlotContentEntityService<Card>(), slotStoreService: new OIMSlotMapStoreService<TOIMEntitySlot<Card, TOIMStoreKey>>() });
|
|
271
323
|
const deliveringCards = deliveringCardService.createStoreSlot(db);
|
|
272
324
|
```
|
|
273
325
|
|
|
@@ -277,11 +329,11 @@ and that constructor builds the type's `OIMSlotOwnerService`. The owner asks the
|
|
|
277
329
|
through `IOIMSlotContentService` (`isSlotEmpty`, `releaseSlotContent`, `hasReference`); the collection
|
|
278
330
|
service mints through the content service's `createSlot`.
|
|
279
331
|
|
|
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
|
-
`
|
|
332
|
+
`keyOf` is the one required option. `entityWriter` falls back to `defaults.entityWriter`, then to
|
|
333
|
+
the in-place writer; `slotEmptinessPolicy` to `defaults.entitySlotEmptinessPolicy`, then to "the entity
|
|
334
|
+
is `undefined`"; `slotReleasePolicy` to `defaults.slotReleasePolicy`, then to nothing, which is the
|
|
335
|
+
default of "a slot with a value stays for good" described under **The injected seams**; the column store
|
|
336
|
+
to `defaults.columnSlotStoreService`, then to a shared map-driven store.
|
|
285
337
|
|
|
286
338
|
A single entity is the same content service under a `void` key, built by
|
|
287
339
|
`createOIMSingleSlotService`, and it reports the same way.
|
|
@@ -290,38 +342,34 @@ A single entity is the same content service under a `void` key, built by
|
|
|
290
342
|
|
|
291
343
|
## Every optional dependency has a default
|
|
292
344
|
|
|
293
|
-
`OIMDefaults`
|
|
294
|
-
|
|
345
|
+
`OIMDefaults` holds the strategies a factory falls back on when its own options leave them out. Build
|
|
346
|
+
one with the strategies you want shared and pass it as the `defaults` option of every factory that
|
|
347
|
+
should use them; a factory given none uses `OIMDefaults.INSTANCE`, which is empty. A field left
|
|
348
|
+
undefined falls back to the built-in default.
|
|
295
349
|
|
|
296
|
-
|
|
|
350
|
+
| Field | Type | Built-in default |
|
|
297
351
|
|---|---|---|
|
|
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.
|
|
352
|
+
| `slotStamp` | `IOIMSlotStamp<TOIMSlot>` | none: no stamp |
|
|
353
|
+
| `slotReleasePolicy` | `IOIMSlotReleasePolicy` | none: a slot with a value stays for good |
|
|
354
|
+
| `entityWriter` | `IOIMEntityWriter` | `createOIMInPlaceEntityWriter()` |
|
|
355
|
+
| `entitySlotEmptinessPolicy` | `IOIMSlotEmptinessPolicy<TOIMEntityContentSlot>` | "empty means `slot.entity === undefined`" |
|
|
356
|
+
| `indexKeysService` | `IOIMIndexKeysService` | an `OIMAppendingIndexKeysService`; see [Index keys strategy](#index-keys-strategy) |
|
|
357
|
+
| `columnSlotStoreService` | `IOIMSlotStoreService<string, TOIMColumnSlot>` | a shared map-driven store service for column slots |
|
|
358
|
+
| `computedColumnSlotStoreService` | `IOIMSlotStoreService<string, TOIMComputedColumnSlot>` | a shared map-driven store service for the column slots of computed services |
|
|
359
|
+
| `storeSlotListener` | `IOIMStoreSlotListener` | `OIMNoStoreSlotListener`, which hears nothing; a service calls `onStoreSlotCreated(storeSlot)` for each store slot it creates |
|
|
360
|
+
|
|
361
|
+
```ts
|
|
362
|
+
const replacingDefaults = new OIMDefaults({ entityWriter: createOIMReplacingEntityWriter() });
|
|
363
|
+
const replacingCardService = createOIMSlotCollectionService({
|
|
364
|
+
defaults: replacingDefaults,
|
|
365
|
+
slotContentService: createOIMSlotContentEntityService<Card>({ defaults: replacingDefaults }),
|
|
366
|
+
});
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
**The built-in defaults cost nothing until used.** Each is an `OIMDefaultValue` whose `getValue()`
|
|
370
|
+
builds it on its first call and hands back the same instance for the rest of the process, and
|
|
371
|
+
`OIMDefaults` itself imports none of them, so a bundle carries only the defaults its factories read.
|
|
372
|
+
`src/db/tests/which-answer-a-collection-the-root-mints-gets.test.ts` pins which door reads which.
|
|
325
373
|
|
|
326
374
|
**One module names every default, so calling one `getValue()` retains all of them.** A
|
|
327
375
|
service drags the map-driven store service and both key tree walks into a
|
|
@@ -346,7 +394,7 @@ const body = (): void => {};
|
|
|
346
394
|
cardService.writeSlotContentByKey(cards, card.id, card); // write: the value is the reference
|
|
347
395
|
cardService.writeSlotContentByKey(cards, key, card); // write: the same door, by key
|
|
348
396
|
const entitySlot = cardService.holdSlotByKey(cards, key); // hold: a counted reference; give it back with entitySlot.owner.unholdSlot(entitySlot)
|
|
349
|
-
effectService.createEffect(db,
|
|
397
|
+
effectService.createEffect(db, body, [entitySlot]); // subscribe: the effect is the reference
|
|
350
398
|
const rankColumn = cardService.columnSlotService.holdColumn(entitySlot, 'rank'); // hold, on a column
|
|
351
399
|
cardService.columnSlotService.unholdColumn(rankColumn); // give the column back
|
|
352
400
|
cardService.getSlotByKey(cards, key); // read: the collector's hold is the reference
|
|
@@ -446,7 +494,7 @@ about a slot through `IOIMSlotContentService`, never through an override:
|
|
|
446
494
|
|---|---|
|
|
447
495
|
| `createSlot(key, storeSlot, slotOwner)` / `createSingleSlot(storeSlot, slotOwner)` | a key (or a single) is minted, by the collection service |
|
|
448
496
|
| `isSlotEmpty(slot)` | emptiness is settled after a write |
|
|
449
|
-
| `releaseSlotContent(slot
|
|
497
|
+
| `releaseSlotContent(slot)` | the identity is going. Give back what its content held. Throws on a held slot |
|
|
450
498
|
| `hasReference(slot)` | something outside `holdCount` and `effects` may keep it |
|
|
451
499
|
|
|
452
500
|
The content service's write doors reach the owner through `slot.owner`; the owner files and unfiles
|
|
@@ -457,12 +505,12 @@ through the store service.
|
|
|
457
505
|
```typescript signature
|
|
458
506
|
interface IOIMSlotEmptinessPolicy<TSlot> { isEmpty(slot: TSlot): boolean; }
|
|
459
507
|
|
|
460
|
-
interface IOIMSlotStamp<TSlot> { stamp(
|
|
508
|
+
interface IOIMSlotStamp<TSlot> { stamp(editableSlot: TOIMEditable<TSlot>): void; }
|
|
461
509
|
```
|
|
462
510
|
|
|
463
511
|
**`IOIMSlotEmptinessPolicy`** decides what counts as empty. The answer is written once
|
|
464
512
|
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`" (`
|
|
513
|
+
service defaults to "the entity is `undefined`" (or `defaults.entitySlotEmptinessPolicy`); a bucket
|
|
466
514
|
content service answers "`getBucketMemberSlotCount(bucketSlot) === 0`"; a computed answers
|
|
467
515
|
`false`.
|
|
468
516
|
|
|
@@ -476,8 +524,8 @@ settled, and it chains: each stamp takes the `next` one and calls it first. Use
|
|
|
476
524
|
for stamps that have nothing to do with release; release has its own policy.
|
|
477
525
|
|
|
478
526
|
**`IOIMSlotReleasePolicy`** decides whether and when a slot goes, and does no
|
|
479
|
-
work itself. A door and `
|
|
480
|
-
`
|
|
527
|
+
work itself. A door and an `OIMDefaults` take ONE through `slotReleasePolicy`; a door given none takes
|
|
528
|
+
`defaults.slotReleasePolicy`. With neither, the slot owner is built with `OIMSlotAtEmptyReleasePolicy`:
|
|
481
529
|
a slot with a value stays for good. A computed service defaults to
|
|
482
530
|
`OIMSlotAtUnholdReleasePolicy`. Two readonly flags on the policy, `releasesWrittenSlots` and
|
|
483
531
|
`schedulesReleaseOnWrite`, are read once by the owner at construction, so a policy that never
|
|
@@ -496,7 +544,7 @@ A slot goes on the SOONEST of those that are set, and a hold outranks them all.
|
|
|
496
544
|
**One number cannot carry both.** A write arriving after the subscriber left
|
|
497
545
|
would overwrite the idle policy's verdict, and the last subscriber leaving after a
|
|
498
546
|
write would overwrite the write policy's. Both directions are pinned by
|
|
499
|
-
`src/
|
|
547
|
+
`src/collector/tests/which-release-marker-each-collector-may-move.test.ts`.
|
|
500
548
|
|
|
501
549
|
**The POLICY answers `isSlotReleasable(slot)`**, and only for a slot that is not
|
|
502
550
|
empty and has nothing left keeping it (no holds, no references). `true` releases
|
|
@@ -554,7 +602,7 @@ leaves at the end of the frame. A window above zero is filed in that policy's ow
|
|
|
554
602
|
queue.
|
|
555
603
|
|
|
556
604
|
```typescript
|
|
557
|
-
const
|
|
605
|
+
const releasingDefaults = new OIMDefaults({
|
|
558
606
|
slotReleasePolicy: new OIMSlotEarliestReleasePolicy([
|
|
559
607
|
new OIMSlotAfterWriteReleasePolicy({ keepAfterWriteMs: 60_000 }),
|
|
560
608
|
new OIMSlotAfterIdleReleasePolicy({ keepAfterIdleMs: 30_000 }),
|
|
@@ -612,7 +660,7 @@ does not re-file it. Two consequences follow:
|
|
|
612
660
|
that those entities' own expired windows already answer for, so the entities go in the
|
|
613
661
|
same sweep.
|
|
614
662
|
|
|
615
|
-
Pinned by `src/
|
|
663
|
+
Pinned by `src/collector/tests/what-a-stale-index-takes-with-it.test.ts`,
|
|
616
664
|
`…/what-an-identity-nobody-subscribed-to-costs-after-a-while.test.ts` and
|
|
617
665
|
`…/what-a-subscribed-row-costs-the-release-clock.test.ts`.
|
|
618
666
|
|
|
@@ -703,7 +751,7 @@ gives them back when released, so the arena holds the top and the chain holds
|
|
|
703
751
|
itself. Use one arena per screen, per component or per job: a scope's cache pins what
|
|
704
752
|
that scope read and nothing else.
|
|
705
753
|
|
|
706
|
-
Pinned by `src/
|
|
754
|
+
Pinned by `src/collector/tests/what-a-named-arena-keeps-cached.test.ts`.
|
|
707
755
|
|
|
708
756
|
Neither policy means *unread*: no read is reported to the clock. A subscriber
|
|
709
757
|
never loses data mid-read, because an effect holds every root it reaches, so the
|
|
@@ -729,8 +777,8 @@ a store slot that holds one slot under no key, or many, a store slot that files
|
|
|
729
777
|
|
|
730
778
|
| kind | single | collection |
|
|
731
779
|
|---|---|---|
|
|
732
|
-
| entity | `createOIMSingleSlotService<TEntity>(
|
|
733
|
-
| bucket | `createOIMSingleSlotService<TMember>(
|
|
780
|
+
| entity | `createOIMSingleSlotService<TEntity>(…)`, then `createStoreSlot(db)` | `createOIMSlotCollectionService<TEntity>({ keyOf, … })`, then `createStoreSlot(db)` |
|
|
781
|
+
| bucket | `createOIMSingleSlotService<TMember>({ slotContentService?, … })`, then `createStoreSlot(db)` | `createOIMSlotCollectionService<TMember>({ slotContentService?, … })`, then `createStoreSlot(db)` |
|
|
734
782
|
| 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
783
|
|
|
736
784
|
A single is not a second content API. Its content service is the same class with `TKey = void`,
|
|
@@ -794,10 +842,10 @@ argument, not a subclass.
|
|
|
794
842
|
new OIMSlotMapStoreService<TOIMEntitySlot<Card, TOIMStoreKey>>()
|
|
795
843
|
|
|
796
844
|
// A composite key of VALUES, descended one composite key part at a time.
|
|
797
|
-
new OIMSlotTreeStoreService<TOIMStoreKey, TOIMKeyedSlot<readonly TOIMStoreKey[]>>(
|
|
845
|
+
new OIMSlotTreeStoreService<TOIMStoreKey, TOIMKeyedSlot<readonly TOIMStoreKey[]>>(OIMScalarKeyTreeWalk.INSTANCE)
|
|
798
846
|
|
|
799
847
|
// A composite key of IDENTITIES, descended the same way — and holding each.
|
|
800
|
-
new OIMSlotTreeStoreService<TOIMEntitySlot, TOIMKeyedSlot<readonly TOIMEntitySlot[]>>(
|
|
848
|
+
new OIMSlotTreeStoreService<TOIMEntitySlot, TOIMKeyedSlot<readonly TOIMEntitySlot[]>>(OIMCompositeKeyTreeWalk.INSTANCE)
|
|
801
849
|
```
|
|
802
850
|
|
|
803
851
|
There are two stores, one per shape of address, and no store is ever asked at run time
|
|
@@ -819,8 +867,8 @@ type TScalarChain = readonly TOIMStoreKey[];
|
|
|
819
867
|
type Tally = { deck: string; day: string; count: number };
|
|
820
868
|
type TTallyStore = TOIMSlotStoreTree<TOIMEntitySlot<Tally, TScalarChain>, TOIMStoreKey>;
|
|
821
869
|
|
|
822
|
-
const tallyService = createOIMSlotCollectionService(
|
|
823
|
-
|
|
870
|
+
const tallyService = createOIMSlotCollectionService({ slotContentService: createOIMSlotContentEntityService<Tally, TScalarChain>(), slotStoreService: new OIMSlotTreeStoreService<TOIMStoreKey, TOIMEntitySlot<Tally, TScalarChain>>(
|
|
871
|
+
OIMScalarKeyTreeWalk.INSTANCE,
|
|
824
872
|
) });
|
|
825
873
|
const tallies = tallyService.createStoreSlot(db);
|
|
826
874
|
tallyService.writeSlotContentByKey(tallies, ((t) => [t.deck, t.day])({ deck: 'spades', day: 'mon', count: 1 }), { deck: 'spades', day: 'mon', count: 1 });
|
|
@@ -842,17 +890,17 @@ type Enrolment = {
|
|
|
842
890
|
};
|
|
843
891
|
type TChain = readonly TOIMEntitySlot[];
|
|
844
892
|
|
|
845
|
-
const userService = createOIMSlotCollectionService(
|
|
893
|
+
const userService = createOIMSlotCollectionService({ slotContentService: createOIMSlotContentEntityService<User>() });
|
|
846
894
|
const users = userService.createStoreSlot(db);
|
|
847
|
-
const groupService = createOIMSlotCollectionService(
|
|
895
|
+
const groupService = createOIMSlotCollectionService({ slotContentService: createOIMSlotContentEntityService<Group>() });
|
|
848
896
|
const groups = groupService.createStoreSlot(db);
|
|
849
897
|
const alice = userService.writeSlotContentByKey(users, 'alice', { id: 'alice', name: 'Alice' });
|
|
850
898
|
const admins = groupService.writeSlotContentByKey(groups, 'admins', { id: 'admins', name: 'Admins' });
|
|
851
899
|
|
|
852
900
|
type TEnrolmentStore = TOIMSlotStoreTree<TOIMEntitySlot<Enrolment, TChain>, TOIMEntitySlot>;
|
|
853
901
|
|
|
854
|
-
const enrolmentService = createOIMSlotCollectionService(
|
|
855
|
-
|
|
902
|
+
const enrolmentService = createOIMSlotCollectionService({ slotContentService: createOIMSlotContentEntityService<Enrolment, TChain>(), slotStoreService: new OIMSlotTreeStoreService<TOIMEntitySlot, TOIMEntitySlot<Enrolment, TChain>>(
|
|
903
|
+
OIMCompositeKeyTreeWalk.INSTANCE,
|
|
856
904
|
) });
|
|
857
905
|
const enrolments = enrolmentService.createStoreSlot(db);
|
|
858
906
|
enrolmentService.writeSlotContentByKey(enrolments, ((m) => [m.user, m.group])({ user: alice, group: admins, since: 2026 }), { user: alice, group: admins, since: 2026 });
|
|
@@ -871,7 +919,7 @@ Both walks throw `an empty key is not a key` on `[]`.
|
|
|
871
919
|
**A scalar is not a composite key part of a composite-walk key.** It is a compile
|
|
872
920
|
error, and past a cast it throws inside the mint (`Cannot create property
|
|
873
921
|
'holdCount' on string`) with the edge already in the tree. Hand the store
|
|
874
|
-
`
|
|
922
|
+
`OIMScalarKeyTreeWalk.INSTANCE` instead of using a cast.
|
|
875
923
|
|
|
876
924
|
**Compare a chain with the strategy that matches it.** `oimSameSlotSequence` walks identities by
|
|
877
925
|
reference. Using it on a chain of values is the `NaN` defect: it answers
|
|
@@ -913,7 +961,7 @@ made once at construction and costs nothing per write:
|
|
|
913
961
|
| `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
962
|
|
|
915
963
|
```typescript
|
|
916
|
-
const copiedCardService = createOIMSlotCollectionService(
|
|
964
|
+
const copiedCardService = createOIMSlotCollectionService({ slotContentService: createOIMSlotContentEntityService<Card>({ entityWriter: createOIMInPlaceEntityWriter({ entityInserter: oimInsertEntityWithCopy }) }) });
|
|
917
965
|
```
|
|
918
966
|
|
|
919
967
|
**A copy is one level deep, and no deeper.** A nested object or
|
|
@@ -948,7 +996,7 @@ cardService.slotContentService.markEntityChanged(slot, ['title']);
|
|
|
948
996
|
**The hand mutation needs no cast.** `TOIMEntitySlot` declares
|
|
949
997
|
`readonly entity: TEntity | undefined`, and `readonly` forbids replacing the
|
|
950
998
|
object, not writing its columns. You still have to narrow the `| undefined`.
|
|
951
|
-
`
|
|
999
|
+
`TOIMEditable<TSlot>` is the type that strips `readonly` from the field
|
|
952
1000
|
itself, which is what assigning `slot.entity` needs.
|
|
953
1001
|
|
|
954
1002
|
**The list is the whole point, and leaving it out gives it back.** Named columns
|
|
@@ -1024,8 +1072,8 @@ interface IOIMEntityWriter {
|
|
|
1024
1072
|
readonly clearEntity: (slot: TOIMEntityContentSlot) => void;
|
|
1025
1073
|
}
|
|
1026
1074
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1075
|
+
createOIMInPlaceEntityWriter(options?) // the stored object keeps its identity
|
|
1076
|
+
createOIMReplacingEntityWriter(options?) // every write that changed something produces a new object
|
|
1029
1077
|
// TOIMEntityWriterOptions = { sameValueGuard?: TOIMSameValueGuard; entityInserter?: TOIMEntityInserter; putEntityColumns?: typeof oimPutEntityColumns }
|
|
1030
1078
|
```
|
|
1031
1079
|
|
|
@@ -1044,9 +1092,9 @@ A writer of your own can reuse the column walks that both shipped writers run,
|
|
|
1044
1092
|
`oimPatchEntityColumns`, `oimPatchEntityColumn` (one column, behind `patchColumn`) and
|
|
1045
1093
|
`oimPutEntityColumns`. The whole-entity put is itself the writers' `putEntityColumns` option:
|
|
1046
1094
|
`oimPutEntityColumns` is the default and skips neither `__proto__` keys nor inherited stored
|
|
1047
|
-
columns; `
|
|
1095
|
+
columns; `oimGuardedPutEntityColumns` keeps both guards, at a measured cost on every put, for
|
|
1048
1096
|
values that can carry a `__proto__` key or are class instances
|
|
1049
|
-
(`
|
|
1097
|
+
(`createOIMInPlaceEntityWriter({ putEntityColumns: oimGuardedPutEntityColumns })`). Each returns the object
|
|
1050
1098
|
`targetOf` handed back with the moved columns written into it, or `undefined`
|
|
1051
1099
|
when nothing moved, so the writer decides only where the result goes. The
|
|
1052
1100
|
[docs](https://oimdb.org/docs/core/writing#writing-your-own-writer) build one that
|
|
@@ -1058,7 +1106,7 @@ does not have are cleared to `undefined`, in one call, and the names it returns
|
|
|
1058
1106
|
only the columns the patch names. `writeSlotContentByKey` of a key that has a value and
|
|
1059
1107
|
`writeSlotContent(slot, entity)` are PUT, so a whole-entity write that drops a column writes
|
|
1060
1108
|
the slot to the channel once, and the replacing writer builds one new object, not
|
|
1061
|
-
two. This is counted in `src/
|
|
1109
|
+
two. This is counted in `src/entity/tests/what-a-put-that-drops-a-column-costs.test.ts`.
|
|
1062
1110
|
|
|
1063
1111
|
Neither writer `delete`s. A column that a PUT drops or `deleteColumns` takes away is
|
|
1064
1112
|
written `undefined`, into the stored object in place or into a spread copy when
|
|
@@ -1068,8 +1116,8 @@ replacing, so the entity keeps its hidden class: the key stays in
|
|
|
1068
1116
|
does not report it, and taking the entity away does not wake its readers.
|
|
1069
1117
|
|
|
1070
1118
|
The choice is made once, when the entity content service is built (`entityWriter`, else
|
|
1071
|
-
`
|
|
1072
|
-
is the answer. The default is `
|
|
1119
|
+
`defaults.entityWriter`), so there is no `if (inPlace)` anywhere on any write path; the object already
|
|
1120
|
+
is the answer. The default is `createOIMInPlaceEntityWriter()`. Pass `createOIMReplacingEntityWriter()` when
|
|
1073
1121
|
something downstream compares by reference.
|
|
1074
1122
|
|
|
1075
1123
|
---
|
|
@@ -1107,8 +1155,7 @@ the next read; a computed with no sources derives on its first read. A throw nev
|
|
|
1107
1155
|
for frames: the next write after an aborted frame asks for one. A throw in one
|
|
1108
1156
|
release of a sweep leaves the rest of that slice on the release clock for the next
|
|
1109
1157
|
sweep. A release that falls inside a flush (an effect body disposing another
|
|
1110
|
-
effect)
|
|
1111
|
-
flush is over. Code that keeps
|
|
1158
|
+
effect) happens at once: nothing holds the slot, so nothing hears it go. Code that keeps
|
|
1112
1159
|
derived state (an index's `keyOf`, an emptiness check) is outside the
|
|
1113
1160
|
policy: a throw there leaves state consistent and is thrown in either mode. An
|
|
1114
1161
|
index whose `keyOf` throws on an entity files that entity under no key, finishes the rest
|
|
@@ -1117,7 +1164,7 @@ of the write or batch, then rethrows; the next write it accepts files the entity
|
|
|
1117
1164
|
**Subscribing is building an effect.** There is no `subscribe` helper over this,
|
|
1118
1165
|
and there must not be one. An effect is stamped with the channel clock at birth,
|
|
1119
1166
|
so `effectService.createEffect` hears only what moves after it and never delivers a first
|
|
1120
|
-
value. `effectService.
|
|
1167
|
+
value. `effectService.createAndRunEffect(db, roots, computeds, body)` is the door that runs
|
|
1121
1168
|
the body once at creation. An effect is a plain `TOIMEffect` object with no methods (its
|
|
1122
1169
|
channel is `effect.channelSlot`); `OIMEffectService.INSTANCE` does everything to it. There is
|
|
1123
1170
|
one effect service for the whole process, and every door is handed the `db` (any
|
|
@@ -1125,14 +1172,14 @@ one effect service for the whole process, and every door is handed the `db` (any
|
|
|
1125
1172
|
|
|
1126
1173
|
```typescript
|
|
1127
1174
|
// Sources are named in two lists: root slots, and derivations.
|
|
1128
|
-
const effect = effectService.createEffect(db, [slot], [totalService.computedSingleSlotService.getSingleSlot(total)]
|
|
1175
|
+
const effect = effectService.createEffect(db, () => {}, [slot], [totalService.computedSingleSlotService.getSingleSlot(total)]);
|
|
1129
1176
|
effectService.disposeEffect(effect);
|
|
1130
1177
|
// The second list is typed `readonly TOIMComputedSlot[]` — pass slots taken from
|
|
1131
1178
|
// a computed service's `computedCollectionService` straight in.
|
|
1132
1179
|
|
|
1133
1180
|
// One column of one slot, woken when that named column moves and by nothing else.
|
|
1134
1181
|
// It takes the COLUMN SLOT, which the column owner mints and holds.
|
|
1135
|
-
const onRank = effectService.createEffect(db, [cardService.columnSlotService.holdColumn(slot, 'rank')]
|
|
1182
|
+
const onRank = effectService.createEffect(db, () => {}, [cardService.columnSlotService.holdColumn(slot, 'rank')]);
|
|
1136
1183
|
```
|
|
1137
1184
|
|
|
1138
1185
|
The body's type is `TOIMEffectBody`, which is `() => (() => void) | void`. **A returned
|
|
@@ -1142,10 +1189,10 @@ so a body that opened something on each run has somewhere to close it.
|
|
|
1142
1189
|
```typescript
|
|
1143
1190
|
const poll = (): void => {};
|
|
1144
1191
|
|
|
1145
|
-
effectService.createEffect(db,
|
|
1192
|
+
effectService.createEffect(db, () => {
|
|
1146
1193
|
const timer = setInterval(poll, 1000);
|
|
1147
1194
|
return () => clearInterval(timer);
|
|
1148
|
-
});
|
|
1195
|
+
}, [slot]);
|
|
1149
1196
|
```
|
|
1150
1197
|
|
|
1151
1198
|
The return is checked with `typeof` rather than for truthiness, because an
|
|
@@ -1153,16 +1200,16 @@ expression-bodied arrow hands back whatever the expression evaluated to
|
|
|
1153
1200
|
(`() => set.add(key)` returns the `Set`), and anything that is not a function is
|
|
1154
1201
|
discarded.
|
|
1155
1202
|
|
|
1156
|
-
**An effect can also find its sources as it reads.** `
|
|
1157
|
-
takes no source lists: the body gets an `
|
|
1203
|
+
**An effect can also find its sources as it reads.** `createSourceTrackingEffect(db, body)`
|
|
1204
|
+
takes no source lists: the body gets an `OIMSourceTracker`, and what it reads through
|
|
1158
1205
|
`readSourceSlot(slot)` (which files the slot and returns it) and
|
|
1159
1206
|
`readSourceComputedValue(computedSlot)` (which files the derivation and returns its value)
|
|
1160
|
-
is what that run depends on. The body's type is `
|
|
1207
|
+
is what that run depends on. The body's type is `TOIMSourceTrackingEffectBody`.
|
|
1161
1208
|
|
|
1162
1209
|
```typescript
|
|
1163
|
-
effectService.
|
|
1164
|
-
const isFirst =
|
|
1165
|
-
console.log(isFirst ?
|
|
1210
|
+
effectService.createSourceTrackingEffect(db, sourceTracker => {
|
|
1211
|
+
const isFirst = sourceTracker.readSourceSlot(slot).entity?.rank === 1;
|
|
1212
|
+
console.log(isFirst ? sourceTracker.readSourceSlot(slot).entity?.title : 'not first');
|
|
1166
1213
|
});
|
|
1167
1214
|
```
|
|
1168
1215
|
|
|
@@ -1226,13 +1273,13 @@ frozen object per set of names on that service; get it once per view, not per en
|
|
|
1226
1273
|
and `unholdColumn(columnSlot)` gives it back. A write wakes it once if it changes any of the
|
|
1227
1274
|
group's columns and not at all if it changes only others.
|
|
1228
1275
|
|
|
1229
|
-
**`columnNames` picks the column slot store.** `createOIMSlotCollectionService<Card, 'rank' | 'title'>(
|
|
1276
|
+
**`columnNames` picks the column slot store.** `createOIMSlotCollectionService<Card, 'rank' | 'title'>({ keyOf, columnNames: ['rank', 'title'] })`
|
|
1230
1277
|
keeps each entity's column slots in an array by column number instead of a `Map` per entity,
|
|
1231
1278
|
which makes an entity's column store cheaper to create and give back; that is what a scrolling view pays.
|
|
1232
1279
|
`holdColumn` and `getColumnGroup` are typed on the named columns, and a slot for any other
|
|
1233
1280
|
column throws. `columnNames` is on the options of every door — entity, bucket, index, computed —
|
|
1234
1281
|
keyed or single. Without `columnNames` the store is the `columnSlotStoreService` option, and the
|
|
1235
|
-
doors default it to the shared map-driven
|
|
1282
|
+
doors default it to the shared map-driven a shared map-driven store.
|
|
1236
1283
|
|
|
1237
1284
|
### Frame rules
|
|
1238
1285
|
|
|
@@ -1287,10 +1334,10 @@ doors default it to the shared map-driven `OIMDefaults.COLUMN_SLOT_STORE_SERVICE
|
|
|
1287
1334
|
const paint = (card: Card | undefined): void => {};
|
|
1288
1335
|
const measureAndScroll = (): void => {};
|
|
1289
1336
|
|
|
1290
|
-
effectService.createEffect(db,
|
|
1337
|
+
effectService.createEffect(db, () => {
|
|
1291
1338
|
paint(slot.entity);
|
|
1292
1339
|
OIMChannelService.INSTANCE.subscribeOnceAfterFrame(db.defaultChannelSlot, () => { measureAndScroll(); });
|
|
1293
|
-
});
|
|
1340
|
+
}, [slot]);
|
|
1294
1341
|
```
|
|
1295
1342
|
|
|
1296
1343
|
The handler is told after **one** frame, the first that ends after the call, and is then gone.
|
|
@@ -1303,7 +1350,7 @@ inside its effect and is answered at the end of that same frame.
|
|
|
1303
1350
|
|
|
1304
1351
|
It is **not** `subscribeAfterEachFrame` plus a disposal. The effect runs again
|
|
1305
1352
|
next frame and registers again, so a standing handler accumulates one more per run:
|
|
1306
|
-
`src/
|
|
1353
|
+
`src/channel/tests/a-handler-that-wants-one-frame-and-not-every-frame.test.ts` measures the
|
|
1307
1354
|
one-shot at 1, 2, 3 calls over three frames against the standing handler's 1, 3,
|
|
1308
1355
|
6. The one-shot handler also comes off *before* its body runs, so a handler that throws is not left
|
|
1309
1356
|
standing for the next frame.
|
|
@@ -1374,7 +1421,7 @@ computeds), and the bucket's type parameter is that slot type,
|
|
|
1374
1421
|
`getEntitiesByKey` is there because its members are entity slots.
|
|
1375
1422
|
|
|
1376
1423
|
```typescript
|
|
1377
|
-
const byDeckService = createOIMSlotCollectionService(
|
|
1424
|
+
const byDeckService = createOIMSlotCollectionService({ slotContentService: new OIMSlotContentSetBucketService<CardSlot>() });
|
|
1378
1425
|
const byDeck = byDeckService.createStoreSlot(db);
|
|
1379
1426
|
|
|
1380
1427
|
const aceSlot = cardService.writeSlotContentByKey(cards, 'c1', { id: 'c1', deck: 'red', title: 'Ace', rank: 1 });
|
|
@@ -1483,7 +1530,7 @@ disagree. With the unique one `hasBucketMemberSlot` answers from the `Set`; with
|
|
|
1483
1530
|
|
|
1484
1531
|
### Positions the caller decides, and the commands that record them
|
|
1485
1532
|
|
|
1486
|
-
`createOIMSlotCollectionService<CardSlot>(
|
|
1533
|
+
`createOIMSlotCollectionService<CardSlot>()` with no `slotContentService` is the bucket collection to
|
|
1487
1534
|
take when the positions are yours: its default content service is
|
|
1488
1535
|
`OIMSlotContentPositionalArrayBucketService` with an `OIMBucketCommands` recording over it.
|
|
1489
1536
|
|
|
@@ -1529,17 +1576,17 @@ from the bucket's for good.
|
|
|
1529
1576
|
consumer patches instead of rebuilding:
|
|
1530
1577
|
|
|
1531
1578
|
```typescript
|
|
1532
|
-
const pageService = createOIMSlotCollectionService(
|
|
1579
|
+
const pageService = createOIMSlotCollectionService({ slotContentService: new OIMSlotContentPositionalArrayBucketService<CardSlot>({ bucketCommands: new OIMBucketCommands({ maxCommandsPerSlot: undefined }) }) });
|
|
1533
1580
|
const page = pageService.createStoreSlot(db);
|
|
1534
1581
|
const bucket = pageService.holdSlotByKey(page, 'p1');
|
|
1535
1582
|
|
|
1536
1583
|
const rebuildFrom = (slots: Iterable<CardSlot>): void => {};
|
|
1537
1584
|
const applyToTheRenderedList = (commands: readonly TOIMOrderedListCommand[]): void => {};
|
|
1538
1585
|
|
|
1539
|
-
effectService.createEffect(db,
|
|
1586
|
+
effectService.createEffect(db, () => {
|
|
1540
1587
|
if (bucket.isWholeListRecorded) rebuildFrom(pageService.slotContentService.getBucketMemberSlots(bucket));
|
|
1541
1588
|
else applyToTheRenderedList(bucket.commands ?? []);
|
|
1542
|
-
});
|
|
1589
|
+
}, [bucket]);
|
|
1543
1590
|
```
|
|
1544
1591
|
|
|
1545
1592
|
The content service already writes the bucket slot on every mutation, so the consumer
|
|
@@ -1584,7 +1631,7 @@ per-bucket state, so **one instance may serve two bucket collections**.
|
|
|
1584
1631
|
### The other strategy: an index that fills itself
|
|
1585
1632
|
|
|
1586
1633
|
```typescript
|
|
1587
|
-
const indexService = createOIMSlotCollectionIndexService(
|
|
1634
|
+
const indexService = createOIMSlotCollectionIndexService({
|
|
1588
1635
|
entityService: cardService,
|
|
1589
1636
|
keyOf: (card: Card) => card.deck, // which key an entity belongs to
|
|
1590
1637
|
});
|
|
@@ -1593,7 +1640,7 @@ const index = indexService.createIndexStoreSlot(cards);
|
|
|
1593
1640
|
const render = (slots: unknown[]): void => {};
|
|
1594
1641
|
|
|
1595
1642
|
const spades = indexService.holdSlotByKey(index, 'spades'); // the hold mints the bucket and fills it
|
|
1596
|
-
effectService.createEffect(db,
|
|
1643
|
+
effectService.createEffect(db, () => render(Array.from(indexService.slotContentService.getBucketMemberSlots(spades))), [spades]);
|
|
1597
1644
|
```
|
|
1598
1645
|
|
|
1599
1646
|
It **is** a bucket collection (`OIMSlotCollectionIndexService` extends `OIMSlotCollectionService`), and
|
|
@@ -1609,7 +1656,7 @@ index's buckets cannot be written from outside. The default content service is
|
|
|
1609
1656
|
new OIMSlotContentSetBucketService()` for buckets that keep no positions. Passing
|
|
1610
1657
|
`entityService` and then the entity store slot is the whole wiring: `createIndexStoreSlot(cards)`
|
|
1611
1658
|
registers a content-change listener on that store slot
|
|
1612
|
-
(`
|
|
1659
|
+
(`OIMSlotContentChangeListenerService.INSTANCE.addContentChangeListener(cards, …)`), and the entity
|
|
1613
1660
|
service knows nothing about indexes.
|
|
1614
1661
|
`disposeIndexStoreSlot(index)` takes that listener off, empties the buckets and gives back every
|
|
1615
1662
|
entity held. After that, `getSlotByKey` and `holdSlotByKey` throw, but the two give-backs
|
|
@@ -1635,14 +1682,14 @@ Know these four things before choosing it:
|
|
|
1635
1682
|
door (`getSlotByKey`, `holdSlotByKey`, `getSlotsByKeys`, `holdSlotsByKeys`) is filled from the source
|
|
1636
1683
|
entities written before it, without a flush, so minting inside an effect body does not
|
|
1637
1684
|
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 `
|
|
1685
|
+
collection's ordinary business: the `slotReleasePolicy` option, or `defaults.slotReleasePolicy`
|
|
1639
1686
|
when you name none; with neither, the bucket is kept for good. `OIMSlotAfterIdleReleasePolicy`
|
|
1640
1687
|
cools it on the queue, and `OIMSlotAtUnholdReleasePolicy.INSTANCE` ends it at
|
|
1641
1688
|
the last give-back. The index injects no lifetime of its own.
|
|
1642
1689
|
- **The key comes from `columns`, `keyOf`, or both**, the same pair as the sorter's
|
|
1643
1690
|
`sortColumns` / `comparator`. The key type is inferred from the columns (`TOIMIndexKey`):
|
|
1644
1691
|
`{ columns: ['deck'] }` is keyed by that one column's value and refuses an array;
|
|
1645
|
-
`createOIMSlotCollectionIndexService(
|
|
1692
|
+
`createOIMSlotCollectionIndexService({ entityService, columns: ['deck', 'suit'] })` is keyed by the
|
|
1646
1693
|
tuple of their values and builds the bucket collection on a tree-driven store
|
|
1647
1694
|
(`OIMSlotTreeStoreService`) by itself, while `createOIMSlotCollectionIndexService` without a
|
|
1648
1695
|
`bucketSlotStoreService` refuses two columns. When the index asks
|
|
@@ -1662,7 +1709,7 @@ Know these four things before choosing it:
|
|
|
1662
1709
|
### A sorted list is a computed over an index
|
|
1663
1710
|
|
|
1664
1711
|
```typescript
|
|
1665
|
-
const indexService = createOIMSlotCollectionIndexService(
|
|
1712
|
+
const indexService = createOIMSlotCollectionIndexService({
|
|
1666
1713
|
entityService: cardService,
|
|
1667
1714
|
keyOf: (card: Card) => card.deck,
|
|
1668
1715
|
});
|
|
@@ -1670,12 +1717,12 @@ const index = indexService.createIndexStoreSlot(cards);
|
|
|
1670
1717
|
|
|
1671
1718
|
const byRankInDeckService = createOIMSortedComputedService({
|
|
1672
1719
|
indexService,
|
|
1673
|
-
sortColumns: [{
|
|
1720
|
+
sortColumns: [{ columnName: 'rank', direction: 'asc' }],
|
|
1674
1721
|
});
|
|
1675
1722
|
const byRankInDeck = byRankInDeckService.createSortedStoreSlot(index);
|
|
1676
1723
|
|
|
1677
1724
|
const spades = byRankInDeckService.computedCollectionService.holdSlotByKey(byRankInDeck, 'spades');
|
|
1678
|
-
effectService.createEffect(db,
|
|
1725
|
+
effectService.createEffect(db, () => { console.log(spades.computedValueService.getValueForSlot(spades).map((entitySlot) => entitySlot.entity?.title)); }, [], [spades]);
|
|
1679
1726
|
spades.owner.unholdSlot(spades);
|
|
1680
1727
|
```
|
|
1681
1728
|
|
|
@@ -1688,7 +1735,7 @@ with that store slot first. A key's value, read with
|
|
|
1688
1735
|
`computedSlot.computedValueService.getValueForSlot(computedSlot)`, is that key's entity slots,
|
|
1689
1736
|
sorted. `indexService` is the `OIMSlotCollectionIndexService` whose buckets it sorts.
|
|
1690
1737
|
|
|
1691
|
-
- `sortColumns` is `readonly TOIMSortColumn<TEntity>[]`, each `{
|
|
1738
|
+
- `sortColumns` is `readonly TOIMSortColumn<TEntity>[]`, each `{ columnName, direction: 'asc' | 'desc' }`.
|
|
1692
1739
|
`column` is a `TOIMSortableColumnName<TEntity>`, meaning a column whose values are strings,
|
|
1693
1740
|
numbers or bigints. **Columns decide which writes re-sort.**
|
|
1694
1741
|
- `comparator` is `(left: TEntity, right: TEntity) => number`. **It decides the order.**
|
|
@@ -1721,17 +1768,17 @@ The other strategy keeps the index's own buckets in order, so a read needs no co
|
|
|
1721
1768
|
The consumer picks one at construction.
|
|
1722
1769
|
|
|
1723
1770
|
```typescript
|
|
1724
|
-
const cardsByDeckService = createOIMSlotCollectionSortedArrayIndexService(
|
|
1771
|
+
const cardsByDeckService = createOIMSlotCollectionSortedArrayIndexService({
|
|
1725
1772
|
entityService: cardService,
|
|
1726
1773
|
columns: ['deck'],
|
|
1727
|
-
sortColumns: [{
|
|
1774
|
+
sortColumns: [{ columnName: 'rank', direction: 'asc' }, { columnName: 'id', direction: 'desc' }],
|
|
1728
1775
|
});
|
|
1729
1776
|
const cardsByDeck = cardsByDeckService.createIndexStoreSlot(cards);
|
|
1730
1777
|
const spades = cardsByDeckService.holdSlotByKey(cardsByDeck, 'spades');
|
|
1731
1778
|
Array.from(cardsByDeckService.slotContentService.getBucketMemberSlots(spades));
|
|
1732
1779
|
```
|
|
1733
1780
|
|
|
1734
|
-
`createOIMSlotCollectionSortedArrayIndexService(
|
|
1781
|
+
`createOIMSlotCollectionSortedArrayIndexService({ entityService, columns? | keyOf?, sortColumns, slotReleasePolicy?, bucketSlotStoreService? })`
|
|
1735
1782
|
returns an `OIMSlotCollectionSortedArrayIndexService`, whose content service is an
|
|
1736
1783
|
`OIMSlotContentSortedArrayBucketService`. `sortColumns` is the same `readonly TOIMSortColumn<TEntity>[]`
|
|
1737
1784
|
that `createOIMSortedComputedService` takes.
|
|
@@ -1760,14 +1807,14 @@ bucket any-column slot doors (`getAnyBucketColumnByKey(indexStoreSlot, key)`,
|
|
|
1760
1807
|
the positional ones of the array-based kinds included.
|
|
1761
1808
|
|
|
1762
1809
|
```typescript
|
|
1763
|
-
const decksService = createOIMSlotCollectionIndexService(
|
|
1810
|
+
const decksService = createOIMSlotCollectionIndexService({
|
|
1764
1811
|
entityService: cardService,
|
|
1765
1812
|
keyOf: (card: Card) => card.deck,
|
|
1766
1813
|
});
|
|
1767
1814
|
const decks = decksService.createIndexStoreSlot(cards);
|
|
1768
1815
|
|
|
1769
1816
|
const rankOfSpades = decksService.holdBucketColumnByKey(decks, 'spades', 'rank');
|
|
1770
|
-
const onRankInSpades = effectService.createEffect(db,
|
|
1817
|
+
const onRankInSpades = effectService.createEffect(db, () => {}, [rankOfSpades]);
|
|
1771
1818
|
effectService.disposeEffect(onRankInSpades);
|
|
1772
1819
|
decksService.columnSlotService.unholdColumn(rankOfSpades);
|
|
1773
1820
|
```
|
|
@@ -1779,7 +1826,7 @@ leaves its old bucket in O(1). Only an index writes it, through its `IOIMIndexKe
|
|
|
1779
1826
|
(`get(slot, position)`, `set(slot, position, indexKey)`), at the position its index store
|
|
1780
1827
|
slot was given (`TOIMIndexStoreSlot.indexKeysPosition`, allocated from the source store slot's
|
|
1781
1828
|
`indexKeysPositions`). Choose the service with the `indexKeysService` option of `OIMDB`; every
|
|
1782
|
-
index the factories build on that `db` takes it (default `
|
|
1829
|
+
index the factories build on that `db` takes it (default `OIMAppendingIndexKeysService.INSTANCE`).
|
|
1783
1830
|
|
|
1784
1831
|
- `OIMAppendingIndexKeysService` (default): a disposed index's position is never
|
|
1785
1832
|
reused, and its stale entry stays on each entity slot. A slot's `indexKeys` grows to the highest
|
|
@@ -1796,7 +1843,7 @@ Creating indexes that way is expensive; better not to.
|
|
|
1796
1843
|
### Rendering an ordered bucket
|
|
1797
1844
|
|
|
1798
1845
|
```typescript setup
|
|
1799
|
-
const orderedBucketsService = createOIMSlotCollectionService(
|
|
1846
|
+
const orderedBucketsService = createOIMSlotCollectionService({ slotContentService: new OIMSlotContentPositionalArrayBucketService<CardSlot>({ bucketCommands: new OIMBucketCommands({ maxCommandsPerSlot: undefined }) }) });
|
|
1800
1847
|
const orderedBuckets = orderedBucketsService.createStoreSlot(db);
|
|
1801
1848
|
```
|
|
1802
1849
|
|
|
@@ -1875,7 +1922,7 @@ this engine (`createEffect`'s second argument, and `sourceComputeds` in
|
|
|
1875
1922
|
it is. Inside `derive`, `computedSlot.value` is the previous value:
|
|
1876
1923
|
|
|
1877
1924
|
```typescript
|
|
1878
|
-
effectService.createEffect(db,
|
|
1925
|
+
effectService.createEffect(db, () => { console.log(totalSlot.computedValueService.getValueForSlot(totalSlot)); }, [], [totalSlot]);
|
|
1879
1926
|
```
|
|
1880
1927
|
|
|
1881
1928
|
### One derivation per key
|
|
@@ -1908,9 +1955,7 @@ A derivation is never empty: `OIMSlotContentComputedService.isSlotEmpty` answers
|
|
|
1908
1955
|
so a filed derivation goes only when nothing holds it,
|
|
1909
1956
|
nothing watches it, and the grace is `0`. That is what
|
|
1910
1957
|
`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.
|
|
1958
|
+
A derivation let go of while the channel is flushing is released on the spot, like every other slot.
|
|
1914
1959
|
`createOIMComputedService` takes a `slotReleasePolicy` as an optional key of its options object.
|
|
1915
1960
|
|
|
1916
1961
|
`createOIMComputedService` is `OIMComputedService` with its generics
|
|
@@ -1941,7 +1986,7 @@ const cardSummary = cardSummaryService.createComputedStoreSlot(
|
|
|
1941
1986
|
);
|
|
1942
1987
|
const summarySingleSlotService = cardSummaryService.computedSingleSlotService;
|
|
1943
1988
|
const topColumn = summarySingleSlotService.columnSlotService.holdColumn(summarySingleSlotService.getSingleSlot(cardSummary), 'top');
|
|
1944
|
-
const onTop = effectService.createEffect(db,
|
|
1989
|
+
const onTop = effectService.createEffect(db, () => { console.log(topColumn.value); }, [], [topColumn]);
|
|
1945
1990
|
effectService.disposeEffect(onTop);
|
|
1946
1991
|
summarySingleSlotService.columnSlotService.unholdColumn(topColumn);
|
|
1947
1992
|
```
|
|
@@ -1959,7 +2004,7 @@ const order = orderService.createBucketMemoStoreSlot(orderedBuckets);
|
|
|
1959
2004
|
const render = (slots: readonly CardSlot[]): void => {};
|
|
1960
2005
|
|
|
1961
2006
|
const deck = orderService.computedCollectionService.holdSlotByKey(order, 'spades'); // mints and holds the bucket too
|
|
1962
|
-
effectService.createEffect(db,
|
|
2007
|
+
effectService.createEffect(db, () => render(deck.computedValueService.getValueForSlot(deck)), [], [deck]);
|
|
1963
2008
|
```
|
|
1964
2009
|
|
|
1965
2010
|
Its value is the bucket's slots in the bucket's order, produced on the read that
|
|
@@ -1981,14 +2026,14 @@ A sorted list is the other derivation of this kind:
|
|
|
1981
2026
|
```typescript
|
|
1982
2027
|
type Settings = { theme: string; fontSize: number };
|
|
1983
2028
|
|
|
1984
|
-
const settingsService = createOIMSingleSlotService(
|
|
2029
|
+
const settingsService = createOIMSingleSlotService({ slotContentService: createOIMSlotContentEntityService<Settings>(), columnNames: ['theme', 'fontSize'] });
|
|
1985
2030
|
const settings = settingsService.createStoreSlot(db);
|
|
1986
2031
|
const settingsContentService = settingsService.slotContentService;
|
|
1987
2032
|
settingsService.writeSingleSlotContent(settings, { theme: 'dark', fontSize: 14 });
|
|
1988
2033
|
const settingsSlot = settingsService.holdSingleSlot(settings);
|
|
1989
2034
|
|
|
1990
|
-
effectService.createEffect(db,
|
|
1991
|
-
effectService.createEffect(db, [settingsService.columnSlotService.holdColumn(settingsSlot, 'theme')]
|
|
2035
|
+
effectService.createEffect(db, () => { console.log(settingsSlot.entity?.theme); }, [settingsSlot]);
|
|
2036
|
+
effectService.createEffect(db, () => {}, [settingsService.columnSlotService.holdColumn(settingsSlot, 'theme')]);
|
|
1992
2037
|
|
|
1993
2038
|
settingsContentService.updateEntity(settingsSlot, { theme: 'light' });
|
|
1994
2039
|
settingsContentService.deleteEntityColumns(settingsSlot, ['fontSize']); // the thing a patch cannot say
|
|
@@ -1999,7 +2044,7 @@ db.flush();
|
|
|
1999
2044
|
A standalone record is a single service: `createOIMSingleSlotService` is
|
|
2000
2045
|
`createOIMSingleSlotService` over `createOIMSlotContentEntityService`, with the store fixed to
|
|
2001
2046
|
`OIMSingleSlotStoreService` and no `keyOf`. Every option is optional:
|
|
2002
|
-
`entityWriter` and `slotEmptinessPolicy` fall back to `
|
|
2047
|
+
`entityWriter` and `slotEmptinessPolicy` fall back to `defaults`, then to the built-in defaults, and `columnNames` names the
|
|
2003
2048
|
columns and files their slots in the ordinal store. The entity is
|
|
2004
2049
|
written through `slotContentService.writeSlotContentByKey(settings, entity)`, an ordinary write announced
|
|
2005
2050
|
to whoever subscribed, not handed to the options or to `createStoreSlot(db)`, which mints
|
|
@@ -2036,7 +2081,7 @@ const body = (): void => {};
|
|
|
2036
2081
|
const entitySlot = cardService.holdSlotByKey(cards, 'c1');
|
|
2037
2082
|
|
|
2038
2083
|
const disposers: TOIMDisposer[] = [];
|
|
2039
|
-
disposers.push(effectService.subscribeEffect(db,
|
|
2084
|
+
disposers.push(effectService.subscribeEffect(db, body, [entitySlot]));
|
|
2040
2085
|
disposers.push(() => { entitySlot.owner.unholdSlot(entitySlot); });
|
|
2041
2086
|
for (let i = disposers.length - 1; i >= 0; i--) disposers[i]();
|
|
2042
2087
|
```
|
|
@@ -2048,7 +2093,7 @@ a throwing disposer does not stop the rest. The engine ships no scope to do that
|
|
|
2048
2093
|
|
|
2049
2094
|
## Export reference
|
|
2050
2095
|
|
|
2051
|
-
**
|
|
2096
|
+
**237 names. This list is `src/index.ts`; nothing else is exported.**
|
|
2052
2097
|
|
|
2053
2098
|
**The channel slot** — `OIMChannelService`, `TOIMChannelSlot`, `TOIMChannelSlotOptions`, `createOIMChannelSlot`,
|
|
2054
2099
|
`IOIMChannelMap`, `TOIMChannelFrame`, `TOIMChannelClock`, `TOIMChannelScheduler`, `OIMChannelQueue` (**type only**),
|
|
@@ -2076,10 +2121,14 @@ a throwing disposer does not stop the rest. The engine ships no scope to do that
|
|
|
2076
2121
|
`TOIMSlotOwnerServiceOptions`, `IOIMSlotContentService`, `IOIMSlotContentWriter`, `IOIMKeyedSlotCreator`, `IOIMSingleSlotCreator`,
|
|
2077
2122
|
`IOIMSlotFilingService`, `TOIMSlotContentServiceKey`, `TOIMSlotContentServiceSlot`, `TOIMSlotContentServiceSingleSlot`,
|
|
2078
2123
|
`TOIMSlotContentServiceContent`, `TOIMSlotContentServicePatch`,
|
|
2079
|
-
`
|
|
2124
|
+
`OIMSlotContentChangeListenerService`, `IOIMSlotContentChangeListener`, `IOIMStoreSlotListener`, `OIMNoStoreSlotListener`,
|
|
2080
2125
|
`IOIMSlotSubscriptionListener`, `OIMNoSlotSubscriptionListener`.
|
|
2081
2126
|
|
|
2082
|
-
**
|
|
2127
|
+
**Store aggregates** — `OIMSlotCollectionStoreAggregate`, `OIMWritableSlotCollectionStoreAggregate`,
|
|
2128
|
+
`OIMSingleSlotStoreAggregate`, `OIMWritableSingleSlotStoreAggregate`, `createOIMSlotCollectionStoreAggregate`,
|
|
2129
|
+
`createOIMSingleSlotStoreAggregate`, `OIMSlotCollectionIndexStoreAggregate`.
|
|
2130
|
+
|
|
2131
|
+
**Slots and their owners** — `TOIMSlot`, `TOIMStoreSlot`, `TOIMSingleSlotStoreSlot`, `TOIMEditable`, `TOIMKeyedSlot`,
|
|
2083
2132
|
`TOIMIndexableSlot`, `OIMSlotOwner`, `IOIMSlotOwner`, `IOIMSlotEmptinessPolicy`, `IOIMSlotStamp`, `TOIMSameValueGuard`,
|
|
2084
2133
|
`oimSameValueZeroGuard`, `oimIsSlotEmpty`, `oimIsSlotReleased`, `oimHoldSlots`, `oimUnholdSlotsIfAlive`,
|
|
2085
2134
|
`OIM_FROZEN_EMPTY_SLOTS`.
|
|
@@ -2088,11 +2137,11 @@ a throwing disposer does not stop the rest. The engine ships no scope to do that
|
|
|
2088
2137
|
`OIMColumnSlotOwner`, `OIMPushColumnSlotOwner` (**type only**), `OIMPullColumnSlotOwner`, `TOIMColumnSlotOwnerOptions`.
|
|
2089
2138
|
|
|
2090
2139
|
**Entities** — `OIMSlotContentEntityService`, `TOIMSlotContentEntityServiceOptions`, `TOIMEntitySlotOwnerService`,
|
|
2091
|
-
`TOIMEntitySlot`, `TOIMEntityContentSlot`, `createOIMSlotContentEntityService`; `IOIMEntityWriter`, `
|
|
2092
|
-
`
|
|
2140
|
+
`TOIMEntitySlot`, `TOIMEntityContentSlot`, `createOIMSlotContentEntityService`; `IOIMEntityWriter`, `createOIMInPlaceEntityWriter`,
|
|
2141
|
+
`createOIMReplacingEntityWriter`, `TOIMEntityWriterOptions`, `TOIMEntityInserter`,
|
|
2093
2142
|
`oimInsertEntityWithCopy`, `oimInsertEntityWithoutCopy`, `oimInsertEntityWithSanitizedCopy`, `oimPatchEntityColumns`,
|
|
2094
|
-
`oimPatchEntityColumn`, `
|
|
2095
|
-
`
|
|
2143
|
+
`oimPatchEntityColumn`, `oimGuardedPatchEntityColumns`, `oimGuardedPatchEntityColumn`, `oimPutEntityColumns`,
|
|
2144
|
+
`oimGuardedPutEntityColumns`, `TOIMEntityColumnName`, `TOIMEntityColumnValue`.
|
|
2096
2145
|
|
|
2097
2146
|
**Buckets** — `OIMSlotContentBucketService`, `OIMSlotContentArrayBucketService`,
|
|
2098
2147
|
`OIMSlotContentPositionalArrayBucketService`, `OIMSlotContentSetBucketService`,
|
|
@@ -2109,8 +2158,8 @@ a throwing disposer does not stop the rest. The engine ships no scope to do that
|
|
|
2109
2158
|
`IOIMIndexKeysService`, `TOIMIndexKeysPositions`, `OIMIndexKeysService`, `OIMAppendingIndexKeysService`,
|
|
2110
2159
|
`OIMReclaimingIndexKeysService`.
|
|
2111
2160
|
|
|
2112
|
-
**The graph** — `OIMEffectService`, `IOIMEffectService`, `TOIMEffect`, `TOIMEffectBody`, `
|
|
2113
|
-
`
|
|
2161
|
+
**The graph** — `OIMEffectService`, `IOIMEffectService`, `TOIMEffect`, `TOIMEffectBody`, `TOIMSourceTrackingEffectBody`,
|
|
2162
|
+
`OIMSourceTracker` (**type only**), `TOIMComputedSlot`, `TOIMComputedSources`, `TOIMComputedValueReleaser`, `OIMSourceTrackingComputedService`, `OIMSingleSourceTrackingComputedService`, `createOIMSourceTrackingComputedService`, `createOIMSingleSourceTrackingComputedService`, `TOIMSourceTrackingComputedSlot`, `TOIMSourceTrackingComputedContentSlot`, `TOIMSourceTrackingComputedDerive`, `OIMSlotContentSourceTrackingComputedService` (**type only**), `TOIMSourceTrackingComputedDerivationOptions`, `TOIMComputedServiceFactoryOptions`, `TOIMComputedDerivation`, `TOIMComputedStoreSlot`,
|
|
2114
2163
|
`OIMComputedValueService`, `IOIMComputedValueService`, `TOIMComputedValueServiceOptions`, `TOIMComputedContentSlot`,
|
|
2115
2164
|
`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
2165
|
|
|
@@ -2125,16 +2174,14 @@ a throwing disposer does not stop the rest. The engine ships no scope to do that
|
|
|
2125
2174
|
`OIMSingleSlotStoreService`, `TOIMSingleSlotStore`, `TOIMDefaultSlotStore`, `TOIMSlotStoreTree`, `IOIMKeyTreeWalk`, `TOIMCompositeKeyNode`,
|
|
2126
2175
|
`OIMKeyTreeWalk`, `OIMCompositeKeyTreeWalk`, `OIMScalarKeyTreeWalk`.
|
|
2127
2176
|
|
|
2128
|
-
**The root and its defaults** — `OIMDB`, `TOIMDBOptions`, `OIMDefaults`, `OIMDefaultValue`, `TOIMFactoryOptions`.
|
|
2177
|
+
**The root and its defaults** — `OIMDB`, `TOIMDBOptions`, `OIMDefaults`, `TOIMDefaultsOptions`, `OIMDefaultValue`, `TOIMFactoryOptions`.
|
|
2129
2178
|
|
|
2130
2179
|
**Comparison and diffing** — `oimSameSlotSequence`, `oimSameKeySequence`, `oimOrderedListCommandsOfTransition`,
|
|
2131
2180
|
`TOIMOrderedListCommand`.
|
|
2132
2181
|
|
|
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
|
|
2182
|
+
**Shared stateless defaults** — `OIMDefaults` (an instance passed as `defaults`, `OIMDefaults.INSTANCE`
|
|
2183
|
+
the empty one) and `OIMDefaultValue`, the lazy holder of each built-in default, which is built on its
|
|
2184
|
+
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
2185
|
dependency has a default** above.
|
|
2139
2186
|
|
|
2140
2187
|
That is the whole of `src/index.ts`. If a name is not on this list, it is not
|