@planet-matrix/mobius-model 0.4.0 → 0.6.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 +32 -0
- package/README.md +134 -21
- package/dist/index.js +45 -4
- package/dist/index.js.map +186 -11
- package/oxlint.config.ts +6 -0
- package/package.json +16 -10
- package/src/abort/README.md +92 -0
- package/src/abort/abort-manager.ts +278 -0
- package/src/abort/abort-signal-listener-manager.ts +81 -0
- package/src/abort/index.ts +2 -0
- package/src/basic/README.md +69 -117
- package/src/basic/enhance.ts +10 -0
- package/src/basic/function.ts +81 -62
- package/src/basic/index.ts +2 -0
- package/src/basic/is.ts +152 -71
- package/src/basic/object.ts +82 -0
- package/src/basic/promise.ts +29 -8
- package/src/basic/string.ts +2 -33
- package/src/color/README.md +105 -0
- package/src/color/index.ts +3 -0
- package/src/color/internal.ts +42 -0
- package/src/color/rgb/analyze.ts +236 -0
- package/src/color/rgb/construct.ts +130 -0
- package/src/color/rgb/convert.ts +227 -0
- package/src/color/rgb/derive.ts +303 -0
- package/src/color/rgb/index.ts +6 -0
- package/src/color/rgb/internal.ts +208 -0
- package/src/color/rgb/parse.ts +302 -0
- package/src/color/rgb/serialize.ts +144 -0
- package/src/color/types.ts +57 -0
- package/src/color/xyz/analyze.ts +80 -0
- package/src/color/xyz/construct.ts +19 -0
- package/src/color/xyz/convert.ts +71 -0
- package/src/color/xyz/index.ts +3 -0
- package/src/color/xyz/internal.ts +23 -0
- package/src/css/README.md +93 -0
- package/src/css/class.ts +559 -0
- package/src/css/index.ts +1 -0
- package/src/encoding/README.md +92 -0
- package/src/encoding/base64.ts +107 -0
- package/src/encoding/index.ts +1 -0
- package/src/environment/README.md +97 -0
- package/src/environment/basic.ts +26 -0
- package/src/environment/device.ts +311 -0
- package/src/environment/feature.ts +285 -0
- package/src/environment/geo.ts +337 -0
- package/src/environment/index.ts +7 -0
- package/src/environment/runtime.ts +400 -0
- package/src/environment/snapshot.ts +60 -0
- package/src/environment/variable.ts +239 -0
- package/src/event/README.md +90 -0
- package/src/event/class-event-proxy.ts +228 -0
- package/src/event/common.ts +19 -0
- package/src/event/event-manager.ts +203 -0
- package/src/event/index.ts +4 -0
- package/src/event/instance-event-proxy.ts +186 -0
- package/src/event/internal.ts +24 -0
- package/src/exception/README.md +96 -0
- package/src/exception/browser.ts +219 -0
- package/src/exception/index.ts +4 -0
- package/src/exception/nodejs.ts +169 -0
- package/src/exception/normalize.ts +106 -0
- package/src/exception/types.ts +99 -0
- package/src/identifier/README.md +92 -0
- package/src/identifier/id.ts +119 -0
- package/src/identifier/index.ts +2 -0
- package/src/identifier/uuid.ts +187 -0
- package/src/index.ts +18 -1
- package/src/log/README.md +79 -0
- package/src/log/index.ts +5 -0
- package/src/log/log-emitter.ts +72 -0
- package/src/log/log-record.ts +10 -0
- package/src/log/log-scheduler.ts +74 -0
- package/src/log/log-type.ts +8 -0
- package/src/log/logger.ts +543 -0
- package/src/orchestration/README.md +89 -0
- package/src/orchestration/coordination/barrier.ts +214 -0
- package/src/orchestration/coordination/count-down-latch.ts +215 -0
- package/src/orchestration/coordination/errors.ts +98 -0
- package/src/orchestration/coordination/index.ts +16 -0
- package/src/orchestration/coordination/internal/wait-constraints.ts +95 -0
- package/src/orchestration/coordination/internal/wait-queue.ts +109 -0
- package/src/orchestration/coordination/keyed-lock.ts +168 -0
- package/src/orchestration/coordination/mutex.ts +257 -0
- package/src/orchestration/coordination/permit.ts +127 -0
- package/src/orchestration/coordination/read-write-lock.ts +444 -0
- package/src/orchestration/coordination/semaphore.ts +280 -0
- package/src/orchestration/index.ts +1 -0
- package/src/random/README.md +78 -0
- package/src/random/index.ts +1 -0
- package/src/random/string.ts +35 -0
- package/src/reactor/README.md +4 -0
- package/src/reactor/reactor-core/primitive.ts +9 -9
- package/src/reactor/reactor-core/reactive-system.ts +5 -5
- package/src/singleton/README.md +79 -0
- package/src/singleton/factory.ts +55 -0
- package/src/singleton/index.ts +2 -0
- package/src/singleton/manager.ts +204 -0
- package/src/storage/README.md +107 -0
- package/src/storage/index.ts +1 -0
- package/src/storage/table.ts +449 -0
- package/src/timer/README.md +86 -0
- package/src/timer/expiration/expiration-manager.ts +594 -0
- package/src/timer/expiration/index.ts +3 -0
- package/src/timer/expiration/min-heap.ts +208 -0
- package/src/timer/expiration/remaining-manager.ts +241 -0
- package/src/timer/index.ts +1 -0
- package/src/type/README.md +54 -307
- package/src/type/class.ts +2 -2
- package/src/type/index.ts +14 -14
- package/src/type/is.ts +265 -2
- package/src/type/object.ts +37 -0
- package/src/type/string.ts +7 -2
- package/src/type/tuple.ts +6 -6
- package/src/type/union.ts +16 -0
- package/src/web/README.md +77 -0
- package/src/web/capture.ts +35 -0
- package/src/web/clipboard.ts +97 -0
- package/src/web/dom.ts +117 -0
- package/src/web/download.ts +16 -0
- package/src/web/event.ts +46 -0
- package/src/web/index.ts +10 -0
- package/src/web/local-storage.ts +113 -0
- package/src/web/location.ts +28 -0
- package/src/web/permission.ts +172 -0
- package/src/web/script-loader.ts +432 -0
- package/tests/unit/abort/abort-manager.spec.ts +225 -0
- package/tests/unit/abort/abort-signal-listener-manager.spec.ts +62 -0
- package/tests/unit/basic/array.spec.ts +1 -1
- package/tests/unit/basic/object.spec.ts +32 -1
- package/tests/unit/basic/stream.spec.ts +1 -1
- package/tests/unit/basic/string.spec.ts +0 -9
- package/tests/unit/color/rgb/analyze.spec.ts +110 -0
- package/tests/unit/color/rgb/construct.spec.ts +56 -0
- package/tests/unit/color/rgb/convert.spec.ts +60 -0
- package/tests/unit/color/rgb/derive.spec.ts +103 -0
- package/tests/unit/color/rgb/parse.spec.ts +66 -0
- package/tests/unit/color/rgb/serialize.spec.ts +46 -0
- package/tests/unit/color/xyz/analyze.spec.ts +33 -0
- package/tests/unit/color/xyz/construct.spec.ts +10 -0
- package/tests/unit/color/xyz/convert.spec.ts +18 -0
- package/tests/unit/css/class.spec.ts +157 -0
- package/tests/unit/encoding/base64.spec.ts +40 -0
- package/tests/unit/environment/basic.spec.ts +20 -0
- package/tests/unit/environment/device.spec.ts +146 -0
- package/tests/unit/environment/feature.spec.ts +388 -0
- package/tests/unit/environment/geo.spec.ts +111 -0
- package/tests/unit/environment/runtime.spec.ts +364 -0
- package/tests/unit/environment/snapshot.spec.ts +4 -0
- package/tests/unit/environment/variable.spec.ts +190 -0
- package/tests/unit/event/class-event-proxy.spec.ts +225 -0
- package/tests/unit/event/event-manager.spec.ts +246 -0
- package/tests/unit/event/instance-event-proxy.spec.ts +187 -0
- package/tests/unit/exception/browser.spec.ts +213 -0
- package/tests/unit/exception/nodejs.spec.ts +144 -0
- package/tests/unit/exception/normalize.spec.ts +57 -0
- package/tests/unit/identifier/id.spec.ts +71 -0
- package/tests/unit/identifier/uuid.spec.ts +85 -0
- package/tests/unit/log/log-emitter.spec.ts +33 -0
- package/tests/unit/log/log-scheduler.spec.ts +40 -0
- package/tests/unit/log/log-type.spec.ts +7 -0
- package/tests/unit/log/logger.spec.ts +222 -0
- package/tests/unit/orchestration/coordination/barrier.spec.ts +96 -0
- package/tests/unit/orchestration/coordination/count-down-latch.spec.ts +63 -0
- package/tests/unit/orchestration/coordination/errors.spec.ts +29 -0
- package/tests/unit/orchestration/coordination/keyed-lock.spec.ts +109 -0
- package/tests/unit/orchestration/coordination/mutex.spec.ts +132 -0
- package/tests/unit/orchestration/coordination/permit.spec.ts +43 -0
- package/tests/unit/orchestration/coordination/read-write-lock.spec.ts +154 -0
- package/tests/unit/orchestration/coordination/semaphore.spec.ts +135 -0
- package/tests/unit/random/string.spec.ts +11 -0
- package/tests/unit/reactor/alien-signals-effect.spec.ts +11 -10
- package/tests/unit/reactor/preact-signal.spec.ts +1 -2
- package/tests/unit/singleton/singleton.spec.ts +49 -0
- package/tests/unit/storage/table.spec.ts +620 -0
- package/tests/unit/timer/expiration/expiration-manager.spec.ts +464 -0
- package/tests/unit/timer/expiration/min-heap.spec.ts +71 -0
- package/tests/unit/timer/expiration/remaining-manager.spec.ts +234 -0
- package/.oxlintrc.json +0 -5
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { BaseTable } from "#Source/storage/index.ts"
|
|
4
|
+
|
|
5
|
+
interface TestRow extends Record<string, unknown> {
|
|
6
|
+
id: string
|
|
7
|
+
label: string
|
|
8
|
+
count: number
|
|
9
|
+
meta: {
|
|
10
|
+
rank: number
|
|
11
|
+
}
|
|
12
|
+
status?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class TestTable extends BaseTable<TestRow> {
|
|
16
|
+
// No additional implementation needed for testing BaseTable functionality
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const createTestTable = (): TestTable => {
|
|
20
|
+
return new TestTable({
|
|
21
|
+
uniqueGetter: (row) => row.id,
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const createRow = (overrides: Partial<TestRow> = {}): TestRow => {
|
|
26
|
+
return {
|
|
27
|
+
id: "row-1",
|
|
28
|
+
label: "alpha",
|
|
29
|
+
count: 1,
|
|
30
|
+
meta: {
|
|
31
|
+
rank: 1,
|
|
32
|
+
},
|
|
33
|
+
status: "kept",
|
|
34
|
+
...overrides,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const createRows = (): [TestRow, TestRow, TestRow] => {
|
|
39
|
+
return [
|
|
40
|
+
createRow(),
|
|
41
|
+
createRow({
|
|
42
|
+
id: "row-2",
|
|
43
|
+
label: "beta",
|
|
44
|
+
count: 2,
|
|
45
|
+
meta: {
|
|
46
|
+
rank: 2,
|
|
47
|
+
},
|
|
48
|
+
status: "held",
|
|
49
|
+
}),
|
|
50
|
+
createRow({
|
|
51
|
+
id: "row-3",
|
|
52
|
+
label: "gamma",
|
|
53
|
+
count: 3,
|
|
54
|
+
meta: {
|
|
55
|
+
rank: 3,
|
|
56
|
+
},
|
|
57
|
+
status: "fresh",
|
|
58
|
+
}),
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
test("BaseTable.clearTable removes all rows and emits an empty snapshot", async () => {
|
|
63
|
+
const table = createTestTable()
|
|
64
|
+
const [rowA, rowB] = createRows()
|
|
65
|
+
const snapshots: TestRow[][] = []
|
|
66
|
+
|
|
67
|
+
await table.safeInsertRows([rowA, rowB])
|
|
68
|
+
table.event.subscribe("valueChanged", (rows) => {
|
|
69
|
+
snapshots.push(rows)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
await table.clearTable()
|
|
73
|
+
|
|
74
|
+
expect(snapshots).toHaveLength(1)
|
|
75
|
+
expect(snapshots[0]).toEqual([])
|
|
76
|
+
|
|
77
|
+
snapshots[0]?.push(createRow({ id: "extra" }))
|
|
78
|
+
|
|
79
|
+
expect(await table.getRows(() => true)).toEqual([])
|
|
80
|
+
expect(snapshots[0]).toEqual([createRow({ id: "extra" })])
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test("BaseTable.getRow returns a cloned row and keeps predicate mutations isolated", async () => {
|
|
84
|
+
const table = createTestTable()
|
|
85
|
+
const [rowA] = createRows()
|
|
86
|
+
|
|
87
|
+
await table.safeInsertRow(rowA)
|
|
88
|
+
|
|
89
|
+
const foundRow = await table.getRow((row) => {
|
|
90
|
+
row.label = "mutated-in-getter"
|
|
91
|
+
row.meta.rank = 99
|
|
92
|
+
return row.id === rowA.id
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
foundRow!.label = "mutated-in-result"
|
|
96
|
+
foundRow!.meta.rank = 100
|
|
97
|
+
|
|
98
|
+
expect(foundRow).toEqual({
|
|
99
|
+
...rowA,
|
|
100
|
+
label: "mutated-in-result",
|
|
101
|
+
meta: {
|
|
102
|
+
rank: 100,
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
expect(await table.getRow((row) => row.id === rowA.id)).toEqual(rowA)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test("BaseTable.getRows returns cloned rows and keeps predicate mutations isolated", async () => {
|
|
109
|
+
const table = createTestTable()
|
|
110
|
+
const [rowA, rowB, rowC] = createRows()
|
|
111
|
+
|
|
112
|
+
await table.safeInsertRows([rowA, rowB, rowC])
|
|
113
|
+
|
|
114
|
+
const foundRows = await table.getRows((row) => {
|
|
115
|
+
row.status = "mutated"
|
|
116
|
+
return row.count >= 2
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
foundRows[0]!.status = "changed-after-read"
|
|
120
|
+
|
|
121
|
+
expect(foundRows).toEqual([
|
|
122
|
+
{
|
|
123
|
+
...rowB,
|
|
124
|
+
status: "changed-after-read",
|
|
125
|
+
},
|
|
126
|
+
rowC,
|
|
127
|
+
])
|
|
128
|
+
expect(await table.getRows((row) => row.count >= 2)).toEqual([rowB, rowC])
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
test("BaseTable.safeInsertRow inserts one row, emits snapshots, and rejects duplicate uniques", async () => {
|
|
132
|
+
const table = createTestTable()
|
|
133
|
+
const [rowA] = createRows()
|
|
134
|
+
const snapshots: TestRow[][] = []
|
|
135
|
+
|
|
136
|
+
table.event.subscribe("valueChanged", (rows) => {
|
|
137
|
+
snapshots.push(rows)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
const insertedRow = await table.safeInsertRow(rowA)
|
|
141
|
+
insertedRow.meta.rank = 100
|
|
142
|
+
snapshots[0]![0]!.label = "mutated-snapshot"
|
|
143
|
+
|
|
144
|
+
await expect(table.safeInsertRow(rowA)).rejects.toThrow('Row with unique "row-1" already exists')
|
|
145
|
+
expect(await table.getRow((row) => row.id === rowA.id)).toEqual(rowA)
|
|
146
|
+
expect(snapshots).toHaveLength(1)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test("BaseTable.insertRow returns undefined for duplicate uniques and inserts missing rows", async () => {
|
|
150
|
+
const table = createTestTable()
|
|
151
|
+
const [rowA, rowB] = createRows()
|
|
152
|
+
|
|
153
|
+
await table.safeInsertRow(rowA)
|
|
154
|
+
|
|
155
|
+
expect(await table.insertRow(rowA)).toBeUndefined()
|
|
156
|
+
expect(await table.insertRow(rowB)).toEqual(rowB)
|
|
157
|
+
expect(await table.getRows(() => true)).toEqual([rowA, rowB])
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
test("BaseTable.safeInsertRows inserts a batch and rejects duplicate uniques inside the batch", async () => {
|
|
161
|
+
const table = createTestTable()
|
|
162
|
+
const [rowA, rowB] = createRows()
|
|
163
|
+
const duplicateBatch = [
|
|
164
|
+
rowA,
|
|
165
|
+
createRow({
|
|
166
|
+
id: rowA.id,
|
|
167
|
+
label: "duplicate",
|
|
168
|
+
count: 9,
|
|
169
|
+
meta: {
|
|
170
|
+
rank: 9,
|
|
171
|
+
},
|
|
172
|
+
}),
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
const insertedRows = await table.safeInsertRows([rowA, rowB])
|
|
176
|
+
insertedRows[0]!.meta.rank = 100
|
|
177
|
+
|
|
178
|
+
await expect(table.safeInsertRows(duplicateBatch)).rejects.toThrow('Duplicate row unique "row-1" found in batch')
|
|
179
|
+
expect(await table.getRows(() => true)).toEqual([rowA, rowB])
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
test("BaseTable.safeSimpleUpdateRow returns the merged stored row", async () => {
|
|
183
|
+
const table = createTestTable()
|
|
184
|
+
const [rowA] = createRows()
|
|
185
|
+
|
|
186
|
+
await table.safeInsertRow(rowA)
|
|
187
|
+
|
|
188
|
+
const updatedRow = await table.safeSimpleUpdateRow({
|
|
189
|
+
id: rowA.id,
|
|
190
|
+
label: "beta",
|
|
191
|
+
count: 8,
|
|
192
|
+
meta: {
|
|
193
|
+
rank: 8,
|
|
194
|
+
},
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
expect(updatedRow).toEqual({
|
|
198
|
+
...rowA,
|
|
199
|
+
label: "beta",
|
|
200
|
+
count: 8,
|
|
201
|
+
meta: {
|
|
202
|
+
rank: 8,
|
|
203
|
+
},
|
|
204
|
+
})
|
|
205
|
+
expect(await table.getRow((row) => row.id === rowA.id)).toEqual(updatedRow)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
test("BaseTable.simpleUpdateRow returns merged rows and undefined for missing uniques", async () => {
|
|
209
|
+
const table = createTestTable()
|
|
210
|
+
const [rowA] = createRows()
|
|
211
|
+
|
|
212
|
+
await table.safeInsertRow(rowA)
|
|
213
|
+
|
|
214
|
+
expect(await table.simpleUpdateRow({
|
|
215
|
+
id: rowA.id,
|
|
216
|
+
label: "simple",
|
|
217
|
+
count: 6,
|
|
218
|
+
meta: {
|
|
219
|
+
rank: 6,
|
|
220
|
+
},
|
|
221
|
+
})).toEqual({
|
|
222
|
+
...rowA,
|
|
223
|
+
label: "simple",
|
|
224
|
+
count: 6,
|
|
225
|
+
meta: {
|
|
226
|
+
rank: 6,
|
|
227
|
+
},
|
|
228
|
+
})
|
|
229
|
+
expect(await table.simpleUpdateRow(createRow({ id: "missing" }))).toBeUndefined()
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
test("BaseTable.safeSimpleUpdateRows merges each row and rejects duplicate batch uniques", async () => {
|
|
233
|
+
const table = createTestTable()
|
|
234
|
+
const [rowA, rowB] = createRows()
|
|
235
|
+
|
|
236
|
+
await table.safeInsertRows([rowA, rowB])
|
|
237
|
+
|
|
238
|
+
const updatedRows = await table.safeSimpleUpdateRows([
|
|
239
|
+
{
|
|
240
|
+
id: rowA.id,
|
|
241
|
+
label: "alpha-updated",
|
|
242
|
+
count: 11,
|
|
243
|
+
meta: {
|
|
244
|
+
rank: 11,
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
id: rowB.id,
|
|
249
|
+
label: "beta-updated",
|
|
250
|
+
count: 12,
|
|
251
|
+
meta: {
|
|
252
|
+
rank: 12,
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
])
|
|
256
|
+
|
|
257
|
+
expect(updatedRows).toEqual([
|
|
258
|
+
{
|
|
259
|
+
...rowA,
|
|
260
|
+
label: "alpha-updated",
|
|
261
|
+
count: 11,
|
|
262
|
+
meta: {
|
|
263
|
+
rank: 11,
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
...rowB,
|
|
268
|
+
label: "beta-updated",
|
|
269
|
+
count: 12,
|
|
270
|
+
meta: {
|
|
271
|
+
rank: 12,
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
])
|
|
275
|
+
|
|
276
|
+
await expect(table.safeSimpleUpdateRows([
|
|
277
|
+
createRow({ id: rowA.id }),
|
|
278
|
+
createRow({ id: rowA.id, label: "duplicate" }),
|
|
279
|
+
])).rejects.toThrow('Duplicate row unique "row-1" found in batch')
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
test("BaseTable.safeUpdateRow updates one matching row and rejects invalid transitions", async () => {
|
|
283
|
+
const table = createTestTable()
|
|
284
|
+
const [rowA] = createRows()
|
|
285
|
+
|
|
286
|
+
await table.safeInsertRow(rowA)
|
|
287
|
+
|
|
288
|
+
const updatedRow = await table.safeUpdateRow(
|
|
289
|
+
(row) => row.id === rowA.id,
|
|
290
|
+
(row) => ({
|
|
291
|
+
...row,
|
|
292
|
+
label: "updated",
|
|
293
|
+
count: row.count + 5,
|
|
294
|
+
meta: {
|
|
295
|
+
rank: row.meta.rank + 5,
|
|
296
|
+
},
|
|
297
|
+
}),
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
expect(updatedRow).toEqual({
|
|
301
|
+
...rowA,
|
|
302
|
+
label: "updated",
|
|
303
|
+
count: 6,
|
|
304
|
+
meta: {
|
|
305
|
+
rank: 6,
|
|
306
|
+
},
|
|
307
|
+
})
|
|
308
|
+
await expect(table.safeUpdateRow(
|
|
309
|
+
(row) => row.id === "missing",
|
|
310
|
+
(row) => row,
|
|
311
|
+
)).rejects.toThrow("Row not found.")
|
|
312
|
+
await expect(table.safeUpdateRow(
|
|
313
|
+
(row) => row.id === rowA.id,
|
|
314
|
+
(row) => ({
|
|
315
|
+
...row,
|
|
316
|
+
id: "changed",
|
|
317
|
+
}),
|
|
318
|
+
)).rejects.toThrow("Unique cannot be changed")
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
test("BaseTable.updateRow returns undefined when no row matches or the unique changes", async () => {
|
|
322
|
+
const table = createTestTable()
|
|
323
|
+
const [rowA] = createRows()
|
|
324
|
+
|
|
325
|
+
await table.safeInsertRow(rowA)
|
|
326
|
+
|
|
327
|
+
expect(await table.updateRow(
|
|
328
|
+
(row) => row.id === rowA.id,
|
|
329
|
+
(row) => ({
|
|
330
|
+
...row,
|
|
331
|
+
label: "updated-by-updateRow",
|
|
332
|
+
}),
|
|
333
|
+
)).toEqual({
|
|
334
|
+
...rowA,
|
|
335
|
+
label: "updated-by-updateRow",
|
|
336
|
+
})
|
|
337
|
+
expect(await table.updateRow(
|
|
338
|
+
(row) => row.id === "missing",
|
|
339
|
+
(row) => row,
|
|
340
|
+
)).toBeUndefined()
|
|
341
|
+
expect(await table.updateRow(
|
|
342
|
+
(row) => row.id === rowA.id,
|
|
343
|
+
(row) => ({
|
|
344
|
+
...row,
|
|
345
|
+
id: "changed",
|
|
346
|
+
}),
|
|
347
|
+
)).toBeUndefined()
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
test("BaseTable.updateRows updates all matching rows and isolates predicate mutations", async () => {
|
|
351
|
+
const table = createTestTable()
|
|
352
|
+
const [rowA, rowB, rowC] = createRows()
|
|
353
|
+
|
|
354
|
+
await table.safeInsertRows([rowA, rowB, rowC])
|
|
355
|
+
|
|
356
|
+
const updatedRows = await table.updateRows(
|
|
357
|
+
(row) => {
|
|
358
|
+
row.status = "mutated-in-getter"
|
|
359
|
+
return row.count >= 2
|
|
360
|
+
},
|
|
361
|
+
(row) => ({
|
|
362
|
+
...row,
|
|
363
|
+
count: row.count + 10,
|
|
364
|
+
}),
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
expect(updatedRows).toEqual([
|
|
368
|
+
{
|
|
369
|
+
...rowB,
|
|
370
|
+
count: 12,
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
...rowC,
|
|
374
|
+
count: 13,
|
|
375
|
+
},
|
|
376
|
+
])
|
|
377
|
+
expect(await table.getRow((row) => row.id === rowA.id)).toEqual(rowA)
|
|
378
|
+
|
|
379
|
+
await expect(table.updateRows(
|
|
380
|
+
() => true,
|
|
381
|
+
(row) => ({
|
|
382
|
+
...row,
|
|
383
|
+
id: `${row.id}-changed`,
|
|
384
|
+
}),
|
|
385
|
+
)).rejects.toThrow("Unique cannot be changed")
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
test("BaseTable.upsertRow inserts new rows and returns merged stored rows for updates", async () => {
|
|
389
|
+
const table = createTestTable()
|
|
390
|
+
const [rowA, rowB] = createRows()
|
|
391
|
+
|
|
392
|
+
expect(await table.upsertRow(rowA)).toEqual(rowA)
|
|
393
|
+
expect(await table.upsertRow({
|
|
394
|
+
id: rowA.id,
|
|
395
|
+
label: "upserted",
|
|
396
|
+
count: 20,
|
|
397
|
+
meta: {
|
|
398
|
+
rank: 20,
|
|
399
|
+
},
|
|
400
|
+
})).toEqual({
|
|
401
|
+
...rowA,
|
|
402
|
+
label: "upserted",
|
|
403
|
+
count: 20,
|
|
404
|
+
meta: {
|
|
405
|
+
rank: 20,
|
|
406
|
+
},
|
|
407
|
+
})
|
|
408
|
+
expect(await table.upsertRow(rowB)).toEqual(rowB)
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
test("BaseTable.upsertRows returns merged stored rows and rejects duplicate batch uniques", async () => {
|
|
412
|
+
const table = createTestTable()
|
|
413
|
+
const [rowA, rowB] = createRows()
|
|
414
|
+
|
|
415
|
+
await table.safeInsertRow(rowA)
|
|
416
|
+
|
|
417
|
+
expect(await table.upsertRows([
|
|
418
|
+
{
|
|
419
|
+
id: rowA.id,
|
|
420
|
+
label: "merged",
|
|
421
|
+
count: 21,
|
|
422
|
+
meta: {
|
|
423
|
+
rank: 21,
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
rowB,
|
|
427
|
+
])).toEqual([
|
|
428
|
+
{
|
|
429
|
+
...rowA,
|
|
430
|
+
label: "merged",
|
|
431
|
+
count: 21,
|
|
432
|
+
meta: {
|
|
433
|
+
rank: 21,
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
rowB,
|
|
437
|
+
])
|
|
438
|
+
|
|
439
|
+
await expect(table.upsertRows([
|
|
440
|
+
createRow({ id: rowA.id }),
|
|
441
|
+
createRow({ id: rowA.id, label: "duplicate" }),
|
|
442
|
+
])).rejects.toThrow('Duplicate row unique "row-1" found in batch')
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
test("BaseTable.safeSimpleDeleteRow deletes by unique and returns the stored row", async () => {
|
|
446
|
+
const table = createTestTable()
|
|
447
|
+
const [rowA] = createRows()
|
|
448
|
+
|
|
449
|
+
await table.safeInsertRow(rowA)
|
|
450
|
+
|
|
451
|
+
expect(await table.safeSimpleDeleteRow({
|
|
452
|
+
id: rowA.id,
|
|
453
|
+
label: "ignored",
|
|
454
|
+
count: 0,
|
|
455
|
+
meta: {
|
|
456
|
+
rank: 0,
|
|
457
|
+
},
|
|
458
|
+
})).toEqual(rowA)
|
|
459
|
+
await expect(table.safeSimpleDeleteRow(createRow({ id: "missing" }))).rejects.toThrow("Row not found")
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
test("BaseTable.simpleDeleteRow returns undefined when the unique does not exist", async () => {
|
|
463
|
+
const table = createTestTable()
|
|
464
|
+
const [rowA] = createRows()
|
|
465
|
+
|
|
466
|
+
await table.safeInsertRow(rowA)
|
|
467
|
+
|
|
468
|
+
expect(await table.simpleDeleteRow({
|
|
469
|
+
id: rowA.id,
|
|
470
|
+
label: "ignored",
|
|
471
|
+
count: 0,
|
|
472
|
+
meta: {
|
|
473
|
+
rank: 0,
|
|
474
|
+
},
|
|
475
|
+
})).toEqual(rowA)
|
|
476
|
+
expect(await table.simpleDeleteRow(createRow({ id: "missing" }))).toBeUndefined()
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
test("BaseTable.simpleDeleteRows returns stored rows and rejects duplicate batch uniques", async () => {
|
|
480
|
+
const table = createTestTable()
|
|
481
|
+
const [rowA, rowB] = createRows()
|
|
482
|
+
|
|
483
|
+
await table.safeInsertRows([rowA, rowB])
|
|
484
|
+
|
|
485
|
+
expect(await table.simpleDeleteRows([
|
|
486
|
+
{
|
|
487
|
+
id: rowA.id,
|
|
488
|
+
label: "ignored",
|
|
489
|
+
count: 0,
|
|
490
|
+
meta: {
|
|
491
|
+
rank: 0,
|
|
492
|
+
},
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
id: rowB.id,
|
|
496
|
+
label: "ignored",
|
|
497
|
+
count: 0,
|
|
498
|
+
meta: {
|
|
499
|
+
rank: 0,
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
])).toEqual([rowA, rowB])
|
|
503
|
+
|
|
504
|
+
await table.safeInsertRows([rowA, rowB])
|
|
505
|
+
await expect(table.simpleDeleteRows([
|
|
506
|
+
createRow({ id: rowA.id }),
|
|
507
|
+
createRow({ id: rowA.id, label: "duplicate" }),
|
|
508
|
+
])).rejects.toThrow('Duplicate row unique "row-1" found in batch')
|
|
509
|
+
})
|
|
510
|
+
|
|
511
|
+
test("BaseTable.safeDeleteRow deletes one matched row and rejects zero or many matches", async () => {
|
|
512
|
+
const table = createTestTable()
|
|
513
|
+
const [rowA, rowB] = createRows()
|
|
514
|
+
|
|
515
|
+
await table.safeInsertRows([rowA, rowB])
|
|
516
|
+
|
|
517
|
+
expect(await table.safeDeleteRow((row) => row.id === rowA.id)).toEqual(rowA)
|
|
518
|
+
await expect(table.safeDeleteRow((row) => row.id === "missing")).rejects.toThrow("Row not found.")
|
|
519
|
+
|
|
520
|
+
await table.safeInsertRow(rowA)
|
|
521
|
+
await expect(table.safeDeleteRow((row) => row.count >= 1)).rejects.toThrow("Multiple rows found")
|
|
522
|
+
})
|
|
523
|
+
|
|
524
|
+
test("BaseTable.deleteRow returns undefined for no matches and throws for multiple matches", async () => {
|
|
525
|
+
const table = createTestTable()
|
|
526
|
+
const [rowA, rowB] = createRows()
|
|
527
|
+
|
|
528
|
+
await table.safeInsertRows([rowA, rowB])
|
|
529
|
+
|
|
530
|
+
await expect(table.deleteRow((row) => row.count >= 1)).rejects.toThrow("Multiple rows found")
|
|
531
|
+
expect(await table.deleteRow((row) => row.id === rowA.id)).toEqual(rowA)
|
|
532
|
+
expect(await table.deleteRow((row) => row.id === "missing")).toBeUndefined()
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
test("BaseTable.deleteRows deletes all matched rows and keeps predicate mutations isolated", async () => {
|
|
536
|
+
const table = createTestTable()
|
|
537
|
+
const [rowA, rowB, rowC] = createRows()
|
|
538
|
+
|
|
539
|
+
await table.safeInsertRows([rowA, rowB, rowC])
|
|
540
|
+
|
|
541
|
+
expect(await table.deleteRows((row) => {
|
|
542
|
+
row.status = "mutated-in-getter"
|
|
543
|
+
return row.count >= 2
|
|
544
|
+
})).toEqual([rowB, rowC])
|
|
545
|
+
expect(await table.getRows(() => true)).toEqual([rowA])
|
|
546
|
+
})
|
|
547
|
+
|
|
548
|
+
test("BaseTable event valueChanged publishes cloned snapshots for successive mutations", async () => {
|
|
549
|
+
const table = createTestTable()
|
|
550
|
+
const [rowA, rowB] = createRows()
|
|
551
|
+
const snapshots: TestRow[][] = []
|
|
552
|
+
|
|
553
|
+
table.event.subscribe("valueChanged", (rows) => {
|
|
554
|
+
snapshots.push(rows)
|
|
555
|
+
})
|
|
556
|
+
|
|
557
|
+
await table.safeInsertRow(rowA)
|
|
558
|
+
await table.upsertRow({
|
|
559
|
+
id: rowA.id,
|
|
560
|
+
label: "updated",
|
|
561
|
+
count: 30,
|
|
562
|
+
meta: {
|
|
563
|
+
rank: 30,
|
|
564
|
+
},
|
|
565
|
+
})
|
|
566
|
+
await table.safeInsertRow(rowB)
|
|
567
|
+
await table.deleteRow((row) => row.id === rowB.id)
|
|
568
|
+
|
|
569
|
+
snapshots[0]![0]!.label = "mutated-outside"
|
|
570
|
+
|
|
571
|
+
expect(snapshots).toEqual([
|
|
572
|
+
[
|
|
573
|
+
{
|
|
574
|
+
...rowA,
|
|
575
|
+
label: "mutated-outside",
|
|
576
|
+
},
|
|
577
|
+
],
|
|
578
|
+
[
|
|
579
|
+
{
|
|
580
|
+
...rowA,
|
|
581
|
+
label: "updated",
|
|
582
|
+
count: 30,
|
|
583
|
+
meta: {
|
|
584
|
+
rank: 30,
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
],
|
|
588
|
+
[
|
|
589
|
+
{
|
|
590
|
+
...rowA,
|
|
591
|
+
label: "updated",
|
|
592
|
+
count: 30,
|
|
593
|
+
meta: {
|
|
594
|
+
rank: 30,
|
|
595
|
+
},
|
|
596
|
+
},
|
|
597
|
+
rowB,
|
|
598
|
+
],
|
|
599
|
+
[
|
|
600
|
+
{
|
|
601
|
+
...rowA,
|
|
602
|
+
label: "updated",
|
|
603
|
+
count: 30,
|
|
604
|
+
meta: {
|
|
605
|
+
rank: 30,
|
|
606
|
+
},
|
|
607
|
+
},
|
|
608
|
+
],
|
|
609
|
+
])
|
|
610
|
+
expect(await table.getRows(() => true)).toEqual([
|
|
611
|
+
{
|
|
612
|
+
...rowA,
|
|
613
|
+
label: "updated",
|
|
614
|
+
count: 30,
|
|
615
|
+
meta: {
|
|
616
|
+
rank: 30,
|
|
617
|
+
},
|
|
618
|
+
},
|
|
619
|
+
])
|
|
620
|
+
})
|