Add Prettier formatting and Gitea Actions CI (format, typecheck, test, build)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qxbkp4tA7G5SCWjzWuxmE
This commit is contained in:
+14
-3
@@ -204,7 +204,10 @@ describe('index declarations', () => {
|
||||
})
|
||||
|
||||
describe('property: findBy equals brute-force filter', () => {
|
||||
const val = fc.record({ age: fc.option(fc.integer({ min: -3, max: 3 }), { nil: undefined }), tags: fc.array(fc.constantFrom('a', 'b', 'c/d'), { maxLength: 3 }) })
|
||||
const val = fc.record({
|
||||
age: fc.option(fc.integer({ min: -3, max: 3 }), { nil: undefined }),
|
||||
tags: fc.array(fc.constantFrom('a', 'b', 'c/d'), { maxLength: 3 }),
|
||||
})
|
||||
const op = fc.oneof(
|
||||
fc.record({ t: fc.constant('set' as const), k: fc.constantFrom('1', '2', '3', 'x/y'), v: val }),
|
||||
fc.record({ t: fc.constant('del' as const), k: fc.constantFrom('1', '2', '3', 'x/y') }),
|
||||
@@ -226,11 +229,19 @@ describe('property: findBy equals brute-force filter', () => {
|
||||
}
|
||||
}
|
||||
const got = (await collect(users.findBy('byAge', { gte: lo, lt: hi }))).map(([k]) => k).sort()
|
||||
const exp = [...model].filter(([, v]) => v.age !== undefined && v.age >= lo && v.age < hi).map(([k]) => k).sort()
|
||||
const exp = [...model]
|
||||
.filter(([, v]) => v.age !== undefined && v.age >= lo && v.age < hi)
|
||||
.map(([k]) => k)
|
||||
.sort()
|
||||
expect(got).toEqual(exp)
|
||||
for (const tag of ['a', 'b', 'c/d']) {
|
||||
const g = (await collect(users.findBy('byTag', tag))).map(([k]) => k).sort()
|
||||
expect(g).toEqual([...model].filter(([, v]) => v.tags?.includes(tag)).map(([k]) => k).sort())
|
||||
expect(g).toEqual(
|
||||
[...model]
|
||||
.filter(([, v]) => v.tags?.includes(tag))
|
||||
.map(([k]) => k)
|
||||
.sort(),
|
||||
)
|
||||
}
|
||||
// No stale entries are ever left behind by clean writes.
|
||||
let entryCount = 0
|
||||
|
||||
+51
-47
@@ -293,56 +293,60 @@ describe('model-based (random ops vs reference Map)', () => {
|
||||
] as const) {
|
||||
it(`matches the model (${name})`, async () => {
|
||||
await fc.assert(
|
||||
fc.asyncProperty(fc.array(opArb, { maxLength: 30 }), fc.array(fc.array(segArb, { maxLength: 2 }), { minLength: 1, maxLength: 3 }), async (ops, probes) => {
|
||||
const db = new KVStorage(make())
|
||||
const model = new Map<string, unknown>()
|
||||
for (const op of ops) {
|
||||
switch (op.t) {
|
||||
case 'set': {
|
||||
if (op.selfForm) await nodeAt(db, op.path).set(op.value)
|
||||
else await nodeAt(db, op.path.slice(0, -1)).set(op.path.at(-1)!, op.value)
|
||||
model.set(enc(op.path), op.value)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
if (op.selfForm) await nodeAt(db, op.path).delete()
|
||||
else await nodeAt(db, op.path.slice(0, -1)).delete(op.path.at(-1)!)
|
||||
model.delete(enc(op.path))
|
||||
break
|
||||
}
|
||||
case 'deleteTree': {
|
||||
await nodeAt(db, op.path).deleteTree()
|
||||
for (const k of [...model.keys()]) if (isPrefix(op.path, JSON.parse(k))) model.delete(k)
|
||||
break
|
||||
}
|
||||
case 'setMany': {
|
||||
await nodeAt(db, op.parent).setMany(op.entries)
|
||||
for (const [k, v] of op.entries) model.set(enc([...op.parent, k]), v)
|
||||
break
|
||||
fc.asyncProperty(
|
||||
fc.array(opArb, { maxLength: 30 }),
|
||||
fc.array(fc.array(segArb, { maxLength: 2 }), { minLength: 1, maxLength: 3 }),
|
||||
async (ops, probes) => {
|
||||
const db = new KVStorage(make())
|
||||
const model = new Map<string, unknown>()
|
||||
for (const op of ops) {
|
||||
switch (op.t) {
|
||||
case 'set': {
|
||||
if (op.selfForm) await nodeAt(db, op.path).set(op.value)
|
||||
else await nodeAt(db, op.path.slice(0, -1)).set(op.path.at(-1)!, op.value)
|
||||
model.set(enc(op.path), op.value)
|
||||
break
|
||||
}
|
||||
case 'delete': {
|
||||
if (op.selfForm) await nodeAt(db, op.path).delete()
|
||||
else await nodeAt(db, op.path.slice(0, -1)).delete(op.path.at(-1)!)
|
||||
model.delete(enc(op.path))
|
||||
break
|
||||
}
|
||||
case 'deleteTree': {
|
||||
await nodeAt(db, op.path).deleteTree()
|
||||
for (const k of [...model.keys()]) if (isPrefix(op.path, JSON.parse(k))) model.delete(k)
|
||||
break
|
||||
}
|
||||
case 'setMany': {
|
||||
await nodeAt(db, op.parent).setMany(op.entries)
|
||||
for (const [k, v] of op.entries) model.set(enc([...op.parent, k]), v)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const [k, v] of model) expect(await nodeAt(db, JSON.parse(k)).get()).toEqual(v)
|
||||
for (const parent of probes) {
|
||||
const node = nodeAt(db, parent)
|
||||
const exp = expectedChildren(model, parent)
|
||||
const keys = exp.map((e) => e.key)
|
||||
expect(await collect(node.keys())).toEqual(keys)
|
||||
expect(await collect(node.keys({ reverse: true }))).toEqual([...keys].reverse())
|
||||
expect(await collect(node.entries())).toEqual(exp.filter((e) => e.hasOwn).map((e) => [e.key, e.own]))
|
||||
expect(await collect(node.entries({ reverse: true }))).toEqual(
|
||||
exp
|
||||
.filter((e) => e.hasOwn)
|
||||
.map((e) => [e.key, e.own])
|
||||
.reverse(),
|
||||
)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
expect(await collect(node.keys({ after: keys[i]! }))).toEqual(keys.slice(i + 1))
|
||||
expect(await collect(node.keys({ after: keys[i]!, reverse: true }))).toEqual(keys.slice(0, i).reverse())
|
||||
expect(await collect(node.keys({ limit: i + 1 }))).toEqual(keys.slice(0, i + 1))
|
||||
for (const [k, v] of model) expect(await nodeAt(db, JSON.parse(k)).get()).toEqual(v)
|
||||
for (const parent of probes) {
|
||||
const node = nodeAt(db, parent)
|
||||
const exp = expectedChildren(model, parent)
|
||||
const keys = exp.map((e) => e.key)
|
||||
expect(await collect(node.keys())).toEqual(keys)
|
||||
expect(await collect(node.keys({ reverse: true }))).toEqual([...keys].reverse())
|
||||
expect(await collect(node.entries())).toEqual(exp.filter((e) => e.hasOwn).map((e) => [e.key, e.own]))
|
||||
expect(await collect(node.entries({ reverse: true }))).toEqual(
|
||||
exp
|
||||
.filter((e) => e.hasOwn)
|
||||
.map((e) => [e.key, e.own])
|
||||
.reverse(),
|
||||
)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
expect(await collect(node.keys({ after: keys[i]! }))).toEqual(keys.slice(i + 1))
|
||||
expect(await collect(node.keys({ after: keys[i]!, reverse: true }))).toEqual(keys.slice(0, i).reverse())
|
||||
expect(await collect(node.keys({ limit: i + 1 }))).toEqual(keys.slice(0, i + 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
},
|
||||
),
|
||||
{ numRuns: 300 },
|
||||
)
|
||||
}, 60_000)
|
||||
|
||||
+24
-19
@@ -36,26 +36,31 @@ describe('ChunkedSortedMap (model-based, tiny chunks)', () => {
|
||||
|
||||
it('iterators resume correctly after mutations between steps', () => {
|
||||
fc.assert(
|
||||
fc.property(fc.uniqueArray(key, { minLength: 1, maxLength: 60 }), fc.array(fc.tuple(fc.boolean(), key), { maxLength: 40 }), fc.boolean(), (initial, muts, reverse) => {
|
||||
const m = new ChunkedSortedMap<number>(3)
|
||||
initial.forEach((k) => m.set(k, 0))
|
||||
const live = new Set(initial)
|
||||
const seen: string[] = []
|
||||
let step = 0
|
||||
for (const k of m.keys({ reverse })) {
|
||||
seen.push(k)
|
||||
const mut = muts[step++]
|
||||
if (mut) {
|
||||
const [add, mk] = mut
|
||||
if (add) (m.set(mk, 1), live.add(mk))
|
||||
else (m.delete(mk), live.delete(mk))
|
||||
fc.property(
|
||||
fc.uniqueArray(key, { minLength: 1, maxLength: 60 }),
|
||||
fc.array(fc.tuple(fc.boolean(), key), { maxLength: 40 }),
|
||||
fc.boolean(),
|
||||
(initial, muts, reverse) => {
|
||||
const m = new ChunkedSortedMap<number>(3)
|
||||
initial.forEach((k) => m.set(k, 0))
|
||||
const live = new Set(initial)
|
||||
const seen: string[] = []
|
||||
let step = 0
|
||||
for (const k of m.keys({ reverse })) {
|
||||
seen.push(k)
|
||||
const mut = muts[step++]
|
||||
if (mut) {
|
||||
const [add, mk] = mut
|
||||
if (add) (m.set(mk, 1), live.add(mk))
|
||||
else (m.delete(mk), live.delete(mk))
|
||||
}
|
||||
}
|
||||
}
|
||||
// Strictly monotone, and every key present throughout the iteration was visited.
|
||||
for (let i = 1; i < seen.length; i++) expect(reverse ? seen[i]! < seen[i - 1]! : seen[i]! > seen[i - 1]!).toBe(true)
|
||||
const stable = initial.filter((k) => !muts.some(([, mk]) => mk === k))
|
||||
for (const k of stable) expect(seen).toContain(k)
|
||||
}),
|
||||
// Strictly monotone, and every key present throughout the iteration was visited.
|
||||
for (let i = 1; i < seen.length; i++) expect(reverse ? seen[i]! < seen[i - 1]! : seen[i]! > seen[i - 1]!).toBe(true)
|
||||
const stable = initial.filter((k) => !muts.some(([, mk]) => mk === k))
|
||||
for (const k of stable) expect(seen).toContain(k)
|
||||
},
|
||||
),
|
||||
{ numRuns: 500 },
|
||||
)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user