diff --git a/.gitignore b/.gitignore index 748cf41..02503a5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ coverage dist build .direnv +tmp diff --git a/package.json b/package.json index 9106e46..b4a2671 100644 --- a/package.json +++ b/package.json @@ -46,8 +46,11 @@ "uuid": ">=8 <10" }, "devDependencies": { + "@types/debug": "^4.1.12", "@types/lodash": "^4.17.23", "@types/node": "^22.10.7", + "@types/prop-types": "^15.7.15", + "@types/uuid": "^9.0.8", "@typescript-eslint/eslint-plugin": "^7.16.0", "@typescript-eslint/parser": "^7.16.0", "@vitest/coverage-v8": "3.0.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8612f8c..9954b39 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,12 +33,21 @@ importers: specifier: '>=8 <10' version: 9.0.1 devDependencies: + '@types/debug': + specifier: ^4.1.12 + version: 4.1.12 '@types/lodash': specifier: ^4.17.23 version: 4.17.23 '@types/node': specifier: ^22.10.7 version: 22.13.1 + '@types/prop-types': + specifier: ^15.7.15 + version: 15.7.15 + '@types/uuid': + specifier: ^9.0.8 + version: 9.0.8 '@typescript-eslint/eslint-plugin': specifier: ^7.16.0 version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3) @@ -493,9 +502,15 @@ packages: '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/uuid@9.0.8': + resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} + '@typescript-eslint/eslint-plugin@7.18.0': resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==} engines: {node: ^18.18.0 || >=20.0.0} @@ -2805,8 +2820,12 @@ snapshots: '@types/normalize-package-data@2.4.4': {} + '@types/prop-types@15.7.15': {} + '@types/unist@3.0.3': {} + '@types/uuid@9.0.8': {} + '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3)': dependencies: '@eslint-community/regexpp': 4.12.1 diff --git a/src/EventQueue.js b/src/EventQueue.js index f9ff514..a1c4ada 100644 --- a/src/EventQueue.js +++ b/src/EventQueue.js @@ -7,8 +7,7 @@ const dbg = debug('strato-db/queue') let warnedLatest -/** @typedef {defaultColumns} Columns */ -const defaultColumns = { +const defaultColumns = /** @type {const} */ ({ v: { type: 'INTEGER', autoIncrement: true, @@ -22,29 +21,54 @@ const defaultColumns = { data: {type: 'JSON'}, result: {type: 'JSON'}, size: {type: 'INTEGER', default: 0, get: false}, -} +}) /** * An event queue, including history. * - * @template {T} - * @template {U} - * @implements {EventQueue} + * @template {ESEvent} [RealItem=ESEvent] Default is `ESEvent` + * @template {Partial>} [Config=object] Default is `object` + * @template {string} [IDCol=EventQueueIDColumn] Default is + * `EventQueueIDColumn` + * @template {{[x: string]: any}} [Item=EventQueueItem] + * Default is `EventQueueItem` + * @template {JMColumns} [Columns=EventQueueColumns] + * Default is `EventQueueColumns` + * @extends {JsonModel< + * RealItem, + * JsonModelConfig & Config, + * 'v', + * Item, + * Config, + * Columns & typeof defaultColumns + * >} + * @implements {EventQueue} */ class EventQueueImpl extends JsonModel { - /** @param {EQOptions} */ + /** @param {EQOptions} args */ constructor({name = 'history', forever, withViews, ...rest}) { - const columns = {...defaultColumns} - if (rest.columns) - for (const [key, value] of Object.entries(rest.columns)) { - if (!value) continue - if (columns[key]) throw new TypeError(`Cannot override column ${key}`) - columns[key] = value - } + const tmpCols = rest.columns + ? /** @type {Columns} */ ( + Object.fromEntries( + Object.entries(rest.columns).filter(([key, value]) => { + if (!value) return false + if (defaultColumns[key]) + throw new TypeError(`Cannot override column ${key}`) + return true + }) + ) + ) + : undefined + + const columns = /** @type {Columns & typeof defaultColumns} */ ({ + ...tmpCols, + ...defaultColumns, + }) + super({ ...rest, name, - idCol: 'v', + idCol: /** @type {const} */ ('v'), columns, migrations: { ...rest.migrations, @@ -52,16 +76,16 @@ class EventQueueImpl extends JsonModel { db.exec( `CREATE INDEX IF NOT EXISTS "history type,size" on history(type, size)` ), - '20190521_addViews': withViews - ? async ({db}) => { - const historySchema = await db.all('PRAGMA table_info("history")') - // This adds a field with data size, kept up-to-date with triggers - if (!historySchema.some(f => f.name === 'size')) - await db.exec( - `ALTER TABLE history ADD COLUMN size INTEGER DEFAULT 0` - ) - // The size WHERE clause is to prevent recursive triggers - await db.exec(` + ...(withViews && { + '20190521_addViews': async ({db}) => { + const historySchema = await db.all('PRAGMA table_info("history")') + // This adds a field with data size, kept up-to-date with triggers + if (!historySchema.some(f => f.name === 'size')) + await db.exec( + `ALTER TABLE history ADD COLUMN size INTEGER DEFAULT 0` + ) + // The size WHERE clause is to prevent recursive triggers + await db.exec(` DROP TRIGGER IF EXISTS "history size insert"; DROP TRIGGER IF EXISTS "history size update"; CREATE TRIGGER "history size insert" AFTER INSERT ON history BEGIN @@ -87,12 +111,13 @@ class EventQueueImpl extends JsonModel { SUM(size)/1024/1024 AS MB FROM history GROUP BY type ORDER BY count DESC; `) - // Recalculate size - await db.exec(`UPDATE history SET size=0`) - } - : null, + // Recalculate size + await db.exec(`UPDATE history SET size=0`) + }, + }), }, }) + this.currentV = -1 this.knownV = 0 this.forever = !!forever @@ -101,8 +126,8 @@ class EventQueueImpl extends JsonModel { /** * Replace existing event data. * - * @param {Event} event - The new event. - * @returns {Promise} - Promise for set completion. + * @param {Partial} event - The new event. + * @returns {Promise} - Promise for set completion. */ set(event) { if (!event.v) { @@ -148,16 +173,18 @@ class EventQueueImpl extends JsonModel { return this.currentV } + /** @type {Promise> | null} */ _addP = null /** * Atomically add an event to the queue. * - * @param {string} type - Event type. - * @param {any} [data] - Event data. + * @template {keyof EventTypes} T + * @param {T} type - Event type. + * @param {EventTypes[T]} [data] - Event data. * @param {number} [ts=Date.now()] - Event timestamp, ms since epoch. Default * is `Date.now()` - * @returns {Promise} - Promise for the added event. + * @returns {Promise>} - Promise for the added event. */ add(type, data, ts) { if (!type || typeof type !== 'string') @@ -192,6 +219,7 @@ class EventQueueImpl extends JsonModel { return this._addP } + /** @type {Promise | null} */ _nextAddedP = null _nextAddedResolve = event => { @@ -222,7 +250,7 @@ class EventQueueImpl extends JsonModel { * * @param {number} [v=0] The version. Default is `0` * @param {boolean} [noWait] Do not wait for the next event. - * @returns {Promise} The event if found. + * @returns {Promise} The event if found. */ async getNext(v = 0, noWait = false) { let event diff --git a/src/JsonModel/JsonModel.js b/src/JsonModel/JsonModel.js index 9fa8d7e..a8dd4ef 100644 --- a/src/JsonModel/JsonModel.js +++ b/src/JsonModel/JsonModel.js @@ -53,13 +53,22 @@ const decodeCursor = cursor => { * * Simple equality lookup values for searching. * - * @template Item - * @template IDCol + * @template {{[x: string]: any}} [RealItem={id: string}] Default is `{id: + * string}`. Default is `{id: string}` + * @template [ConfigOrID='id'] Default is `'id'` + * @template {string} [IDCol=JsonModelIDColumn] Default is + * `JsonModelIDColumn` + * @template {{[x: string]: any}} [ItemT=JsonModelItem] + * Default is `JsonModelItem` + * @template [Config=ConfigOrID extends string ? object : ConfigOrID] Default is + * `ConfigOrID extends string ? object : ConfigOrID` + * @template {JMColumns} [Columns=JsonModelColumns] + * Default is `JsonModelColumns` * @class JsonModelImpl - * @implements {JsonModel} + * @implements {JsonModel} */ class JsonModelImpl { - /** @param {JMOptions} options - The model declaration. */ + /** @param {JMOptions} options - The model declaration. */ constructor(options) { verifyOptions(options) const { @@ -69,7 +78,7 @@ class JsonModelImpl { migrationOptions, columns, ItemClass, - idCol = 'id', + idCol = /** @type {IDCol} */ ('id'), keepRowId = true, } = options @@ -80,24 +89,29 @@ class JsonModelImpl { this.idColQ = sql.quoteId(idCol) this.Item = ItemClass - const idColDef = (columns && columns[idCol]) || {} - const jsonColDef = (columns && columns.json) || {} + const idColDef = columns?.[idCol] + const jsonColDef = + columns?.json && typeof columns.json !== 'function' + ? columns.json + : undefined const allColumns = { ...columns, + /** @type {JMColumnDef} */ [idCol]: { - type: idColDef.type || 'TEXT', - alias: idColDef.alias || '_i', + type: idColDef?.type || 'TEXT', + alias: idColDef?.alias || '_i', value: makeIdValue(idCol, idColDef), index: 'ALL', - autoIncrement: idColDef.autoIncrement, + autoIncrement: idColDef?.autoIncrement, unique: true, get: true, }, + /** @type {JMColumnDef} */ json: { - alias: jsonColDef.alias || '_j', + alias: jsonColDef?.alias || '_j', // return null if empty, makes parseRow faster - parse: jsonColDef.parse || parseJson, - stringify: jsonColDef.stringify || stringifyJsonObject, + parse: jsonColDef?.parse || parseJson, + stringify: jsonColDef?.stringify || stringifyJsonObject, type: 'JSON', alwaysObject: true, path: '', @@ -106,6 +120,7 @@ class JsonModelImpl { } // Note the order above, id and json should be calculated last this.columnArr = [] + /** @type {{[x: string]: JMColumnDef}} */ this.columns = {} let i = 0 for (const colName of Object.keys(allColumns)) { @@ -153,8 +168,9 @@ class JsonModelImpl { this.selectColsSql = this.selectCols.map(c => c.select).join(',') } - parseRow = (row, options) => { - /** @type {JMColumnDef[]} */ + /** @returns {ItemT} */ + parseRow = (/** @type {SQLiteRow} */ row, options) => { + /** @type {JMColumnDef[]} */ const mapCols = options && options.cols ? options.cols.map(n => this.columns[n] ?? {name: n, alias: n, path: n}) @@ -285,11 +301,32 @@ class JsonModelImpl { } } + /** @param {string} colName */ _colSql(colName) { return this.columns[colName] ? this.columns[colName].sql : colName } - // Converts a row or array of rows to objects + /** + * @overload + * @param {SQLiteRow[] | undefined} thing + * @param options + * @returns {ItemT[] | undefined} + */ + + /** + * @overload + * @param {SQLiteRow | undefined} thing + * @param options + * @returns {ItemT | undefined} + */ + + /** + * Converts a row or array of rows to objects + * + * @param {SQLiteRow[] | SQLiteRow | undefined} thing + * @param options + * @returns {ItemT | ItemT[] | undefined} + */ toObj = (thing, options) => { if (!thing) { return @@ -303,8 +340,18 @@ class JsonModelImpl { /** * Parses query options into query parts. Override this function to implement * search behaviors. + * + * @param {JMSearchOptions} options + * @returns {[ + * string, + * SQLiteParam[], + * string[], + * string, + * SQLiteParam[], + * boolean, + * ]} */ - makeSelect(/** @type {JMSearchOptions} */ options) { + makeSelect(options) { if (process.env.NODE_ENV !== 'production') { const extras = Object.keys(options).filter( k => @@ -538,7 +585,7 @@ class JsonModelImpl { * * @param {JMSearchAttrs} attrs - Simple value attributes. * @param {JMSearchOptions} [options] - Search options. - * @returns {Promise} - The result or null if no match. + * @returns {Promise} - The result or null if no match. */ async searchOne(attrs, options) { const [q, vals] = this.makeSelect({ @@ -551,6 +598,10 @@ class JsonModelImpl { return this.toObj(row, options) } + /** + * @param {JMSearchOptions} [attrs] - Simple value attributes. + * @param options - Search options. + */ async search(attrs, {itemsOnly, ...options} = {}) { const [q, vals, cursorKeys, totalQ, totalVals, invert] = this.makeSelect({ attrs, @@ -712,7 +763,7 @@ class JsonModelImpl { /** * Get all objects. * - * @returns {Promise} - The table contents. + * @returns {Promise} - The table contents. */ all() { if (this._allSql?.db !== this.db) @@ -729,7 +780,7 @@ class JsonModelImpl { * @param {IDValue} id - The value for the column. * @param {string} [colName=this.idCol] - The columnname, defaults to the ID * column. Default is `this.idCol` - * @returns {Promise} - The object if it exists. + * @returns {Promise} - The object if it exists. */ get(id, colName = this.idCol) { if (id == null) { @@ -757,8 +808,8 @@ class JsonModelImpl { * @param {IDValue[]} ids - The values for the column. * @param {string} [colName=this.idCol] - The columnname, defaults to the ID * column. Default is `this.idCol` - * @returns {Promise<(Item | null)[]>} - The objects, or null where they don't - * exist, in order of their requested ID. + * @returns {Promise<(ItemT | null)[]>} - The objects, or null where they + * don't exist, in order of their requested ID. */ async getAll(ids, colName = this.idCol) { let {path, _getAllSql} = this.columns[colName] @@ -781,7 +832,7 @@ class JsonModelImpl { return ids.map(id => objs.find(o => get(o, path) === id)) } - _ensureLoader(/** @type {JMCache} */ cache, colName) { + _ensureLoader(/** @type {JMCache} */ cache, colName) { if (!cache) throw new Error(`cache is required`) const key = `_DL_${this.name}_${colName}` if (!cache[key]) { @@ -816,7 +867,7 @@ class JsonModelImpl { } // I wish I could use these types - // @typedef {(o: Item) => Promise} RowCallback + // @typedef {(o: ItemT) => Promise} RowCallback // @typedef { // (fn: RowCallback) => Promise | // (attrs: JMSearchAttrs, fn: RowCallback) => Promise | @@ -901,7 +952,7 @@ class JsonModelImpl { * @param {Object} obj The changes to store, including the id field. * @param {boolean} [upsert] Insert the object if it doesn't exist. * @param {boolean} [noReturn] Do not return the stored object. - * @returns {Promise} A copy of the stored object. + * @returns {Promise} A copy of the stored object. */ update(obj, upsert, noReturn) { // Update needs to read the object to apply the changes, so it needs a transaction diff --git a/src/JsonModel/verifyOptions.js b/src/JsonModel/verifyOptions.js index f394600..b01ffa7 100644 --- a/src/JsonModel/verifyOptions.js +++ b/src/JsonModel/verifyOptions.js @@ -1,110 +1,5 @@ import PropTypes from 'prop-types' -/** - * @type {Object} - * - * @typedef ColumnDef - * @property {boolean} [real=!!type] - Is this a real table column. Default is - * `!!type` - * @property {string} [type] - * - * - Sql column type as accepted by {@link DB} - * - * @property {string} [path] - * - * - Path to the value in the object. - * - * @property {boolean} [autoIncrement] - * - * - INTEGER id column only: apply AUTOINCREMENT on the column. - * - * @property {string} [alias] - * - * - The alias to use in SELECT statements. - * - * @property {boolean} [get=true] - Should the column be included in search - * results. Default is `true` - * @property {function} [parse] - * - * - Process the value after getting from DB. - * - * @property {function} [stringify] - * - * - Process the value before putting into DB. - * - * @property {boolean} [alwaysObject] - * - * - The value is an object and must always be there. If this is a real column, a - * NULL column value will be replaced by `{}` and vice versa. - * - * @property {function} [value] - * - * - Function getting object and returning the value for the column; this creates - * a real column. Right now the column value is not regenerated for - * existing rows. - * - * @property {function} [slugValue] - * - * - Same as value, but the result is used to generate a unique slug. - * - * @property {string} [sql] - * - * - Any sql expression to use in SELECT statements. - * - * @property {any} [default] - * - * - If the value is nullish, this will be stored instead. - * - * @property {boolean} [required] - * - * - Throw when trying to store a NULL. - * - * @property {boolean} [falsyBool] - * - * - Store/retrieve this boolean value as either `true` or absent from the object. - * - * @property {boolean} [index] - * - * - Should it be indexed? If `unique` is false, NULLs are never indexed. - * - * @property {boolean} [ignoreNull=!unique] - Are null values ignored in the - * index?. Default is `!unique` - * @property {boolean} [unique] - * - * - Should the index enforce uniqueness? - * - * @property {function} [whereVal] - * - * - A function receiving `origVals` and returning the `vals` given to `where`. It - * should return falsy or an array of values. - * - * @property {string | function} [where] - * - * - The where clause for querying, or a function returning one given `(vals, - * origVals)` - * - * @property {boolean} [isArray] - * - * - This column contains an array of values. - * - * @property {boolean} [in] - * - * - To query, this column value must match one of the given array items. - * - * @property {boolean} [inAll] - * - * - [isArray only] to query, this column value must match all of the given array - * items. - * - * @property {boolean} [textSearch] - * - * - Perform searches as substring search with LIKE. - * - * @property {boolean} [isAnyOfArray] - * - * - Alias for isArray+inAll. - */ - export const columnPropType = process.env.NODE_ENV === 'production' ? null @@ -169,24 +64,6 @@ export const verifyColumn = (name, column) => { } } -/** - * @type {Object} - * - * @typedef JMOptions - * @property {DB} db - A DB instance, normally passed by DB. - * @property {string} name - The table name. - * @property {Object} [migrations] - An object with migration functions. They - * are ran in alphabetical order. - * @property {Object} [migrationOptions] - Free-form data passed to the - * migration functions. - * @property {Object} [columns] - The column definitions as {@link ColumnDef} - * objects. Each value must be a columndef or a function returning a - * columndef. - * @property {function} [ItemClass] - An object class to use for results, must - * be able to handle `Object.assign(item, result)` - * @property {string} [idCol='id'] - The key of the ID column. Default is `'id'` - * @property {boolean} [keepRowId] - Preserve row id after vacuum. - */ const jmPropTypes = process.env.NODE_ENV === 'production' ? null @@ -203,7 +80,12 @@ const jmPropTypes = ), migrationOptions: PropTypes.object, columns: PropTypes.objectOf( - PropTypes.oneOfType([PropTypes.func, columnPropType]) + PropTypes.oneOfType([ + PropTypes.func, + /** @type {NonNullable} */ ( + columnPropType + ), + ]) ), ItemClass: PropTypes.func, idCol: PropTypes.string, diff --git a/types.d.ts b/types.d.ts index b4c40c8..fd7b3d9 100644 --- a/types.d.ts +++ b/types.d.ts @@ -90,6 +90,8 @@ declare class SQLite extends EventEmitter { sql: {quoteId: (id: SQLiteParam) => string} & SqlTag /** `true` if an sqlite connection was set up. Mostly useful for tests. */ isOpen: boolean + /** `true` if `withTransaction` is active */ + inTransaction: boolean /** * Force opening the database instead of doing it lazily on first access. * @@ -416,7 +418,7 @@ type JMOptions< * An object class to use for results, must be able to handle * `Object.assign(item, result)` */ - ItemClass?: object + ItemClass?: new () => object /** The key of the IDCol column */ idCol?: IDCol /** Preserve row id after vacuum */ @@ -461,6 +463,21 @@ type JMSearchOptions = { noTotal?: boolean } +type JsonModelIDColumn = ConfigOrID extends {idCol: string} + ? ConfigOrID['idCol'] + : ConfigOrID extends string + ? ConfigOrID + : 'id' +type JsonModelItem = RealItem extends {[id in IDCol]?: unknown} + ? RealItem + : RealItem & {[id in IDCol]: IDValue} +type JsonModelColumns = Config extends { + columns: {[x: string]: any} +} + ? Config['columns'] + : // If we didn't get a config, assume all keys are columns + Item + /** * Stores Item objects in a SQLite table. Pass the type of the item it stores * and the config so it can determine the columns @@ -469,19 +486,10 @@ declare class JsonModel< RealItem extends {[x: string]: any} = {id: string}, // Allow the id column name as well for backwards compatibility ConfigOrID = 'id', - IDCol extends string = ConfigOrID extends {idCol: string} - ? ConfigOrID['idCol'] - : ConfigOrID extends string - ? ConfigOrID - : 'id', - Item extends {[x: string]: any} = RealItem extends {[id in IDCol]?: unknown} - ? RealItem - : RealItem & {[id in IDCol]: IDValue}, + IDCol extends string = JsonModelIDColumn, + Item extends {[x: string]: any} = JsonModelItem, Config = ConfigOrID extends string ? object : ConfigOrID, - Columns extends JMColumns = Config extends {columns: object} - ? Config['columns'] - : // If we didn't get a config, assume all keys are columns - Item, + Columns extends JMColumns = JsonModelColumns, SearchAttrs = JMSearchAttrs, SearchOptions = JMSearchOptions, > { @@ -498,11 +506,11 @@ declare class JsonModel< /** The SQL-quoted name of the id column */ idColQ: string /** The prototype of returned Items */ - Item: object + Item?: new () => object /** The column definitions */ - columnArr: JMColumnDef[] + columnArr: JMColumnDef[] /** The column definitions keyed by name */ - columns: Columns + columns: {[x: string]: JMColumnDef} /** Parses a row as returned by sqlite */ parseRow: (row: SQLiteRow, options?: SearchOptions) => Item /** @@ -512,7 +520,7 @@ declare class JsonModel< makeSelect( /** The query options. */ options: SearchOptions - ): [string, SQLiteParam[], string[], string, SQLiteParam[]] + ): [string, SQLiteParam[], string[], string, SQLiteParam[], boolean] /** * Search the first matching object. * @@ -703,7 +711,7 @@ declare class JsonModel< obj: Partial, upsert?: boolean, noReturn?: boolean - ): Promise + ): Promise /** * Update or upsert an object. This does not use a transaction so is open to * race conditions if you don't run it in a transaction. @@ -759,36 +767,54 @@ type EQOptions = JMOptions & { /** Add views to the database to assist with inspecting the data */ withViews?: boolean } + +type EventQueueIDColumn = Config extends {idCol: string} + ? Config['idCol'] + : 'v' +type EventQueueItem = RealItem extends { + [id in IDCol]?: unknown +} + ? RealItem + : RealItem & {[id in IDCol]: IDValue} +type EventQueueColumns = Config extends {columns: object} + ? Config['columns'] + : // If we didn't get a config, assume all keys are columns + {[colName in keyof Item]: object} +type JsonModelConfig = { + idCol: 'v' + columns: { + v: true + type: true + ts: true + data: true + result: true + size: true + } +} + +type EventQueueAddResult = { + v: number + type: T + ts: number + data?: EventTypes[T] +} + /** Creates a new EventQueue model, called by DB. */ -interface EventQueue< +declare class EventQueue< RealItem extends ESEvent = ESEvent, Config extends Partial> = object, - IDCol extends string = Config extends {idCol: string} ? Config['idCol'] : 'v', - Item extends {[x: string]: any} = RealItem extends {[id in IDCol]?: unknown} - ? RealItem - : RealItem & {[id in IDCol]: IDValue}, - Columns extends JMColumns = Config extends {columns: object} - ? Config['columns'] - : // If we didn't get a config, assume all keys are columns - {[colName in keyof Item]: object}, + IDCol extends string = EventQueueIDColumn, + Item extends {[x: string]: any} = EventQueueItem, + Columns extends JMColumns = EventQueueColumns, > extends JsonModel< - RealItem, - { - idCol: 'v' - columns: { - v: true - type: true - ts: true - data: true - result: true - size: true - } - } & Config, - IDCol, - Item, - Columns - > { - new (options: EQOptions): this + RealItem, + JsonModelConfig & Config, + IDCol, + Item, + Config, + Columns +> { + constructor(options: EQOptions): this /** * Get the highest version stored in the queue. * @@ -808,7 +834,7 @@ interface EventQueue< type: T, data?: EventTypes[T], ts?: number - ): Promise + ): Promise> /** * Get the next event after v (gaps are ok). The wait can be cancelled by * `.cancelNext()`. @@ -817,7 +843,7 @@ interface EventQueue< * @param [noWait=false] - Do not wait for the next event. Default is `false` * @returns The event if found. */ - getNext(v?: number, noWait?: boolean): Promise + getNext(v?: number, noWait?: boolean): Promise /** Cancel any pending `.getNext()` calls */ cancelNext(): void /** @@ -825,7 +851,7 @@ interface EventQueue< * * @param v - The last known version. */ - setKnownV(v: number): Promise + setKnownV(v: number): void } type ReduceResult = Record<