@voidhash/mimic 0.0.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/package.json +33 -0
- package/src/Document.ts +256 -0
- package/src/FractionalIndex.ts +1249 -0
- package/src/Operation.ts +59 -0
- package/src/OperationDefinition.ts +23 -0
- package/src/OperationPath.ts +197 -0
- package/src/Presence.ts +142 -0
- package/src/Primitive.ts +32 -0
- package/src/Proxy.ts +8 -0
- package/src/ProxyEnvironment.ts +52 -0
- package/src/Transaction.ts +72 -0
- package/src/Transform.ts +13 -0
- package/src/client/ClientDocument.ts +1163 -0
- package/src/client/Rebase.ts +309 -0
- package/src/client/StateMonitor.ts +307 -0
- package/src/client/Transport.ts +318 -0
- package/src/client/WebSocketTransport.ts +572 -0
- package/src/client/errors.ts +145 -0
- package/src/client/index.ts +61 -0
- package/src/index.ts +12 -0
- package/src/primitives/Array.ts +457 -0
- package/src/primitives/Boolean.ts +128 -0
- package/src/primitives/Lazy.ts +89 -0
- package/src/primitives/Literal.ts +128 -0
- package/src/primitives/Number.ts +169 -0
- package/src/primitives/String.ts +189 -0
- package/src/primitives/Struct.ts +348 -0
- package/src/primitives/Tree.ts +1120 -0
- package/src/primitives/TreeNode.ts +113 -0
- package/src/primitives/Union.ts +329 -0
- package/src/primitives/shared.ts +122 -0
- package/src/server/ServerDocument.ts +267 -0
- package/src/server/errors.ts +90 -0
- package/src/server/index.ts +40 -0
- package/tests/Document.test.ts +556 -0
- package/tests/FractionalIndex.test.ts +377 -0
- package/tests/OperationPath.test.ts +151 -0
- package/tests/Presence.test.ts +321 -0
- package/tests/Primitive.test.ts +381 -0
- package/tests/client/ClientDocument.test.ts +1398 -0
- package/tests/client/WebSocketTransport.test.ts +992 -0
- package/tests/primitives/Array.test.ts +418 -0
- package/tests/primitives/Boolean.test.ts +126 -0
- package/tests/primitives/Lazy.test.ts +143 -0
- package/tests/primitives/Literal.test.ts +122 -0
- package/tests/primitives/Number.test.ts +133 -0
- package/tests/primitives/String.test.ts +128 -0
- package/tests/primitives/Struct.test.ts +311 -0
- package/tests/primitives/Tree.test.ts +467 -0
- package/tests/primitives/TreeNode.test.ts +50 -0
- package/tests/primitives/Union.test.ts +210 -0
- package/tests/server/ServerDocument.test.ts +528 -0
- package/tsconfig.build.json +24 -0
- package/tsconfig.json +8 -0
- package/tsdown.config.ts +18 -0
- package/vitest.mts +11 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll } from "@effect/vitest"
|
|
2
|
+
import { Effect, Random } from "effect"
|
|
3
|
+
import {
|
|
4
|
+
base62CharSet,
|
|
5
|
+
generateJitteredKeyBetween,
|
|
6
|
+
generateKeyBetween,
|
|
7
|
+
generateNJitteredKeysBetween,
|
|
8
|
+
generateNKeysBetween,
|
|
9
|
+
indexCharacterSet,
|
|
10
|
+
IndexGenerator,
|
|
11
|
+
} from "../src/FractionalIndex"
|
|
12
|
+
|
|
13
|
+
describe("generateKeyBetween", () => {
|
|
14
|
+
const charSet = base62CharSet()
|
|
15
|
+
it.effect.each([
|
|
16
|
+
// a, expected, b
|
|
17
|
+
[null, "a0", null],
|
|
18
|
+
[null, "a0", "a1"],
|
|
19
|
+
[null, "Zz", "a0"],
|
|
20
|
+
[null, "b0S", "b0T"],
|
|
21
|
+
["b0S", "b0T", null],
|
|
22
|
+
["a0", "a4", "a8"],
|
|
23
|
+
["a0", "a0V", "a1"],
|
|
24
|
+
])("a:%s mid: %s b:%s", ([a, expected, b]: any) =>
|
|
25
|
+
Effect.gen(function* () {
|
|
26
|
+
const result = yield* generateKeyBetween(a, b, charSet)
|
|
27
|
+
expect(result).toBe(expected)
|
|
28
|
+
})
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
it("should fail if a >= b", () => Effect.gen(function* () {
|
|
32
|
+
const result1 = yield* generateKeyBetween("a0", "a0", charSet).pipe(Effect.either)
|
|
33
|
+
expect(result1._tag).toBe("Left")
|
|
34
|
+
|
|
35
|
+
const result2 = yield* generateKeyBetween("a1", "a0", charSet).pipe(Effect.either)
|
|
36
|
+
expect(result2._tag).toBe("Left")
|
|
37
|
+
}))
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe("generateJitteredKeyBetween", () => {
|
|
41
|
+
const charSet = base62CharSet()
|
|
42
|
+
it.effect.each([
|
|
43
|
+
// a, expected, b
|
|
44
|
+
[null, "a06CO", null],
|
|
45
|
+
[null, "a06CO", "a1"],
|
|
46
|
+
[null, "Zz6CO", "a0"],
|
|
47
|
+
[null, "b0S6CO", "b0T46n"],
|
|
48
|
+
["b0S", "b0T6CO", null],
|
|
49
|
+
["a0", "a46CO", "a8"],
|
|
50
|
+
["a0", "a0V6CO", "a1"],
|
|
51
|
+
])("a:%s mid: %s b:%s, should not mess up integer part", ([a, expected, b]: any) =>
|
|
52
|
+
Effect.gen(function* () {
|
|
53
|
+
const result = yield* generateJitteredKeyBetween(a, b, charSet).pipe(Effect.withRandomFixed([0.5])) as Effect.Effect<string, Error>
|
|
54
|
+
expect(result).toBe(expected)
|
|
55
|
+
})
|
|
56
|
+
)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe("generateNKeysBetween", () => {
|
|
60
|
+
const charSet = base62CharSet()
|
|
61
|
+
it('should generate 3 keys between "a0" and "a1"', () => Effect.gen(function* () {
|
|
62
|
+
const keys = yield* generateNKeysBetween("a0", "a1", 3, charSet)
|
|
63
|
+
expect(keys.length).toBe(3)
|
|
64
|
+
expect(keys).toStrictEqual(["a0F", "a0V", "a0k"])
|
|
65
|
+
}))
|
|
66
|
+
|
|
67
|
+
it('should generate 3 keys after "b01"', () => Effect.gen(function* () {
|
|
68
|
+
const keys = yield* generateNKeysBetween("b01", null, 3, charSet)
|
|
69
|
+
expect(keys.length).toBe(3)
|
|
70
|
+
expect(keys).toStrictEqual(["b02", "b03", "b04"])
|
|
71
|
+
}))
|
|
72
|
+
|
|
73
|
+
it('should generate 3 keys before "a0"', () => Effect.gen(function* () {
|
|
74
|
+
const keys = yield* generateNKeysBetween(null, "a0", 3, charSet)
|
|
75
|
+
expect(keys.length).toBe(3)
|
|
76
|
+
expect(keys).toStrictEqual(["Zx", "Zy", "Zz"])
|
|
77
|
+
}))
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe("generateNJitteredKeysBetween", () => {
|
|
81
|
+
const charSet = base62CharSet()
|
|
82
|
+
it('should generate 3 keys between "a0" and "a1"', () => Effect.gen(function* () {
|
|
83
|
+
const keys = yield* generateNJitteredKeysBetween("a0", "a1", 3, charSet).pipe(Effect.withRandomFixed([0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5])) as Effect.Effect<string[], Error>
|
|
84
|
+
expect(keys.length).toBe(3)
|
|
85
|
+
expect(keys).toStrictEqual(["a0FeIa", "a0V6CO", "a0keIa"])
|
|
86
|
+
}))
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
describe("Basic Generator", () => {
|
|
90
|
+
let generator: IndexGenerator
|
|
91
|
+
beforeAll(() => {
|
|
92
|
+
generator = new IndexGenerator([], { useJitter: false })
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it("should generate correct keys for an empty list", () => Effect.gen(function* () {
|
|
96
|
+
const key1 = yield* generator.keyStart();
|
|
97
|
+
expect(key1).toBe("a0")
|
|
98
|
+
const key2 = yield* generator.keyEnd();
|
|
99
|
+
expect(key2).toBe("a0")
|
|
100
|
+
const keysStart = yield* generator.nKeysStart(2);
|
|
101
|
+
expect(keysStart).toStrictEqual(["a0", "a1"])
|
|
102
|
+
const keysEnd = yield* generator.nKeysEnd(2);
|
|
103
|
+
expect(keysEnd).toStrictEqual(["a0", "a1"])
|
|
104
|
+
}))
|
|
105
|
+
|
|
106
|
+
it("should generate correct start keys for populated list", () => Effect.gen(function* () {
|
|
107
|
+
generator.updateList(["a0"])
|
|
108
|
+
const key = yield* generator.keyStart()
|
|
109
|
+
const keys = yield* generator.nKeysStart(2)
|
|
110
|
+
expect(key).toBe("Zz")
|
|
111
|
+
expect(keys).toStrictEqual(["Zy", "Zz"])
|
|
112
|
+
}))
|
|
113
|
+
|
|
114
|
+
it("should generate correct end keys for populated list", () => Effect.gen(function* () {
|
|
115
|
+
generator.updateList(["a1"])
|
|
116
|
+
const key = yield* generator.keyEnd()
|
|
117
|
+
const keys = yield* generator.nKeysEnd(2)
|
|
118
|
+
expect(key).toBe("a2")
|
|
119
|
+
expect(keys).toStrictEqual(["a2", "a3"])
|
|
120
|
+
}))
|
|
121
|
+
|
|
122
|
+
it("should generate correct keys after if last item", () => Effect.gen(function* () {
|
|
123
|
+
generator.updateList(["a1"])
|
|
124
|
+
const key = yield* generator.keyAfter("a1")
|
|
125
|
+
const keys = yield* generator.nKeysAfter("a1", 2)
|
|
126
|
+
expect(key).toBe("a2")
|
|
127
|
+
expect(keys).toStrictEqual(["a2", "a3"])
|
|
128
|
+
}))
|
|
129
|
+
|
|
130
|
+
it("should generate correct keys after not last item", () => Effect.gen(function* () {
|
|
131
|
+
generator.updateList(["a1", "a2"])
|
|
132
|
+
const key = yield* generator.keyAfter("a1")
|
|
133
|
+
const keys = yield* generator.nKeysAfter("a1", 3)
|
|
134
|
+
expect(key).toBe("a1V")
|
|
135
|
+
expect(keys).toStrictEqual(["a1F", "a1V", "a1k"])
|
|
136
|
+
}))
|
|
137
|
+
|
|
138
|
+
it("should generate correct keys before if first item", () => Effect.gen(function* () {
|
|
139
|
+
generator.updateList(["a5"])
|
|
140
|
+
const key = yield* generator.keyBefore("a5")
|
|
141
|
+
const keys = yield* generator.nKeysBefore("a5", 2)
|
|
142
|
+
expect(key).toBe("a4")
|
|
143
|
+
expect(keys).toStrictEqual(["a3", "a4"])
|
|
144
|
+
}))
|
|
145
|
+
|
|
146
|
+
it("should generate correct keys before if not first item", () => Effect.gen(function* () {
|
|
147
|
+
generator.updateList(["a1", "a2"])
|
|
148
|
+
const key = yield* generator.keyBefore("a2")
|
|
149
|
+
const keys = yield* generator.nKeysBefore("a2", 3)
|
|
150
|
+
expect(key).toBe("a1V")
|
|
151
|
+
expect(keys).toStrictEqual(["a1F", "a1V", "a1k"])
|
|
152
|
+
}))
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
describe("Jittered Generator", () => {
|
|
156
|
+
let generator: IndexGenerator
|
|
157
|
+
beforeAll(() => {
|
|
158
|
+
generator = new IndexGenerator([], { useJitter: true })
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it("should generate correct jittered keys", () => Effect.gen(function* () {
|
|
162
|
+
const keys = yield* generator.nKeysStart(3).pipe(Effect.withRandomFixed([0.5, 0.5, 0.5])) as Effect.Effect<string[], Error>
|
|
163
|
+
expect(keys).toStrictEqual(["a06CO", "a16CO", "a26CO"])
|
|
164
|
+
}))
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
describe("Group Generator", () => {
|
|
168
|
+
let generator: IndexGenerator
|
|
169
|
+
beforeAll(() => {
|
|
170
|
+
generator = new IndexGenerator([], { useJitter: false, groupIdLength: 2 })
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it("should generate correct keys for an empty list", () => Effect.gen(function* () {
|
|
174
|
+
const key1 = yield* generator.keyStart("g1")
|
|
175
|
+
expect(key1).toBe("g1a0")
|
|
176
|
+
const key2 = yield* generator.keyEnd("g1")
|
|
177
|
+
expect(key2).toBe("g1a0")
|
|
178
|
+
const keysStart = yield* generator.nKeysStart(2, "g1")
|
|
179
|
+
expect(keysStart).toStrictEqual(["g1a0", "g1a1"])
|
|
180
|
+
const keysEnd = yield* generator.nKeysEnd(2, "g1")
|
|
181
|
+
expect(keysEnd).toStrictEqual(["g1a0", "g1a1"])
|
|
182
|
+
}))
|
|
183
|
+
|
|
184
|
+
it("should fail if groupId is not supplied", () => Effect.gen(function* () {
|
|
185
|
+
const result = yield* generator.keyStart().pipe(Effect.either)
|
|
186
|
+
expect(result._tag).toBe("Left")
|
|
187
|
+
}))
|
|
188
|
+
|
|
189
|
+
it("should fail if groupId is incorrect length", () => Effect.gen(function* () {
|
|
190
|
+
const result = yield* generator.keyStart("group1").pipe(Effect.either)
|
|
191
|
+
expect(result._tag).toBe("Left")
|
|
192
|
+
}))
|
|
193
|
+
|
|
194
|
+
it("should generate correct start keys for populated list", () => Effect.gen(function* () {
|
|
195
|
+
generator.updateList(["g1a0"])
|
|
196
|
+
const key = yield* generator.keyStart("g1")
|
|
197
|
+
const keys = yield* generator.nKeysStart(2, "g1")
|
|
198
|
+
expect(key).toBe("g1Zz")
|
|
199
|
+
expect(keys).toStrictEqual(["g1Zy", "g1Zz"])
|
|
200
|
+
}))
|
|
201
|
+
|
|
202
|
+
it("should generate correct end keys for populated list", () => Effect.gen(function* () {
|
|
203
|
+
generator.updateList(["g1a1"])
|
|
204
|
+
const key = yield* generator.keyEnd("g1")
|
|
205
|
+
const keys = yield* generator.nKeysEnd(2, "g1")
|
|
206
|
+
expect(key).toBe("g1a2")
|
|
207
|
+
expect(keys).toStrictEqual(["g1a2", "g1a3"])
|
|
208
|
+
}))
|
|
209
|
+
|
|
210
|
+
it("should generate correct keys after if last item", () => Effect.gen(function* () {
|
|
211
|
+
generator.updateList(["g1a1"])
|
|
212
|
+
const key = yield* generator.keyAfter("g1a1")
|
|
213
|
+
const keys = yield* generator.nKeysAfter("g1a1", 2)
|
|
214
|
+
expect(key).toBe("g1a2")
|
|
215
|
+
expect(keys).toStrictEqual(["g1a2", "g1a3"])
|
|
216
|
+
}))
|
|
217
|
+
|
|
218
|
+
it("should generate correct keys after not last item", () => Effect.gen(function* () {
|
|
219
|
+
generator.updateList(["g1a1", "g1a2"])
|
|
220
|
+
const key = yield* generator.keyAfter("g1a1")
|
|
221
|
+
const keys = yield* generator.nKeysAfter("g1a1", 3)
|
|
222
|
+
expect(key).toBe("g1a1V")
|
|
223
|
+
expect(keys).toStrictEqual(["g1a1F", "g1a1V", "g1a1k"])
|
|
224
|
+
}))
|
|
225
|
+
|
|
226
|
+
it("should generate correct keys before if first item", () => Effect.gen(function* () {
|
|
227
|
+
generator.updateList(["g1a5"])
|
|
228
|
+
const key = yield* generator.keyBefore("g1a5")
|
|
229
|
+
const keys = yield* generator.nKeysBefore("g1a5", 2)
|
|
230
|
+
expect(key).toBe("g1a4")
|
|
231
|
+
expect(keys).toStrictEqual(["g1a3", "g1a4"])
|
|
232
|
+
}))
|
|
233
|
+
|
|
234
|
+
it("should generate correct keys before if not first item", () => Effect.gen(function* () {
|
|
235
|
+
generator.updateList(["g1a1", "g1a2"])
|
|
236
|
+
const key = yield* generator.keyBefore("g1a2")
|
|
237
|
+
const keys = yield* generator.nKeysBefore("g1a2", 3)
|
|
238
|
+
expect(key).toBe("g1a1V")
|
|
239
|
+
expect(keys).toStrictEqual(["g1a1F", "g1a1V", "g1a1k"])
|
|
240
|
+
}))
|
|
241
|
+
|
|
242
|
+
it("should generate correct new start key for populated list and different group", () => Effect.gen(function* () {
|
|
243
|
+
generator.updateList(["g1a5"])
|
|
244
|
+
const key1 = yield* generator.keyStart("g2")
|
|
245
|
+
expect(key1).toBe("g2a0")
|
|
246
|
+
}))
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
describe("readme examples", () => {
|
|
250
|
+
it("should run generator example without errors", () => Effect.gen(function* () {
|
|
251
|
+
const generator = new IndexGenerator([])
|
|
252
|
+
|
|
253
|
+
// dummy code, would normally be stored in database or CRDT and updated from there
|
|
254
|
+
const list: string[] = []
|
|
255
|
+
function updateList(newKey: string) {
|
|
256
|
+
list.push(newKey)
|
|
257
|
+
generator.updateList(list)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const first = yield* generator.keyStart() // "a01TB" a0 with jitter
|
|
261
|
+
updateList(first)
|
|
262
|
+
|
|
263
|
+
const second = yield* generator.keyEnd() // "a10Vt" a1 with jitter
|
|
264
|
+
updateList(second)
|
|
265
|
+
|
|
266
|
+
const firstAndHalf = yield* generator.keyAfter(first) // "a0fMq" midpoint between firstKey and secondKey
|
|
267
|
+
updateList(firstAndHalf)
|
|
268
|
+
|
|
269
|
+
const firstAndQuarter = yield* generator.keyBefore(firstAndHalf) // "a0M3o" midpoint between firstKey and keyInBetween
|
|
270
|
+
updateList(firstAndQuarter)
|
|
271
|
+
|
|
272
|
+
// [ 'a01TB', 'a0M3o', 'a0fMq', 'a10Vt' ]
|
|
273
|
+
// [ first, firstAndHalf, firstAndQuarter, second ]
|
|
274
|
+
// console.log(list.sort())
|
|
275
|
+
}))
|
|
276
|
+
|
|
277
|
+
it("should run generator group code without errors", () => Effect.gen(function* () {
|
|
278
|
+
// Jitter is disabled for this example to make the output more readable, but should be preferred in production
|
|
279
|
+
const generator = new IndexGenerator([], {
|
|
280
|
+
useJitter: false,
|
|
281
|
+
groupIdLength: 2,
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
const list: string[] = []
|
|
285
|
+
// dummy code, would normally be stored in database or CRDT and updated from there
|
|
286
|
+
function updateList(orderKey: string) {
|
|
287
|
+
list.push(orderKey)
|
|
288
|
+
generator.updateList(list)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// same length as groupIdLength
|
|
292
|
+
const group1 = "g1"
|
|
293
|
+
const group2 = "g2"
|
|
294
|
+
|
|
295
|
+
// "g1a0" group1 and first key
|
|
296
|
+
const first = yield* generator.keyStart(group1)
|
|
297
|
+
updateList(first)
|
|
298
|
+
|
|
299
|
+
// "g1a1" group1 and first key
|
|
300
|
+
const second = yield* generator.keyEnd(group1)
|
|
301
|
+
updateList(second)
|
|
302
|
+
|
|
303
|
+
// "g1a0V" midpoint between first and second
|
|
304
|
+
const firstAndAHalf = yield* generator.keyAfter(first)
|
|
305
|
+
updateList(firstAndAHalf)
|
|
306
|
+
|
|
307
|
+
// "g2a0" group2 and first key
|
|
308
|
+
const firstGroup2 = yield* generator.keyStart(group2)
|
|
309
|
+
updateList(firstGroup2)
|
|
310
|
+
|
|
311
|
+
// ["g1a0", "g1a0V", "g1a1", "g2a0"]
|
|
312
|
+
// [ first, firstAndAHalf, second, firstGroup2 ]
|
|
313
|
+
// console.log(list.sort())
|
|
314
|
+
}))
|
|
315
|
+
|
|
316
|
+
it("should run generateJitteredKeyBetween", () => Effect.gen(function* () {
|
|
317
|
+
const first = yield* generateJitteredKeyBetween(null, null).pipe(Effect.withRandomFixed([0.5])) as Effect.Effect<string, Error>
|
|
318
|
+
// "a090d" (with fixed random)
|
|
319
|
+
|
|
320
|
+
// Insert after 1st
|
|
321
|
+
const second = yield* generateJitteredKeyBetween(first, null).pipe(Effect.withRandomFixed([0.5])) as Effect.Effect<string, Error>
|
|
322
|
+
// "a1C1i" (with fixed random)
|
|
323
|
+
|
|
324
|
+
// Insert after 2nd
|
|
325
|
+
const third = yield* generateJitteredKeyBetween(second, null).pipe(Effect.withRandomFixed([0.5])) as Effect.Effect<string, Error>
|
|
326
|
+
// "a28hy" (with fixed random)
|
|
327
|
+
|
|
328
|
+
// Insert before 1st
|
|
329
|
+
const zeroth = yield* generateJitteredKeyBetween(null, first).pipe(Effect.withRandomFixed([0.5])) as Effect.Effect<string, Error>
|
|
330
|
+
// "ZzBYL" (with fixed random)
|
|
331
|
+
|
|
332
|
+
// Insert in between 2nd and 3rd (midpoint)
|
|
333
|
+
const secondAndHalf = yield* generateJitteredKeyBetween(second, third).pipe(Effect.withRandomFixed([0.5])) as Effect.Effect<string, Error>
|
|
334
|
+
// "a1kek" (with fixed random)
|
|
335
|
+
|
|
336
|
+
// console.log(first, second, third, zeroth, secondAndHalf)
|
|
337
|
+
}))
|
|
338
|
+
|
|
339
|
+
it("should run generateNJitteredKeysBetween", () => Effect.gen(function* () {
|
|
340
|
+
const first = yield* generateNJitteredKeysBetween(null, null, 2).pipe(Effect.withRandomFixed([0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5])) as Effect.Effect<string[], Error>
|
|
341
|
+
// ['a061p', 'a18Ev'] (with fixed random)
|
|
342
|
+
|
|
343
|
+
// Insert two keys after 2nd
|
|
344
|
+
yield* generateNJitteredKeysBetween(first[1]!, null, 2).pipe(Effect.withRandomFixed([0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5])) as Effect.Effect<string[], Error>
|
|
345
|
+
// ['a23WQ', 'a315m'] (with fixed random)
|
|
346
|
+
|
|
347
|
+
// Insert two keys before 1st
|
|
348
|
+
yield* generateNJitteredKeysBetween(null, first[0]!, 2).pipe(Effect.withRandomFixed([0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5])) as Effect.Effect<string[], Error>
|
|
349
|
+
// ['Zy6Gx', 'ZzB7s'] (with fixed random)
|
|
350
|
+
|
|
351
|
+
// Insert two keys in between 1st and 2nd (midpoints)
|
|
352
|
+
// yield* generateNJitteredKeysBetween(second, third, 2).pipe(Effect.withRandomFixed([0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5])) as Effect.Effect<string[], Error>
|
|
353
|
+
// ['a0SIA', 'a0iDa'] (with fixed random)
|
|
354
|
+
}))
|
|
355
|
+
|
|
356
|
+
it("should run indexCharacterSet", () => Effect.gen(function* () {
|
|
357
|
+
const base90Set = yield* indexCharacterSet({
|
|
358
|
+
chars:
|
|
359
|
+
"!#$%&()*+,./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~",
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
const first = yield* generateKeyBetween(null, null, base90Set) // 'Q!'
|
|
363
|
+
|
|
364
|
+
// Insert after 1st
|
|
365
|
+
const second = yield* generateKeyBetween(first, null, base90Set) // 'Q#'
|
|
366
|
+
|
|
367
|
+
// Insert in between 2nd and 3rd (midpoint)
|
|
368
|
+
const firstAndHalf = yield* generateKeyBetween(first, second, base90Set)
|
|
369
|
+
// 'Q!Q'
|
|
370
|
+
|
|
371
|
+
// Jittering is still recommended to avoid collisions
|
|
372
|
+
const jitteredStart = yield* generateNJitteredKeysBetween(null, null, 2, base90Set).pipe(Effect.withRandomFixed([0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5])) as Effect.Effect<string[], Error>
|
|
373
|
+
// [ 'Q!$i8', 'Q#.f}' ] (with fixed random)
|
|
374
|
+
|
|
375
|
+
// console.log(base90Set.jitterRange) // 145800 (so 3 times less likely to collide than base62)
|
|
376
|
+
}))
|
|
377
|
+
})
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { describe, expect, it } from "@effect/vitest";
|
|
2
|
+
import * as OperationPath from "../src/OperationPath";
|
|
3
|
+
|
|
4
|
+
describe("OperationPath", () => {
|
|
5
|
+
describe("pathsOverlap", () => {
|
|
6
|
+
it("should return true for identical paths", () => {
|
|
7
|
+
const pathA = OperationPath.make("users/0/name");
|
|
8
|
+
const pathB = OperationPath.make("users/0/name");
|
|
9
|
+
|
|
10
|
+
expect(OperationPath.pathsOverlap(pathA, pathB)).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("should return true when pathA is a prefix of pathB", () => {
|
|
14
|
+
const pathA = OperationPath.make("users");
|
|
15
|
+
const pathB = OperationPath.make("users/0/name");
|
|
16
|
+
|
|
17
|
+
expect(OperationPath.pathsOverlap(pathA, pathB)).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should return true when pathB is a prefix of pathA", () => {
|
|
21
|
+
const pathA = OperationPath.make("users/0/name");
|
|
22
|
+
const pathB = OperationPath.make("users");
|
|
23
|
+
|
|
24
|
+
expect(OperationPath.pathsOverlap(pathA, pathB)).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should return false for completely different paths", () => {
|
|
28
|
+
const pathA = OperationPath.make("users/0/name");
|
|
29
|
+
const pathB = OperationPath.make("settings/theme");
|
|
30
|
+
|
|
31
|
+
expect(OperationPath.pathsOverlap(pathA, pathB)).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should return false for paths that diverge at some point", () => {
|
|
35
|
+
const pathA = OperationPath.make("users/0/name");
|
|
36
|
+
const pathB = OperationPath.make("users/1/name");
|
|
37
|
+
|
|
38
|
+
expect(OperationPath.pathsOverlap(pathA, pathB)).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should return true for root paths", () => {
|
|
42
|
+
const pathA = OperationPath.make("");
|
|
43
|
+
const pathB = OperationPath.make("users/0/name");
|
|
44
|
+
|
|
45
|
+
expect(OperationPath.pathsOverlap(pathA, pathB)).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should return true for both root paths", () => {
|
|
49
|
+
const pathA = OperationPath.make("");
|
|
50
|
+
const pathB = OperationPath.make("");
|
|
51
|
+
|
|
52
|
+
expect(OperationPath.pathsOverlap(pathA, pathB)).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe("isPrefix", () => {
|
|
57
|
+
it("should return true when pathA is a strict prefix of pathB", () => {
|
|
58
|
+
const pathA = OperationPath.make("users");
|
|
59
|
+
const pathB = OperationPath.make("users/0/name");
|
|
60
|
+
|
|
61
|
+
expect(OperationPath.isPrefix(pathA, pathB)).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should return true for identical paths", () => {
|
|
65
|
+
const pathA = OperationPath.make("users/0/name");
|
|
66
|
+
const pathB = OperationPath.make("users/0/name");
|
|
67
|
+
|
|
68
|
+
expect(OperationPath.isPrefix(pathA, pathB)).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should return false when pathA is longer than pathB", () => {
|
|
72
|
+
const pathA = OperationPath.make("users/0/name");
|
|
73
|
+
const pathB = OperationPath.make("users");
|
|
74
|
+
|
|
75
|
+
expect(OperationPath.isPrefix(pathA, pathB)).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("should return false for non-prefix paths", () => {
|
|
79
|
+
const pathA = OperationPath.make("users/0");
|
|
80
|
+
const pathB = OperationPath.make("settings/theme");
|
|
81
|
+
|
|
82
|
+
expect(OperationPath.isPrefix(pathA, pathB)).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should return true when pathA is root", () => {
|
|
86
|
+
const pathA = OperationPath.make("");
|
|
87
|
+
const pathB = OperationPath.make("users/0/name");
|
|
88
|
+
|
|
89
|
+
expect(OperationPath.isPrefix(pathA, pathB)).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe("pathsEqual", () => {
|
|
94
|
+
it("should return true for identical paths", () => {
|
|
95
|
+
const pathA = OperationPath.make("users/0/name");
|
|
96
|
+
const pathB = OperationPath.make("users/0/name");
|
|
97
|
+
|
|
98
|
+
expect(OperationPath.pathsEqual(pathA, pathB)).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should return false for different length paths", () => {
|
|
102
|
+
const pathA = OperationPath.make("users");
|
|
103
|
+
const pathB = OperationPath.make("users/0/name");
|
|
104
|
+
|
|
105
|
+
expect(OperationPath.pathsEqual(pathA, pathB)).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("should return false for same length but different paths", () => {
|
|
109
|
+
const pathA = OperationPath.make("users/0/name");
|
|
110
|
+
const pathB = OperationPath.make("users/1/name");
|
|
111
|
+
|
|
112
|
+
expect(OperationPath.pathsEqual(pathA, pathB)).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("should return true for both root paths", () => {
|
|
116
|
+
const pathA = OperationPath.make("");
|
|
117
|
+
const pathB = OperationPath.make("");
|
|
118
|
+
|
|
119
|
+
expect(OperationPath.pathsEqual(pathA, pathB)).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("getRelativePath", () => {
|
|
124
|
+
it("should return remaining tokens when base is prefix", () => {
|
|
125
|
+
const basePath = OperationPath.make("users");
|
|
126
|
+
const fullPath = OperationPath.make("users/0/name");
|
|
127
|
+
|
|
128
|
+
const result = OperationPath.getRelativePath(basePath, fullPath);
|
|
129
|
+
|
|
130
|
+
expect(result).toEqual(["0", "name"]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("should return empty array for identical paths", () => {
|
|
134
|
+
const basePath = OperationPath.make("users/0/name");
|
|
135
|
+
const fullPath = OperationPath.make("users/0/name");
|
|
136
|
+
|
|
137
|
+
const result = OperationPath.getRelativePath(basePath, fullPath);
|
|
138
|
+
|
|
139
|
+
expect(result).toEqual([]);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("should handle root base path", () => {
|
|
143
|
+
const basePath = OperationPath.make("");
|
|
144
|
+
const fullPath = OperationPath.make("users/0/name");
|
|
145
|
+
|
|
146
|
+
const result = OperationPath.getRelativePath(basePath, fullPath);
|
|
147
|
+
|
|
148
|
+
expect(result).toEqual(["users", "0", "name"]);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|