diff --git a/src/datatype/index.d.ts b/src/datatype/index.d.ts deleted file mode 100644 index 2a6f7d367..000000000 --- a/src/datatype/index.d.ts +++ /dev/null @@ -1,108 +0,0 @@ -// Typescript was incorrectly compiling declaration files for this, so the -// declaration for DataType is written manually below, and copied into the -// `dist` folder at build-time. The types are checked in `test-types/data-types.ts` - -import { type MapeoDoc, type MapeoValue } from '@comapeo/schema' -import { - type MapeoDocMap, - type MapeoValueMap, - type CoreOwnershipWithSignaturesValue, -} from '../types.js' -import { type BetterSQLite3Database } from 'drizzle-orm/better-sqlite3' -import { SQLiteSelectBase } from 'drizzle-orm/sqlite-core' -import { RunResult } from 'better-sqlite3' -import type Hypercore from 'hypercore' -import { TypedEmitter } from 'tiny-typed-emitter' -import TranslationApi from '../translation-api.js' - -type MapeoDocTableName = `${MapeoDoc['schemaName']}Table` -type GetMapeoDocTables = T[keyof T & MapeoDocTableName] -/** Union of Drizzle schema tables that correspond to MapeoDoc types (e.g. excluding backlink tables and other utility tables) */ -type MapeoDocTables = - | GetMapeoDocTables - | GetMapeoDocTables -type MapeoDocTablesMap = { - [K in MapeoDocTables['_']['name']]: Extract< - MapeoDocTables, - { _: { name: K } } - > -} -export interface DataTypeEvents { - 'updated-docs': (docs: TDoc[]) => void -} - -export const kCreateWithDocId: unique symbol -export const kSelect: unique symbol -export const kTable: unique symbol -export const kDataStore: unique symbol - -type OmitUnion = T extends any ? Omit : never -type ExcludeSchema< - T extends MapeoValue, - S extends MapeoValue['schemaName'] -> = Exclude - -export class DataType< - TDataStore extends import('../datastore/index.js').DataStore, - TTable extends MapeoDocTables, - TSchemaName extends TTable['_']['name'], - TDoc extends MapeoDocMap[TSchemaName], - TValue extends MapeoValueMap[TSchemaName] -> extends TypedEmitter> { - constructor({ - dataStore, - table, - db, - getTranslations, - }: { - table: TTable - dataStore: TDataStore - db: import('drizzle-orm/better-sqlite3').BetterSQLite3Database - getTranslations: TranslationApi['get'] - }) - - get [kTable](): TTable - - get [kDataStore](): TDataStore - - get schemaName(): TSchemaName - - get namespace(): TDataStore.namespace - - [kCreateWithDocId]( - docId: string, - value: - | ExcludeSchema - | CoreOwnershipWithSignaturesValue - ): Promise - - [kSelect](): Promise> - - create< - T extends import('type-fest').Exact< - ExcludeSchema, - T - > - >(value: T): Promise - - getByDocId( - docId: string, - opts?: { lang?: string } - ): Promise - - getByVersionId(versionId: string, opts?: { lang?: string }): Promise - - getMany(opts?: { - includeDeleted?: boolean - lang?: string - }): Promise> - - update< - T extends import('type-fest').Exact< - ExcludeSchema, - T - > - >(versionId: string | string[], value: T): Promise - - delete(docId: string): Promise -} diff --git a/src/datatype/index.js b/src/datatype/index.js index 82459ca2c..026bb3742 100644 --- a/src/datatype/index.js +++ b/src/datatype/index.js @@ -8,29 +8,57 @@ import { TypedEmitter } from 'tiny-typed-emitter' import { parse as parseBCP47 } from 'bcp-47' import { setProperty, getProperty } from 'dot-prop' /** @import { MapeoDoc, MapeoValue } from '@comapeo/schema' */ -/** @import { MapeoDocMap, MapeoValueMap } from '../types.js' */ +/** @import { RunResult } from 'better-sqlite3' */ +/** @import { SQLiteSelectBase } from 'drizzle-orm/sqlite-core' */ +/** @import { Exact } from 'type-fest' */ /** @import { DataStore } from '../datastore/index.js' */ +/** + * @import { + * CoreOwnershipWithSignaturesValue, + * DefaultEmitterEvents, + * MapeoDocMap, + * MapeoValueMap, + * } from '../types.js' + */ /** + * @internal * @typedef {`${MapeoDoc['schemaName']}Table`} MapeoDocTableName */ + /** + * @internal * @template T * @typedef {T[(keyof T) & MapeoDocTableName]} GetMapeoDocTables */ + /** * Union of Drizzle schema tables that correspond to MapeoDoc types (e.g. excluding backlink tables and other utility tables) + * @internal * @typedef {GetMapeoDocTables | GetMapeoDocTables} MapeoDocTables */ + /** + * @internal * @typedef {{ [K in MapeoDocTables['_']['name']]: Extract }} MapeoDocTablesMap */ + /** + * @internal * @template T * @template {keyof any} K * @typedef {T extends any ? Omit : never} OmitUnion */ + +/** + * @internal + * @template {MapeoValue} T + * @template {MapeoValue['schemaName']} S + * @typedef {Exclude} ExcludeSchema + */ + /** + * @internal * @template {MapeoDoc} TDoc * @typedef {object} DataTypeEvents * @property {(docs: TDoc[]) => void} updated-docs @@ -49,11 +77,11 @@ export const kDataStore = Symbol('dataStore') /** * @template {DataStore} TDataStore - * @template {TDataStore['schemas'][number]} TSchemaName - * @template {MapeoDocTablesMap[TSchemaName]} TTable - * @template {Exclude} TDoc - * @template {Exclude} TValue - * @extends {TypedEmitter & import('../types.js').DefaultEmitterEvents>>} + * @template {MapeoDocTables} TTable + * @template {TTable['_']['name']} TSchemaName + * @template {MapeoDocMap[TSchemaName]} TDoc + * @template {MapeoValueMap[TSchemaName]} TValue + * @extends {TypedEmitter & DefaultEmitterEvents>>} */ export class DataType extends TypedEmitter { #dataStore @@ -106,36 +134,42 @@ export class DataType extends TypedEmitter { }) } + /** @returns {TTable} */ get [kTable]() { return this.#table } + /** @returns {TSchemaName} */ get schemaName() { return this.#schemaName } + /** @returns {TDataStore['namespace']} */ get namespace() { return this.#dataStore.namespace } + /** @returns {TDataStore} */ get [kDataStore]() { return this.#dataStore } /** - * @template {import('type-fest').Exact} T + * @template {Exact, T>} T * @param {T} value + * @returns {Promise} */ async create(value) { const docId = generateId() - // @ts-expect-error - can't figure this one out, types in index.d.ts override this + // @ts-expect-error - can't figure this one out return this[kCreateWithDocId](docId, value, { checkExisting: false }) } /** * @param {string} docId - * @param {TValue | import('../types.js').CoreOwnershipWithSignaturesValue} value + * @param {ExcludeSchema | CoreOwnershipWithSignaturesValue} value * @param {{ checkExisting?: boolean }} [opts] - only used internally to skip the checkExisting check when creating a document with a random ID (collisions should be too small probability to be worth checking for) + * @returns {Promise} */ async [kCreateWithDocId](docId, value, { checkExisting = true } = {}) { if (!validate(this.#schemaName, value)) { @@ -166,12 +200,11 @@ export class DataType extends TypedEmitter { /** * @param {string} docId * @param {{ lang?: string }} [opts] + * @returns {Promise} */ async getByDocId(docId, { lang } = {}) { await this.#dataStore.indexer.idle() - const result = /** @type {undefined | MapeoDoc} */ ( - this.#sql.getByDocId.get({ docId }) - ) + const result = this.#sql.getByDocId.get({ docId }) if (!result) throw new NotFoundError() return this.#translate(deNullify(result), { lang }) } @@ -179,15 +212,17 @@ export class DataType extends TypedEmitter { /** * @param {string} versionId * @param {{ lang?: string }} [opts] + * @returns {Promise} */ async getByVersionId(versionId, { lang } = {}) { const result = await this.#dataStore.read(versionId) - return this.#translate(result, { lang }) + return this.#translate(deNullify(result), { lang }) } /** - * @param {MapeoDoc} doc + * @param {any} doc * @param {{ lang?: string }} [opts] + * @returns {Promise} */ async #translate(doc, { lang } = {}) { if (!lang) return doc @@ -221,27 +256,27 @@ export class DataType extends TypedEmitter { return doc } - /** @param {{ includeDeleted?: boolean, lang?: string }} [opts] */ + /** + * @param {object} opts + * @param {boolean} [opts.includeDeleted] + * @param {string} [opts.lang] + * @returns {Promise>} + */ async getMany({ includeDeleted = false, lang } = {}) { await this.#dataStore.indexer.idle() const rows = includeDeleted ? this.#sql.getManyWithDeleted.all() : this.#sql.getMany.all() return await Promise.all( - rows.map( - async (doc) => - await this.#translate(deNullify(/** @type {MapeoDoc} */ (doc)), { - lang, - }) - ) + rows.map((doc) => this.#translate(deNullify(doc), { lang })) ) } /** - * - * @template {import('type-fest').Exact} T + * @template {Exact, T>} T * @param {string | string[]} versionId * @param {T} value + * @returns {Promise} */ async update(versionId, value) { await this.#dataStore.indexer.idle() @@ -265,6 +300,7 @@ export class DataType extends TypedEmitter { /** * @param {string} docId + * @returns {Promise} */ async delete(docId) { await this.#dataStore.indexer.idle() @@ -278,7 +314,6 @@ export class DataType extends TypedEmitter { const doc = { ...existingDoc, updatedAt: new Date().toISOString(), - // @ts-expect-error - TS just doesn't work in this class links: [existingDoc.versionId, ...existingDoc.forks], deleted: true, } diff --git a/src/translation-api.js b/src/translation-api.js index 05d0a92eb..83e03cbee 100644 --- a/src/translation-api.js +++ b/src/translation-api.js @@ -1,6 +1,6 @@ import { and, sql } from 'drizzle-orm' import { kCreateWithDocId, kSelect } from './datatype/index.js' -import { hashObject } from './utils.js' +import { deNullify, hashObject } from './utils.js' import { NotFoundError } from './errors.js' import { omit } from './lib/omit.js' /** @import { Translation, TranslationValue } from '@comapeo/schema' */ @@ -105,6 +105,7 @@ export default class TranslationApi { .where(and.apply(null, filters)) .prepare() .all() + .map(deNullify) } /**