diff --git a/README.md b/README.md index f178251..6ac72de 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,10 @@ Alias for `await find(...).one()` Get a document from a collection +#### `{ count } = await db.stats(collectionOrIndex)` + +Get stats, about a collection or index with stats enabled. + #### `await db.insert(collection, doc)` Insert a document into a collection. NOTE: you have to flush the db later for this to be persisted. diff --git a/builder/codegen.js b/builder/codegen.js index 76286a7..c61331c 100644 --- a/builder/codegen.js +++ b/builder/codegen.js @@ -62,13 +62,13 @@ module.exports = function generateCode (hyperdb) { for (let i = 0; i < hyperdb.orderedTypes.length; i++) { const type = hyperdb.orderedTypes[i] if (type.isCollection) { - const id = `collection${collections.length}` + const id = `collection${type.id}` collections.push({ id, type }) str += generateCollectionDefinition(id, type) } else if (type.isIndex) { const id = `index${indexes.length}` indexes.push({ id, type }) - str += generateIndexDefinition(id, type) + str += generateIndexDefinition(id, type, `collection${type.collection.id}`) } str += '\n' } @@ -93,9 +93,8 @@ module.exports = function generateCode (hyperdb) { str += 'const Indexes = [...IndexMap.values()]\n' str += 'for (const index of IndexMap.values()) {\n' - str += ' const collection = CollectionMap.get(index._collectionName)\n' + str += ' const collection = index.collection\n' str += ' collection.indexes.push(index)\n' - str += ' index.collection = collection\n' str += ' index.offset = collection.indexes.length - 1\n' str += '}\n' str += '\n' @@ -169,7 +168,7 @@ function generateCollectionDefinition (id, collection) { str += `// ${s(collection.fqn)} reconstruction function\n` str += `function ${id}_reconstruct (version, keyBuf, valueBuf) {\n` str += ' // TODO: This should be fully code generated\n' - str += ' const key = collection0_key.decode(keyBuf)\n' + str += ` const key = ${id}_key.decode(keyBuf)\n` str += ` const value = c.decode(resolveStruct(${s(collection.valueEncoding)}, version), valueBuf)\n` str += ' return {\n' for (let i = 0; i < collection.key.length; i++) { @@ -184,6 +183,8 @@ function generateCollectionDefinition (id, collection) { str += `// ${s(collection.fqn)}\n` str += `const ${id} = {\n` str += ` name: ${s(collection.fqn)},\n` + str += ` id: ${collection.id},\n` + str += ` stats: ${collection.stats},\n` str += ` encodeKey: ${generateEncodeCollectionKey(id, collection)},\n` str += ` encodeKeyRange: ${generateEncodeKeyRange(id, collection)},\n` str += ` encodeValue: ${generateEncodeCollectionValue(collection)},\n` @@ -193,18 +194,19 @@ function generateCollectionDefinition (id, collection) { return str } -function generateIndexDefinition (id, index) { +function generateIndexDefinition (id, index, collectionId) { let str = generateCommonPrefix(id, index) str += `// ${s(index.fqn)}\n` str += `const ${id} = {\n` - str += ` _collectionName: ${s(index.description.collection)},\n` str += ` name: ${s(index.fqn)},\n` + str += ` id: ${index.id},\n` + str += ` stats: ${index.stats},\n` str += ` encodeKeys: ${generateEncodeIndexKeys(id, index)},\n` str += ` encodeKeyRange: ${generateEncodeKeyRange(id, index)},\n` str += ` encodeValue: (doc) => ${id}.collection.encodeKey(doc),\n` str += ' reconstruct: (keyBuf, valueBuf) => valueBuf,\n' str += ' offset: 0,\n' - str += ' collection: null\n' + str += ` collection: ${collectionId}\n` str += '}\n' return str } diff --git a/builder/index.js b/builder/index.js index 8391c10..c2a6818 100644 --- a/builder/index.js +++ b/builder/index.js @@ -19,6 +19,7 @@ class DBType { this.fqn = getFQN(this.namespace, this.description.name) this.key = description.key this.fullKey = [] + this.stats = !!description.stats this.isMapped = false this.isIndex = false @@ -56,7 +57,8 @@ class DBType { name: this.description.name, unsafe: this.description.unsafe, namespace: this.namespace, - id: this.id + id: this.id, + stats: this.stats } } } @@ -229,6 +231,7 @@ class Builder { this.typesById = new Map() this.orderedTypes = [] + this.registeredStats = false this.currentOffset = this.offset this.initializing = true @@ -256,12 +259,39 @@ class Builder { return { id: this.currentOffset++, prefix: null } } + _registerStats () { + if (this.registeredStats) return + this.registeredStats = true + + this.schema.register({ + namespace: null, + name: 'stats', + derived: true, + fields: [{ + name: 'id', + type: 'uint', + required: true + }, { + name: 'count', + type: 'uint', + required: true + }] + }) + this.registerCollection({ + name: 'stats', + schema: 'stats', + key: ['id'] + }, null) + } + registerCollection (description, namespace) { const collection = new Collection(this, namespace, description) if (this.typesByName.has(collection.fqn)) return this.orderedTypes.push(collection) this.typesByName.set(collection.fqn, collection) + + if (collection.stats) this._registerStats() } registerIndex (description, namespace) { @@ -322,5 +352,6 @@ class Builder { module.exports = Builder function getFQN (namespace, name) { + if (namespace === null) return name return '@' + namespace + '/' + name } diff --git a/index.js b/index.js index d2f5816..1e71b7b 100644 --- a/index.js +++ b/index.js @@ -4,12 +4,15 @@ const b4a = require('b4a') // engines const RocksEngine = require('./lib/engine/rocks') +const STATS = 'stats' + class Updates { - constructor (clock, entries) { + constructor (clock, entries, stats) { this.refs = 1 this.mutating = 0 this.clock = clock this.map = new Map(entries) + this.stats = new Map(stats) } get size () { @@ -28,13 +31,24 @@ class Updates { detach () { const entries = new Array(this.map.size) - let i = 0 - for (const [key, u] of this.map) { - entries[i++] = [key, { key: u.key, value: u.value, indexes: u.indexes.slice(0) }] + if (entries.length > 0) { + let i = 0 + for (const [key, u] of this.map) { + entries[i++] = [key, { key: u.key, value: u.value, indexes: u.indexes.slice(0) }] + } + } + + const stats = new Array(this.stats.size) + + if (stats.length > 0) { + let i = 0 + for (const [col, st] of this.stats) { + stats[i++] = [col, st] + } } this.refs-- - return new Updates(this.clock, entries) + return new Updates(this.clock, entries, stats) } get (key) { @@ -44,11 +58,26 @@ class Updates { flush (clock) { this.clock = clock + this.stats.clear() this.map.clear() } - update (key, value) { - const u = { key, value, indexes: [] } + prestats (collectionOrIndex, engine) { + const st = this.stats.get(collectionOrIndex) + if (st) return st + + const state = { + key: null, + value: null, + promise: null + } + + this.stats.set(collectionOrIndex, state) + return state + } + + update (collection, key, value) { + const u = { created: false, collection, key, value, indexes: [] } this.map.set(b4a.toString(key, 'hex'), u) return u } @@ -61,6 +90,22 @@ class Updates { return this.map.values() } + indexStatsOverlay (index) { + throw new Error('Index stats are not currently implemented, open an issue') + } + + collectionStatsOverlay (collection) { + const info = { count: 0 } + + for (const u of this.map.values()) { + if (u.collection !== collection) continue + if (u.value === null) info.count-- + else if (u.created) info.count++ + } + + return info + } + overlay (range, index, reverse) { const overlay = [] @@ -98,7 +143,7 @@ class HyperDB { constructor (engine, definition, { version = definition.version, snapshot = engine.snapshot(), - updates = new Updates(engine.clock, []), + updates = new Updates(engine.clock, [], []), rootInstance = null, writable = true } = {}) { @@ -239,7 +284,7 @@ class HyperDB { } function map (entries) { - return engine.getRange(snap, entries) + return engine.getIndirectRange(snap, entries) } } @@ -254,12 +299,52 @@ class HyperDB { if (collection === null) return null const key = collection.encodeKey(doc) + const u = this.updates.get(key) const value = u !== null ? u.value : await this.engine.get(this.engineSnapshot, key) return value === null ? null : collection.reconstruct(this.version, key, value) } + async stats (indexName) { + const collection = this.definition.resolveCollection(indexName) + const index = collection === null ? this.definition.resolveIndex(indexName) : null + + if (collection === null && index === null) throw new Error('Unknown index: ' + indexName) + + const target = collection || index + const st = await this.get(STATS, { id: target.id }) + + const overlay = index ? this.updates.indexStatsOverlay(index) : this.updates.collectionStatsOverlay(collection) + + if (!st) return overlay + + st.count += overlay.count + return st + } + + async _getPrev (key, collection) { + const st = collection.stats === true ? this.updates.prestats(collection) : null + + if (st !== null && !st.promise && !st.value) { + const statsCollection = this.definition.resolveCollection(STATS) + + st.key = statsCollection.encodeKey({ id: collection.id }) + st.promise = this.engine.getBatch(this.engineSnapshot, [key, st.key]) + + const [value, stats] = await st.promise + + st.value = stats === null ? statsCollection.encodeValue(this.version, { count: 0 }) : stats + st.promise = null + + return value + } + + const value = await this.engine.get(this.engineSnapshot, key) + if (st !== null && st.promise !== null) await st.promise + return value + } + async delete (collectionName, doc) { maybeClosed(this) @@ -273,7 +358,7 @@ class HyperDB { let prevValue = null this.updates.mutating++ try { - prevValue = await this.engine.get(this.engineSnapshot, key) + prevValue = await this._getPrev(key, collection) } finally { this.updates.mutating-- } @@ -285,7 +370,7 @@ class HyperDB { const prevDoc = collection.reconstruct(this.version, key, prevValue) - const u = this.updates.update(key, null) + const u = this.updates.update(collection, key, null) for (let i = 0; i < collection.indexes.length; i++) { const idx = collection.indexes[i] @@ -312,7 +397,7 @@ class HyperDB { let prevValue = null this.updates.mutating++ try { - prevValue = await this.engine.get(this.engineSnapshot, key) + prevValue = await this._getPrev(key, collection) } finally { this.updates.mutating-- } @@ -321,7 +406,9 @@ class HyperDB { const prevDoc = prevValue === null ? null : collection.reconstruct(this.version, key, prevValue) - const u = this.updates.update(key, value) + const u = this.updates.update(collection, key, value) + + u.created = prevValue === null for (let i = 0; i < collection.indexes.length; i++) { const idx = collection.indexes[i] @@ -355,6 +442,18 @@ class HyperDB { } } + _applyStats () { + const statsCollection = this.definition.resolveCollection(STATS) + for (const [collection, { key, value }] of this.updates.stats) { + const stats = statsCollection.reconstruct(this.version, key, value) + const overlay = this.updates.collectionStatsOverlay(collection) + stats.count += overlay.count + const updatedValue = statsCollection.encodeValue(this.version, stats) + if (b4a.equals(value, updatedValue)) continue + this.updates.update(statsCollection, key, updatedValue) + } + } + async flush () { maybeClosed(this) @@ -364,6 +463,8 @@ class HyperDB { if (this.updates.clock !== this.engine.clock) throw new Error('Database has changed, refusing to commit') if (this.updates.refs > 1) this.updates = this.updates.detach() + if (this.updates.stats.size) this._applyStats() + await this.engine.commit(this.updates) this.reload() diff --git a/lib/engine/rocks.js b/lib/engine/rocks.js index 8a4ea5d..5c96bcb 100644 --- a/lib/engine/rocks.js +++ b/lib/engine/rocks.js @@ -43,7 +43,7 @@ module.exports = class RocksEngine { return new RocksSnapshot(this.db.snapshot()) } - getRange (snapshot, entries) { + getIndirectRange (snapshot, entries) { const read = this.db.read({ snapshot: getSnapshot(snapshot) }) const promises = new Array(entries.length) @@ -55,6 +55,18 @@ module.exports = class RocksEngine { return promises } + getBatch (snapshot, keys) { + const read = this.db.read({ snapshot: getSnapshot(snapshot) }) + const promises = new Array(keys.length) + + for (let i = 0; i < promises.length; i++) { + promises[i] = read.get(keys[i]) + } + + read.tryFlush() + return Promise.all(promises) + } + get (snapshot, key) { return this.db.get(key, { snapshot: getSnapshot(snapshot) }) } diff --git a/test/basic.js b/test/basic.js index 01cfc91..d34991b 100644 --- a/test/basic.js +++ b/test/basic.js @@ -244,3 +244,39 @@ test('watch', async function (t) { await db.close() }) + +test('stats', async function (t) { + const db = await rocks(t, { fixture: 2 }) + + await db.insert('@db/members', { id: 'maf', age: 34 }) + await db.insert('@db/members', { id: 'andrew', age: 34 }) + await db.insert('@db/members', { id: 'anna', age: 32 }) + + { + const st = await db.stats('@db/members') + t.is(st.count, 3) + } + + await db.flush() + + { + const st = await db.stats('@db/members') + t.is(st.count, 3) + } + + await db.delete('@db/members', { id: 'anna', age: 32 }) + + { + const st = await db.stats('@db/members') + t.is(st.count, 2) + } + + await db.insert('@db/members', { id: 'maf', age: 35 }) + + { + const st = await db.stats('@db/members') + t.is(st.count, 2) + } + + await db.close() +}) diff --git a/test/fixtures/generate.js b/test/fixtures/generate.js index 8c245fa..dd867e5 100644 --- a/test/fixtures/generate.js +++ b/test/fixtures/generate.js @@ -1 +1,2 @@ require('./generated/1/build.js') +require('./generated/2/build.js') diff --git a/test/fixtures/generated/1/hyperdb/db.json b/test/fixtures/generated/1/hyperdb/db.json index 5a1f27f..331f659 100644 --- a/test/fixtures/generated/1/hyperdb/db.json +++ b/test/fixtures/generated/1/hyperdb/db.json @@ -6,9 +6,9 @@ "name": "members", "namespace": "db", "id": 0, + "stats": false, "type": 1, "indexes": [ - "@db/members-by-age", "@db/members-by-age" ], "schema": "@db/member", @@ -20,6 +20,7 @@ "name": "members-by-age", "namespace": "db", "id": 1, + "stats": false, "type": 2, "collection": "@db/members", "unique": false, diff --git a/test/fixtures/generated/1/hyperdb/index.js b/test/fixtures/generated/1/hyperdb/index.js index 1d4f65a..e54c97e 100644 --- a/test/fixtures/generated/1/hyperdb/index.js +++ b/test/fixtures/generated/1/hyperdb/index.js @@ -34,6 +34,8 @@ function collection0_reconstruct (version, keyBuf, valueBuf) { // '@db/members' const collection0 = { name: '@db/members', + id: 0, + stats: false, encodeKey: function encodeKey (record) { const key = [record.id] return collection0_key.encode(key) @@ -75,8 +77,9 @@ function index0_indexify (record) { // '@db/members-by-age' const index0 = { - _collectionName: '@db/members', name: '@db/members-by-age', + id: 1, + stats: false, encodeKeys: function encodeKeys (record) { const key = [record.age, record.id] return [index0_key.encode(key)] @@ -92,7 +95,7 @@ const index0 = { encodeValue: (doc) => index0.collection.encodeKey(doc), reconstruct: (keyBuf, valueBuf) => valueBuf, offset: 0, - collection: null + collection: collection0 } const IndexMap = new Map([ @@ -104,9 +107,8 @@ const CollectionMap = new Map([ const Collections = [...CollectionMap.values()] const Indexes = [...IndexMap.values()] for (const index of IndexMap.values()) { - const collection = CollectionMap.get(index._collectionName) + const collection = index.collection collection.indexes.push(index) - index.collection = collection index.offset = collection.indexes.length - 1 } diff --git a/test/fixtures/generated/2/build.js b/test/fixtures/generated/2/build.js new file mode 100644 index 0000000..c209be8 --- /dev/null +++ b/test/fixtures/generated/2/build.js @@ -0,0 +1,46 @@ +const HyperDB = require('../../../../builder') +const Hyperschema = require('hyperschema') +const path = require('path') + +const SCHEMA_DIR = path.join(__dirname, 'hyperschema') +const DB_DIR = path.join(__dirname, 'hyperdb') + +const schema = Hyperschema.from(SCHEMA_DIR) + +const dbSchema = schema.namespace('db') + +dbSchema.register({ + name: 'member', + fields: [ + { + name: 'id', + type: 'string', + required: true + }, + { + name: 'age', + type: 'uint', + required: true + } + ] +}) + +Hyperschema.toDisk(schema) + +const db = HyperDB.from(SCHEMA_DIR, DB_DIR) +const testDb = db.namespace('db') + +testDb.collections.register({ + name: 'members', + stats: true, + schema: '@db/member', + key: ['id'] +}) + +testDb.indexes.register({ + name: 'members-by-age', + collection: '@db/members', + key: ['age'] +}) + +HyperDB.toDisk(db) diff --git a/test/fixtures/generated/2/hyperdb/db.json b/test/fixtures/generated/2/hyperdb/db.json new file mode 100644 index 0000000..e73f2cf --- /dev/null +++ b/test/fixtures/generated/2/hyperdb/db.json @@ -0,0 +1,44 @@ +{ + "version": 0, + "offset": 0, + "schema": [ + { + "name": "members", + "namespace": "db", + "id": 0, + "stats": true, + "type": 1, + "indexes": [ + "@db/members-by-age" + ], + "schema": "@db/member", + "key": [ + "id" + ] + }, + { + "name": "stats", + "namespace": null, + "id": 1, + "stats": false, + "type": 1, + "indexes": [], + "schema": "stats", + "key": [ + "id" + ] + }, + { + "name": "members-by-age", + "namespace": "db", + "id": 2, + "stats": false, + "type": 2, + "collection": "@db/members", + "unique": false, + "key": [ + "age" + ] + } + ] +} \ No newline at end of file diff --git a/test/fixtures/generated/2/hyperdb/index.js b/test/fixtures/generated/2/hyperdb/index.js new file mode 100644 index 0000000..09c43a7 --- /dev/null +++ b/test/fixtures/generated/2/hyperdb/index.js @@ -0,0 +1,182 @@ +// This file is autogenerated by the hyperdb compiler +/* eslint-disable camelcase */ + +const { IndexEncoder, c } = require('hyperdb/runtime') + +const { version, resolveStruct } = require('./messages.js') + +// '@db/members' collection key +const collection0_key = new IndexEncoder([ + IndexEncoder.STRING +], { prefix: 0 }) + +function collection0_indexify (record) { + const arr = [] + + const a0 = record.id + if (a0 === undefined) return arr + arr.push(a0) + + return arr +} + +// '@db/members' reconstruction function +function collection0_reconstruct (version, keyBuf, valueBuf) { + // TODO: This should be fully code generated + const key = collection0_key.decode(keyBuf) + const value = c.decode(resolveStruct('@db/members/value', version), valueBuf) + return { + id: key[0], + ...value + } +} + +// '@db/members' +const collection0 = { + name: '@db/members', + id: 0, + stats: true, + encodeKey: function encodeKey (record) { + const key = [record.id] + return collection0_key.encode(key) + }, + encodeKeyRange: function encodeKeyRange ({ gt, lt, gte, lte } = {}) { + return collection0_key.encodeRange({ + gt: gt ? collection0_indexify(gt) : null, + lt: lt ? collection0_indexify(lt) : null, + gte: gte ? collection0_indexify(gte) : null, + lte: lte ? collection0_indexify(lte) : null + }) + }, + encodeValue: function encodeValue (version, record) { + return c.encode(resolveStruct('@db/members/value', version), record) + }, + reconstruct: collection0_reconstruct, + indexes: [] +} + +// 'stats' collection key +const collection1_key = new IndexEncoder([ + IndexEncoder.UINT +], { prefix: 1 }) + +function collection1_indexify (record) { + const arr = [] + + const a0 = record.id + if (a0 === undefined) return arr + arr.push(a0) + + return arr +} + +// 'stats' reconstruction function +function collection1_reconstruct (version, keyBuf, valueBuf) { + // TODO: This should be fully code generated + const key = collection1_key.decode(keyBuf) + const value = c.decode(resolveStruct('stats/value', version), valueBuf) + return { + id: key[0], + ...value + } +} + +// 'stats' +const collection1 = { + name: 'stats', + id: 1, + stats: false, + encodeKey: function encodeKey (record) { + const key = [record.id] + return collection1_key.encode(key) + }, + encodeKeyRange: function encodeKeyRange ({ gt, lt, gte, lte } = {}) { + return collection1_key.encodeRange({ + gt: gt ? collection1_indexify(gt) : null, + lt: lt ? collection1_indexify(lt) : null, + gte: gte ? collection1_indexify(gte) : null, + lte: lte ? collection1_indexify(lte) : null + }) + }, + encodeValue: function encodeValue (version, record) { + return c.encode(resolveStruct('stats/value', version), record) + }, + reconstruct: collection1_reconstruct, + indexes: [] +} + +// '@db/members-by-age' collection key +const index0_key = new IndexEncoder([ + IndexEncoder.UINT, + IndexEncoder.STRING +], { prefix: 2 }) + +function index0_indexify (record) { + const arr = [] + + const a0 = record.age + if (a0 === undefined) return arr + arr.push(a0) + + const a1 = record.id + if (a1 === undefined) return arr + arr.push(a1) + + return arr +} + +// '@db/members-by-age' +const index0 = { + name: '@db/members-by-age', + id: 2, + stats: false, + encodeKeys: function encodeKeys (record) { + const key = [record.age, record.id] + return [index0_key.encode(key)] + }, + encodeKeyRange: function encodeKeyRange ({ gt, lt, gte, lte } = {}) { + return index0_key.encodeRange({ + gt: gt ? index0_indexify(gt) : null, + lt: lt ? index0_indexify(lt) : null, + gte: gte ? index0_indexify(gte) : null, + lte: lte ? index0_indexify(lte) : null + }) + }, + encodeValue: (doc) => index0.collection.encodeKey(doc), + reconstruct: (keyBuf, valueBuf) => valueBuf, + offset: 0, + collection: collection0 +} + +const IndexMap = new Map([ + ['@db/members-by-age', index0] +]) +const CollectionMap = new Map([ + ['@db/members', collection0], + ['stats', collection1] +]) +const Collections = [...CollectionMap.values()] +const Indexes = [...IndexMap.values()] +for (const index of IndexMap.values()) { + const collection = index.collection + collection.indexes.push(index) + index.offset = collection.indexes.length - 1 +} + +function resolveCollection (fqn) { + const coll = CollectionMap.get(fqn) + return coll || null +} + +function resolveIndex (fqn) { + const index = IndexMap.get(fqn) + return index || null +} + +module.exports = { + version, + collections: Collections, + indexes: Indexes, + resolveCollection, + resolveIndex +} diff --git a/test/fixtures/generated/2/hyperdb/messages.js b/test/fixtures/generated/2/hyperdb/messages.js new file mode 100644 index 0000000..153e5ff --- /dev/null +++ b/test/fixtures/generated/2/hyperdb/messages.js @@ -0,0 +1,118 @@ +// This file is autogenerated by the hyperschema compiler +// Schema Version: 2 +/* eslint-disable camelcase */ +/* eslint-disable quotes */ + +const VERSION = 2 +const { c } = require('hyperschema/runtime') + +// eslint-disable-next-line no-unused-vars +let version = VERSION + +// @db/member +const encoding0 = { + preencode (state, m) { + c.string.preencode(state, m.id) + c.uint.preencode(state, m.age) + }, + encode (state, m) { + c.string.encode(state, m.id) + c.uint.encode(state, m.age) + }, + decode (state) { + const res = {} + res.id = null + res.age = 0 + + res.id = c.string.decode(state) + res.age = c.uint.decode(state) + + return res + } +} + +// @db/members/value +const encoding1 = { + preencode (state, m) { + c.uint.preencode(state, m.age) + }, + encode (state, m) { + c.uint.encode(state, m.age) + }, + decode (state) { + const res = {} + res.age = 0 + + res.age = c.uint.decode(state) + + return res + } +} + +// stats +const encoding2 = { + preencode (state, m) { + c.uint.preencode(state, m.id) + c.uint.preencode(state, m.count) + }, + encode (state, m) { + c.uint.encode(state, m.id) + c.uint.encode(state, m.count) + }, + decode (state) { + const res = {} + if (version >= 2) res.id = 0 + if (version >= 2) res.count = 0 + + res.id = c.uint.decode(state) + res.count = c.uint.decode(state) + + return res + } +} + +// stats/value +const encoding3 = { + preencode (state, m) { + c.uint.preencode(state, m.count) + }, + encode (state, m) { + c.uint.encode(state, m.count) + }, + decode (state) { + const res = {} + if (version >= 2) res.count = 0 + + res.count = c.uint.decode(state) + + return res + } +} + +const StructMap = new Map([ + ['@db/member', encoding0], + ['@db/members/value', encoding1], + ['stats', encoding2], + ['stats/value', encoding3] +]) + +function resolveStruct (name, v = VERSION) { + const enc = StructMap.get(name) + if (!enc) throw new Error('Encoder not found' + name) + return { + preencode (state, m) { + version = v + enc.preencode(state, m) + }, + encode (state, m) { + version = v + enc.encode(state, m) + }, + decode (state) { + version = v + return enc.decode(state) + } + } +} + +module.exports = { resolveStruct, version } diff --git a/test/fixtures/generated/2/hyperschema/index.js b/test/fixtures/generated/2/hyperschema/index.js new file mode 100644 index 0000000..39dcd03 --- /dev/null +++ b/test/fixtures/generated/2/hyperschema/index.js @@ -0,0 +1,57 @@ +// This file is autogenerated by the hyperschema compiler +// Schema Version: 1 +/* eslint-disable camelcase */ +/* eslint-disable quotes */ + +const VERSION = 1 +const { c } = require('hyperschema/runtime') + +// eslint-disable-next-line no-unused-vars +let version = VERSION + +// @db/member +const encoding0 = { + preencode (state, m) { + c.string.preencode(state, m.id) + c.uint.preencode(state, m.age) + }, + encode (state, m) { + c.string.encode(state, m.id) + c.uint.encode(state, m.age) + }, + decode (state) { + const res = {} + res.id = null + res.age = 0 + + res.id = c.string.decode(state) + res.age = c.uint.decode(state) + + return res + } +} + +const StructMap = new Map([ + ['@db/member', encoding0] +]) + +function resolveStruct (name, v = VERSION) { + const enc = StructMap.get(name) + if (!enc) throw new Error('Encoder not found' + name) + return { + preencode (state, m) { + version = v + enc.preencode(state, m) + }, + encode (state, m) { + version = v + enc.encode(state, m) + }, + decode (state) { + version = v + return enc.decode(state) + } + } +} + +module.exports = { resolveStruct, version } diff --git a/test/fixtures/generated/2/hyperschema/schema.json b/test/fixtures/generated/2/hyperschema/schema.json new file mode 100644 index 0000000..b7813c1 --- /dev/null +++ b/test/fixtures/generated/2/hyperschema/schema.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "schema": [ + { + "name": "member", + "namespace": "db", + "compact": false, + "flagsPosition": -1, + "fields": [ + { + "name": "id", + "required": true, + "type": "string", + "version": 1 + }, + { + "name": "age", + "required": true, + "type": "uint", + "version": 1 + } + ] + } + ] +} \ No newline at end of file diff --git a/test/helpers/index.js b/test/helpers/index.js index f3b444d..1b5c75e 100644 --- a/test/helpers/index.js +++ b/test/helpers/index.js @@ -2,7 +2,7 @@ const HyperDB = require('../../') const tmp = require('test-tmp') exports.rocks = async function (t, def, opts) { - if (!HyperDB.isDefinition(def)) return exports.rocks(t, require('../fixtures/generated/1/hyperdb'), def) + if (!HyperDB.isDefinition(def)) return exports.rocks(t, require(`../fixtures/generated/${(def && def.fixture) || 1}/hyperdb`), def) const db = HyperDB.rocks(await tmp(t), def, opts) const engine = db.engine