feat(8.0): upsert + FindParams.includeVectors + removeMany adaptive chunking
Three additive ergonomics from the API-simplification audit (no behavior change to existing call sites): - AddParams.upsert: create-or-update in one call. With a custom id, an existing entity is MERGED via the update path (merges metadata, re-embeds changed data, bumps _rev, PRESERVES createdAt) instead of the destructive full overwrite a plain add() does. Mutually exclusive with ifAbsent (throws if both set); ignored when no id is supplied. Wired into add(), addMany (per-item flag propagation), and the transact add op (routes to planTxUpdate). Kills the get()-then-add() round-trip for idempotent writes. - FindParams.includeVectors: mirror of GetOptions.includeVectors — find() returns stored vectors when set; default stays empty (the perf contract is preserved). Honored on both the query and metadata-only where paths, and in db.find(). - removeMany adaptive chunking: replaced the hardcoded chunkSize=10 with params.chunkSize ?? storageConfig.maxBatchSize, matching addMany/relateMany — one storage-adaptive batch policy across all *Many methods (no thrash on high-latency backends). Tests: tests/unit/brainy/upsert.test.ts (insert/merge/createdAt-preserved/ re-embed/ifAbsent-conflict/no-id/addMany/transact), find-include-vectors.test.ts (true/default/where-path), batch-operations.test.ts (removeMany >10 items).
This commit is contained in:
parent
1bc709d31b
commit
4cc2088aed
6 changed files with 481 additions and 12 deletions
|
|
@ -180,6 +180,49 @@ describe('Brainy Batch Operations', () => {
|
|||
}
|
||||
})
|
||||
|
||||
it('removes more than the legacy 10-item chunk in a single call (adaptive chunking)', async () => {
|
||||
// Seed 25 entities — more than the old hardcoded chunk of 10 — to prove the
|
||||
// storage-adaptive chunk size processes every chunk and nothing is capped.
|
||||
const seed = await brain.addMany({
|
||||
items: Array.from({ length: 25 }, (_, i) => ({
|
||||
data: `Adaptive Chunk ${i}`,
|
||||
type: NounType.Thing,
|
||||
metadata: { batch: 'adaptive' }
|
||||
}))
|
||||
})
|
||||
expect(seed.successful).toHaveLength(25)
|
||||
|
||||
const result = await brain.removeMany({ ids: seed.successful })
|
||||
expect(result.successful).toHaveLength(25)
|
||||
expect(result.failed).toHaveLength(0)
|
||||
expect(result.total).toBe(25)
|
||||
|
||||
// Every entity is actually gone.
|
||||
for (const id of seed.successful) {
|
||||
expect(await brain.get(id)).toBeNull()
|
||||
}
|
||||
})
|
||||
|
||||
it('removes all entities across multiple chunks when chunkSize is overridden', async () => {
|
||||
const seed = await brain.addMany({
|
||||
items: Array.from({ length: 12 }, (_, i) => ({
|
||||
data: `Override Chunk ${i}`,
|
||||
type: NounType.Thing,
|
||||
metadata: { batch: 'override' }
|
||||
}))
|
||||
})
|
||||
expect(seed.successful).toHaveLength(12)
|
||||
|
||||
// chunkSize 5 → 3 chunks (5 + 5 + 2); all must be removed.
|
||||
const result = await brain.removeMany({ ids: seed.successful, chunkSize: 5 })
|
||||
expect(result.successful).toHaveLength(12)
|
||||
expect(result.failed).toHaveLength(0)
|
||||
|
||||
for (const id of seed.successful) {
|
||||
expect(await brain.get(id)).toBeNull()
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle selective deletion', async () => {
|
||||
// Delete only some
|
||||
const toDelete = [testIds[0], testIds[2], testIds[4]]
|
||||
|
|
|
|||
Reference in a new issue