@owlmeans/state 0.1.18-rc.8 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +173 -68
  2. package/agent-meta/manifest.json +3 -3
  3. package/agent-meta/skills/state/SKILL.md +137 -58
  4. package/build/consts.local.d.ts +10 -0
  5. package/build/consts.local.d.ts.map +1 -0
  6. package/build/consts.local.js +10 -0
  7. package/build/consts.local.js.map +1 -0
  8. package/build/errors.d.ts +12 -5
  9. package/build/errors.d.ts.map +1 -1
  10. package/build/errors.js +16 -13
  11. package/build/errors.js.map +1 -1
  12. package/build/helper.d.ts +16 -0
  13. package/build/helper.d.ts.map +1 -0
  14. package/build/helper.js +14 -0
  15. package/build/helper.js.map +1 -0
  16. package/build/index.d.ts +3 -2
  17. package/build/index.d.ts.map +1 -1
  18. package/build/index.js +2 -2
  19. package/build/index.js.map +1 -1
  20. package/build/resource.d.ts +12 -5
  21. package/build/resource.d.ts.map +1 -1
  22. package/build/resource.js +317 -207
  23. package/build/resource.js.map +1 -1
  24. package/build/types.d.ts +91 -50
  25. package/build/types.d.ts.map +1 -1
  26. package/build/types.local.d.ts +15 -0
  27. package/build/types.local.d.ts.map +1 -0
  28. package/build/types.local.js +2 -0
  29. package/build/types.local.js.map +1 -0
  30. package/build/utils/criteria.d.ts.map +1 -1
  31. package/build/utils/criteria.js.map +1 -1
  32. package/build/utils/model.d.ts +10 -2
  33. package/build/utils/model.d.ts.map +1 -1
  34. package/build/utils/model.js +26 -22
  35. package/build/utils/model.js.map +1 -1
  36. package/build/utils/types.d.ts +15 -0
  37. package/build/utils/types.d.ts.map +1 -0
  38. package/build/utils/types.js +2 -0
  39. package/build/utils/types.js.map +1 -0
  40. package/package.json +4 -4
  41. package/src/consts.local.ts +11 -0
  42. package/src/errors.ts +16 -13
  43. package/src/helper.ts +17 -0
  44. package/src/index.ts +3 -2
  45. package/src/resource.ts +359 -241
  46. package/src/types.local.ts +16 -0
  47. package/src/types.ts +98 -55
  48. package/src/utils/model.ts +35 -28
  49. package/src/utils/types.ts +15 -0
  50. package/tests/resource.spec.ts +347 -90
  51. package/build/.gitkeep +0 -0
  52. package/src/consts.ts +0 -3
  53. package/src/utils/criteria.ts +0 -252
  54. package/src/utils/index.ts +0 -3
  55. package/tests/criteria.spec.ts +0 -121
@@ -1,14 +1,24 @@
1
1
  import { describe, expect, test } from 'bun:test'
2
- import { createStateResource, DEFAULT_ID } from '@owlmeans/state'
3
- import type { StateModel } from '@owlmeans/state'
2
+ import { createStateResource, StateConfigError } from '@owlmeans/state'
3
+ import type { StateEvent, StateModel } from '@owlmeans/state'
4
+ import type { ResourceRecord } from '@owlmeans/resource'
4
5
 
5
- interface Task {
6
- id?: string
6
+ interface Task extends ResourceRecord {
7
7
  title?: string
8
8
  status?: string
9
9
  points?: number
10
10
  }
11
11
 
12
+ interface Session extends ResourceRecord {
13
+ token?: string
14
+ user?: string
15
+ }
16
+
17
+ interface Doc extends ResourceRecord {
18
+ key?: string
19
+ body?: string
20
+ }
21
+
12
22
  const seeded = async () => {
13
23
  const resource = createStateResource<Task>('tasks')
14
24
  await resource.create({ id: 'a', title: 'Alpha', status: 'open', points: 3 })
@@ -18,145 +28,392 @@ const seeded = async () => {
18
28
  return resource
19
29
  }
20
30
 
21
- describe('@owlmeans/state — resource list surface', () => {
22
- // Every caller written before criteria existed spells `list()` and expects the whole store.
23
- // A server resource defaults to ten per page; inheriting that here would silently truncate
24
- // them, and nothing on the call site would say so.
25
- test('list() with no arguments returns everything, unpaged', async () => {
31
+ describe('@owlmeans/state — reads', () => {
32
+ test('list() is unpaged and always reports the total', async () => {
33
+ const resource = await seeded()
34
+ const result = await resource.list()
35
+ expect(result.items.map(item => item.id)).toEqual(['a', 'b', 'c'])
36
+ expect(result.total).toBe(3)
37
+ })
38
+
39
+ test('list(where, opts) filters, sorts and pages', async () => {
40
+ const resource = await seeded()
41
+ const first = await resource.list(
42
+ { status: ['open', 'done'] },
43
+ { page: 0, size: 2, sort: [{ field: 'points', order: 'desc' }] }
44
+ )
45
+ expect(first.items.map(item => item.id)).toEqual(['b', 'a'])
46
+ expect(first.total).toBe(3)
47
+
48
+ const second = await resource.list(
49
+ { status: ['open', 'done'] },
50
+ { page: 1, size: 2, sort: [{ field: 'points', order: 'desc' }] }
51
+ )
52
+ expect(second.items.map(item => item.id)).toEqual(['c'])
53
+ })
54
+
55
+ // The store is unpaged, so a page number alone has no page size to count from. Answering with
56
+ // everything would silently ignore what the caller asked for.
57
+ test('a page without a size is refused', async () => {
58
+ const resource = await seeded()
59
+ await expect(resource.list(undefined, { page: 1 })).rejects.toThrow()
60
+ })
61
+
62
+ test('count() answers without carrying the records', async () => {
63
+ const resource = await seeded()
64
+ expect(await resource.count()).toBe(3)
65
+ expect(await resource.count({ status: 'open' })).toBe(2)
66
+ })
67
+
68
+ test('one record by id or by criteria', async () => {
69
+ const resource = await seeded()
70
+ expect((await resource.load('a'))?.title).toBe('Alpha')
71
+ expect((await resource.load({ status: 'done' }))?.id).toBe('b')
72
+ expect((await resource.load({ status: 'missing' }))).toBeNull()
73
+ expect((await resource.load('zz'))).toBeNull()
74
+
75
+ expect((await resource.get({ title: 'Gamma' })).id).toBe('c')
76
+ await expect(resource.get('zz')).rejects.toThrow()
77
+ await expect(resource.get({ status: 'missing' })).rejects.toThrow()
78
+ })
79
+
80
+ test('a sort picks which of several matches comes first', async () => {
26
81
  const resource = await seeded()
82
+ const lowest = await resource.get({ status: 'open' }, { sort: ['points'] })
83
+ expect(lowest.id).toBe('c')
84
+ })
85
+ })
86
+
87
+ describe('@owlmeans/state — writes', () => {
88
+ test('create refuses a record that is already there', async () => {
89
+ const resource = await seeded()
90
+ await expect(resource.create({ id: 'a' })).rejects.toThrow()
91
+ })
92
+
93
+ test('update replaces the record and refuses an unknown one', async () => {
94
+ const resource = await seeded()
95
+ await resource.update({ id: 'a', title: 'Renamed' })
96
+ const record = await resource.get('a')
97
+ expect(record.title).toBe('Renamed')
98
+ expect(record.status).toBeUndefined()
99
+
100
+ await expect(resource.update({ id: 'zz' })).rejects.toThrow()
101
+ })
102
+
103
+ test('save creates or replaces', async () => {
104
+ const resource = await seeded()
105
+ await resource.save({ id: 'd', title: 'Delta' })
106
+ await resource.save({ id: 'a', title: 'Alpha again' })
107
+ expect(await resource.count()).toBe(4)
108
+ expect((await resource.get('a')).title).toBe('Alpha again')
109
+ })
110
+
111
+ // Nothing here mints ids, so a record without one is misfiled rather than new — inventing a key
112
+ // would put a record in the store that no screen can ever address again.
113
+ test('a write without an id is a configuration error', async () => {
114
+ const resource = await seeded()
115
+ await expect(resource.save({ title: 'nameless' })).rejects.toThrow(StateConfigError)
116
+ })
117
+
118
+ test('the store keeps nothing that expires, so a ttl is refused', async () => {
119
+ const resource = await seeded()
120
+ await expect(resource.save({ id: 'a' }, { ttl: 60 })).rejects.toThrow()
121
+ })
122
+
123
+ test('delete answers with null, take throws', async () => {
124
+ const resource = await seeded()
125
+ expect((await resource.delete('a'))?.id).toBe('a')
126
+ expect(await resource.delete('a')).toBeNull()
127
+
128
+ expect((await resource.take('b')).id).toBe('b')
129
+ await expect(resource.take('b')).rejects.toThrow()
130
+ })
131
+
132
+ test('purge removes the matching records and refuses to empty the store', async () => {
133
+ const resource = await seeded()
134
+ await expect(resource.purge({})).rejects.toThrow()
135
+ expect(await resource.purge({ status: 'open' })).toBe(2)
136
+ expect((await resource.list()).items.map(item => item.id)).toEqual(['b'])
137
+ })
138
+
139
+ test('replace makes the store agree with an authoritative list', async () => {
140
+ const resource = await seeded()
141
+ await resource.replace([
142
+ { id: 'a', title: 'Alpha', status: 'done' },
143
+ { id: 'z', title: 'Zeta', status: 'open' }
144
+ ])
27
145
  const { items } = await resource.list()
28
- expect(items.map(item => item.id)).toEqual(['a', 'b', 'c'])
146
+ expect(items.map(item => item.id)).toEqual(['a', 'z'])
147
+ expect((await resource.get('a')).status).toBe('done')
29
148
  })
30
149
 
31
- test('list(criteria) filters and reports the true total', async () => {
150
+ test('clear drops everything', async () => {
32
151
  const resource = await seeded()
33
- const result = await resource.list({ status: 'open' })
34
- expect(result.items.map(item => item.id)).toEqual(['a', 'c'])
35
- expect(result.pager?.total).toBe(2)
152
+ await resource.clear()
153
+ expect(await resource.count()).toBe(0)
36
154
  })
155
+ })
37
156
 
38
- test('list accepts the ListOptions form with a pager', async () => {
157
+ describe('@owlmeans/state — watching one record', () => {
158
+ // The whole point of the model: a subscription READS the store, it does not seed it. A screen
159
+ // bound to an id that has not arrived must not put a blank row into every list reading the same
160
+ // resource.
161
+ test('an id subscription creates NOTHING', async () => {
39
162
  const resource = await seeded()
40
- const result = await resource.list({
41
- criteria: { status: ['open', 'done'] },
42
- pager: { page: 0, size: 2, sort: [['points', true]] },
43
- })
44
- expect(result.items.map(item => item.id)).toEqual(['b', 'a'])
45
- expect(result.pager?.total).toBe(3)
163
+ const seen: StateModel<Task>[] = []
164
+ const stop = resource.watch('zz', model => { seen.push(model) })
165
+
166
+ expect(await resource.count()).toBe(3)
167
+ expect(await resource.load('zz')).toBeNull()
168
+ expect(seen).toHaveLength(1)
169
+ expect(seen[0]!.empty).toBe(true)
170
+ expect(seen[0]!.id).toBe('zz')
46
171
 
47
- const second = await resource.list({
48
- criteria: { status: ['open', 'done'] },
49
- pager: { page: 1, size: 2, sort: [['points', true]] },
172
+ stop()
173
+ })
174
+
175
+ test('an empty model shows the configured default and stays out of the store', async () => {
176
+ const resource = createStateResource<Task>('drafts', {
177
+ default: () => ({ title: 'Untitled', status: 'draft' })
50
178
  })
51
- expect(second.items.map(item => item.id)).toEqual(['c'])
179
+ let model!: StateModel<Task>
180
+ const stop = resource.watch('new', current => { model = current })
181
+
182
+ expect(model.empty).toBe(true)
183
+ expect(model.record.title).toBe('Untitled')
184
+ expect(await resource.count()).toBe(0)
185
+
186
+ stop()
187
+ })
188
+
189
+ test('the listener is seeded synchronously and follows the record', async () => {
190
+ const resource = await seeded()
191
+ const titles: Array<string | undefined> = []
192
+ const stop = resource.watch('a', model => { titles.push(model.record.title) })
193
+
194
+ expect(titles).toEqual(['Alpha'])
195
+
196
+ await resource.save({ id: 'a', title: 'Renamed' })
197
+ expect(titles.at(-1)).toBe('Renamed')
198
+
199
+ await resource.delete('a')
200
+ expect(titles.at(-1)).toBeUndefined()
201
+
202
+ stop()
203
+ await resource.save({ id: 'a', title: 'Back' })
204
+ expect(titles).toHaveLength(3)
205
+ })
206
+
207
+ test('a model written through stops being empty', async () => {
208
+ const resource = await seeded()
209
+ let model!: StateModel<Task>
210
+ const stop = resource.watch('zz', current => { model = current })
211
+
212
+ const written = await model.update({ title: 'Zulu' })
213
+ expect(written.id).toBe('zz')
214
+ expect((await resource.get('zz')).title).toBe('Zulu')
215
+
216
+ // The listener has been handed a fresh model by now; the one written through knows it landed.
217
+ expect(model.empty).toBe(false)
218
+
219
+ await model.clear()
220
+ expect(await resource.load('zz')).toBeNull()
221
+
222
+ stop()
223
+ })
224
+
225
+ /**
226
+ * A screen binds to `watch(project.id)` while the project is still loading, so an absent id is
227
+ * a rendering state and must not take the component tree down with it. Reading with no id is a
228
+ * different question — there the caller has lost track of which record it meant.
229
+ */
230
+ test('watching with no id reports empty rather than throwing', async () => {
231
+ const resource = await seeded()
232
+ let model!: StateModel<Session>
233
+ const stop = resource.watch(undefined, current => { model = current })
234
+
235
+ expect(model.empty).toBe(true)
236
+ expect(model.id).toBeUndefined()
237
+
238
+ stop()
52
239
  })
53
240
 
54
- test('all() and match() answer without the envelope', async () => {
241
+ test('the empty model is one reference, so a subscriber sees no change', async () => {
55
242
  const resource = await seeded()
56
- expect((await resource.all()).map(item => item.id)).toEqual(['a', 'b', 'c'])
57
- expect((await resource.match({ status: 'open' })).map(item => item.id)).toEqual(['a', 'c'])
58
- expect((await resource.match()).map(item => item.id)).toEqual(['a', 'b', 'c'])
243
+ const seen: StateModel<Session>[] = []
244
+ const first = resource.watch(undefined, current => { seen.push(current) })
245
+ const second = resource.watch(undefined, current => { seen.push(current) })
246
+
247
+ expect(seen).toHaveLength(2)
248
+ expect(seen[0]).toBe(seen[1])
249
+
250
+ first()
251
+ second()
59
252
  })
60
253
 
61
- test('the pre-existing throws are untouched', async () => {
254
+ test('writing through the empty model is refused', async () => {
62
255
  const resource = await seeded()
63
- expect(resource.load('a', 'title')).rejects.toThrow()
64
- expect(resource.create({ id: 'a' })).rejects.toThrow()
256
+ let model!: StateModel<Session>
257
+ const stop = resource.watch(undefined, current => { model = current })
258
+
259
+ await expect(model.update({ token: 'x' } as Partial<Session>)).rejects.toThrow(StateConfigError)
260
+
261
+ stop()
262
+ })
263
+
264
+ /**
265
+ * The reason the empty model exists at all: the store it stands in for must be untouched. The
266
+ * placeholder record this replaced put a blank row into every list reading the same store.
267
+ */
268
+ test('watching with no id writes nothing into the store', async () => {
269
+ const resource = await seeded()
270
+ const before = await resource.list()
271
+ const stop = resource.watch(undefined, () => { })
272
+ const after = await resource.list()
273
+
274
+ expect(after.total).toBe(before.total)
275
+ expect(after.items.some(item => item.id == null)).toBe(false)
276
+
277
+ stop()
278
+ })
279
+ })
280
+
281
+ describe('@owlmeans/state — a single resource', () => {
282
+ test('one slot, addressed without an id', async () => {
283
+ const resource = createStateResource<Session>('session', { single: true })
284
+ let model!: StateModel<Session>
285
+ const stop = resource.watch(undefined, current => { model = current })
286
+
287
+ expect(model.empty).toBe(true)
288
+
289
+ await resource.save({ token: 'first', user: 'ann' })
290
+ expect(model.empty).toBe(false)
291
+ expect(model.record.token).toBe('first')
292
+
293
+ await resource.save({ token: 'second', user: 'bob' })
294
+ expect(await resource.count()).toBe(1)
295
+ expect(model.record.user).toBe('bob')
296
+
297
+ await model.clear()
298
+ expect(await resource.count()).toBe(0)
299
+
300
+ stop()
301
+ })
302
+
303
+ test('the one record still answers only to its own id', async () => {
304
+ const resource = createStateResource<Session>('session-id', { single: true })
305
+ await resource.save({ id: 'sid', token: 'a' })
306
+ expect((await resource.load('sid'))?.token).toBe('a')
307
+ expect(await resource.load('other')).toBeNull()
65
308
  })
66
309
  })
67
310
 
68
- describe('@owlmeans/state — live query subscriptions', () => {
69
- test('a query subscriber is seeded with the current answer and follows writes', async () => {
311
+ describe('@owlmeans/state — a custom id field', () => {
312
+ test('records are keyed by the configured field', async () => {
313
+ const resource = createStateResource<Doc>('docs', { id: 'key' })
314
+ await resource.save({ key: 'intro', body: 'hello' })
315
+ await resource.save({ key: 'outro', body: 'bye' })
316
+
317
+ expect((await resource.get('intro')).body).toBe('hello')
318
+ expect(await resource.count()).toBe(2)
319
+ await expect(resource.save({ body: 'orphan' })).rejects.toThrow(StateConfigError)
320
+ })
321
+ })
322
+
323
+ describe('@owlmeans/state — live queries', () => {
324
+ test('a query subscriber is seeded and follows every write that changes the answer', async () => {
70
325
  const resource = await seeded()
71
326
  const seen: string[][] = []
72
- const [unsubscribe, initial] = resource.subscribe({
73
- query: { status: 'open' },
74
- listener: models => { seen.push(models.map(model => model.record.id!)) },
327
+ const stop = resource.query({ status: 'open' }, models => {
328
+ seen.push(models.map(model => model.record.id!))
75
329
  })
76
330
 
77
- expect(initial.map(model => model.record.id)).toEqual(['a', 'c'])
331
+ expect(seen.at(-1)).toEqual(['a', 'c'])
78
332
 
79
- // A record entering the set
80
333
  await resource.create({ id: 'd', title: 'Delta', status: 'open' })
81
334
  expect(seen.at(-1)).toEqual(['a', 'c', 'd'])
82
335
 
83
- // A record leaving the set through an update
84
- await resource.update({ id: 'a', status: 'done' })
336
+ await resource.save({ id: 'a', status: 'done' })
85
337
  expect(seen.at(-1)).toEqual(['c', 'd'])
86
338
 
87
- // A record leaving the set through a delete
88
339
  await resource.delete('c')
89
340
  expect(seen.at(-1)).toEqual(['d'])
90
341
 
91
- unsubscribe()
92
- const before = seen.length
342
+ stop()
93
343
  await resource.create({ id: 'e', status: 'open' })
94
- expect(seen.length).toBe(before)
344
+ expect(seen.at(-1)).toEqual(['d'])
95
345
  })
96
346
 
97
- // The id path creates a placeholder so a screen has something to bind to before its record
98
- // arrives. A query has no such record to invent, and inventing one would put a blank row in
99
- // every list.
100
347
  test('a query subscription creates nothing', async () => {
101
348
  const resource = createStateResource<Task>('empty')
102
- const [, initial] = resource.subscribe({ query: { status: 'open' }, listener: () => { } })
103
- expect(initial).toEqual([])
104
- expect((await resource.all()).length).toBe(0)
349
+ const stop = resource.query({ status: 'open' }, () => { })
350
+ expect(await resource.count()).toBe(0)
351
+ stop()
105
352
  })
106
353
 
107
- test('an id subscription still creates its placeholder and still fires', async () => {
354
+ test('an absent query matches everything and takes the sort', async () => {
108
355
  const resource = await seeded()
109
- const seen: string[] = []
110
- resource.subscribe({
111
- id: 'zz',
112
- default: { title: 'placeholder' },
113
- listener: models => { seen.push(models[0]!.record.title!) },
114
- })
115
- expect((await resource.load('zz'))?.title).toBe('placeholder')
356
+ let ids: string[] = []
357
+ const stop = resource.query(undefined, models => {
358
+ ids = models.map(model => model.record.id!)
359
+ }, { sort: [{ field: 'points', order: 'desc' }] })
116
360
 
117
- await resource.update({ id: 'zz', title: 'real' })
118
- expect(seen.at(-1)).toBe('real')
361
+ expect(ids).toEqual(['b', 'a', 'c'])
362
+ stop()
119
363
  })
120
364
 
121
- test('id wins when a caller passes both', async () => {
365
+ // A write that leaves the answer alone must not re-render the screens reading it.
366
+ test('an unchanged answer is not announced again', async () => {
122
367
  const resource = await seeded()
123
- const [, initial] = resource.subscribe({
124
- id: 'a',
125
- query: { status: 'done' },
126
- listener: () => { },
127
- })
128
- expect(initial.map(model => model.record.id)).toEqual(['a'])
368
+ let calls = 0
369
+ const stop = resource.query({ status: 'open' }, () => { calls += 1 })
370
+ expect(calls).toBe(1)
371
+
372
+ await resource.save({ id: 'b', status: 'done', title: 'Beta reworded' })
373
+ expect(calls).toBe(1)
374
+
375
+ await resource.save({ id: 'b', status: 'open' })
376
+ expect(calls).toBe(2)
377
+
378
+ stop()
129
379
  })
380
+ })
130
381
 
131
- test('a repeated plain query listener is refused, a _systemId one is deduped', async () => {
382
+ describe('@owlmeans/state — the change stream', () => {
383
+ test('every write announces itself', async () => {
132
384
  const resource = await seeded()
133
- const listener = () => { }
134
- resource.subscribe({ query: { status: 'open' }, listener })
135
- expect(() => resource.subscribe({ query: { status: 'open' }, listener })).toThrow()
385
+ const events: StateEvent<Task>[] = []
386
+ const stop = await resource.subscribe(event => { events.push(event) })
136
387
 
137
- const systemListener = () => { }
138
- const [, first] = resource.subscribe({ _systemId: 'r1', query: { status: 'open' }, listener: systemListener })
139
- const [, again] = resource.subscribe({ _systemId: 'r1', query: { status: 'open' }, listener: systemListener })
140
- expect(again.map(model => model.record.id)).toEqual(first.map(model => model.record.id))
141
- })
388
+ await resource.save({ id: 'd', title: 'Delta' })
389
+ expect(events.at(-1)?.type).toBe('set')
390
+ expect(events.at(-1)?.records.map(record => record.id)).toEqual(['d'])
142
391
 
143
- test('DEFAULT_ID is still what an id-less model gets', async () => {
144
- const resource = createStateResource<Task>('single')
145
- const [, models] = resource.subscribe({ listener: () => { } })
146
- expect(models[0]!.record.id).toBe(DEFAULT_ID)
392
+ await resource.purge({ status: 'open' })
393
+ expect(events.at(-1)?.type).toBe('remove')
394
+ expect(events.at(-1)?.records.map(record => record.id)).toEqual(['a', 'c'])
395
+
396
+ await stop()
397
+ await resource.save({ id: 'e' })
398
+ expect(events).toHaveLength(2)
147
399
  })
148
400
 
149
- test('a model committed from a query result writes through', async () => {
401
+ test('a once subscriber hears one event, and a channel keeps its traffic apart', async () => {
150
402
  const resource = await seeded()
151
- let latest: StateModel<Task>[] = []
152
- resource.subscribe({ query: { status: 'open' }, listener: models => { latest = models } })
403
+ let once = 0
404
+ await resource.subscribe(() => { once += 1 }, { once: true })
405
+
406
+ const aside: StateEvent<Task>[] = []
407
+ const stop = await resource.subscribe(event => { aside.push(event) }, { channel: 'aside' })
408
+
409
+ await resource.save({ id: 'd' })
410
+ await resource.save({ id: 'f' })
411
+ expect(once).toBe(1)
412
+ expect(aside).toHaveLength(0)
153
413
 
154
- const [first] = await Promise.resolve(latest.length > 0 ? latest : resource.subscribe({
155
- query: { status: 'open' }, listener: () => { },
156
- })[1])
414
+ await resource.publish({ type: 'set', records: [{ id: 'd' }] }, 'aside')
415
+ expect(aside).toHaveLength(1)
157
416
 
158
- first!.update({ points: 42 })
159
- await Promise.resolve()
160
- expect((await resource.load('a'))?.points).toBe(42)
417
+ await stop()
161
418
  })
162
419
  })
package/build/.gitkeep DELETED
File without changes
package/src/consts.ts DELETED
@@ -1,3 +0,0 @@
1
-
2
- export const DEFAULT_ID = '_default'
3
- export const DEFAULT_ALIAS = 'state'