From b696c1e6619afe24cc1dbcfbfafe8128afa68a08 Mon Sep 17 00:00:00 2001 From: Erik Michelson Date: Thu, 13 Mar 2025 01:29:47 +0100 Subject: [PATCH] feat(knex): initial knexjs migration Co-authored-by: Philip Molares Signed-off-by: Erik Michelson Signed-off-by: Philip Molares --- backend/.eslintrc.js | 15 +- backend/knexfile.ts | 21 ++ backend/package.json | 5 +- backend/src/app.module.ts | 3 + backend/src/database/identity.ts | 32 -- backend/src/database/knex.types.ts | 83 ----- .../migrations/20250312211152_initial.ts | 351 ++++++++++++++++++ backend/src/database/{ => types}/alias.ts | 14 +- backend/src/database/{ => types}/api-token.ts | 23 +- .../database/{ => types}/authorship-info.ts | 20 +- .../src/database/{ => types}/group-user.ts | 11 +- backend/src/database/{ => types}/group.ts | 17 +- backend/src/database/types/identity.ts | 44 +++ backend/src/database/types/index.ts | 44 +++ backend/src/database/types/knex.types.ts | 118 ++++++ .../src/database/{ => types}/media-upload.ts | 28 +- .../{ => types}/note-group-permission.ts | 14 +- .../{ => types}/note-user-permission.ts | 14 +- backend/src/database/{ => types}/note.ts | 17 +- .../src/database/{ => types}/revision-tag.ts | 11 +- backend/src/database/{ => types}/revision.ts | 32 +- .../database/{ => types}/user-pinned-note.ts | 11 +- backend/src/database/{ => types}/user.ts | 31 +- yarn.lock | 292 ++++++++++++++- 24 files changed, 1066 insertions(+), 185 deletions(-) create mode 100644 backend/knexfile.ts delete mode 100644 backend/src/database/identity.ts delete mode 100644 backend/src/database/knex.types.ts create mode 100644 backend/src/database/migrations/20250312211152_initial.ts rename backend/src/database/{ => types}/alias.ts (71%) rename backend/src/database/{ => types}/api-token.ts (61%) rename backend/src/database/{ => types}/authorship-info.ts (61%) rename backend/src/database/{ => types}/group-user.ts (59%) rename backend/src/database/{ => types}/group.ts (68%) create mode 100644 backend/src/database/types/identity.ts create mode 100644 backend/src/database/types/index.ts create mode 100644 backend/src/database/types/knex.types.ts rename backend/src/database/{ => types}/media-upload.ts (59%) rename backend/src/database/{ => types}/note-group-permission.ts (59%) rename backend/src/database/{ => types}/note-user-permission.ts (59%) rename backend/src/database/{ => types}/note.ts (72%) rename backend/src/database/{ => types}/revision-tag.ts (63%) rename backend/src/database/{ => types}/revision.ts (55%) rename backend/src/database/{ => types}/user-pinned-note.ts (69%) rename backend/src/database/{ => types}/user.ts (67%) diff --git a/backend/.eslintrc.js b/backend/.eslintrc.js index 574b8a4b4..e36689c99 100644 --- a/backend/.eslintrc.js +++ b/backend/.eslintrc.js @@ -38,14 +38,25 @@ module.exports = { ], }, }, + { + files: ['src/database/**'], + rules: { + '@typescript-eslint/naming-convention': 'off', + }, + }, + ], + plugins: [ + '@typescript-eslint', + 'jest', + 'eslint-plugin-local-rules', + '@darraghor/nestjs-typed', ], - plugins: ['@typescript-eslint', 'jest', 'eslint-plugin-local-rules','@darraghor/nestjs-typed'], extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended-requiring-type-checking', 'plugin:prettier/recommended', - 'plugin:@darraghor/nestjs-typed/recommended' + 'plugin:@darraghor/nestjs-typed/recommended', ], root: true, env: { diff --git a/backend/knexfile.ts b/backend/knexfile.ts new file mode 100644 index 000000000..03b1e733a --- /dev/null +++ b/backend/knexfile.ts @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import type { Knex } from 'knex'; + +/** This is used for the Knex CLI to create migrations during development */ +const config: { [key: string]: Knex.Config } = { + development: { + client: 'better-sqlite3', + connection: { + filename: './hedgedoc.sqlite', + }, + migrations: { + directory: './src/database/migrations', + }, + }, +}; + +module.exports = config; diff --git a/backend/package.json b/backend/package.json index a84cb13af..5851cb080 100644 --- a/backend/package.json +++ b/backend/package.json @@ -21,8 +21,7 @@ "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config jest-e2e.json && rimraf test_uploads*", "test:e2e:ci": "jest --config jest-e2e.json --coverage && rimraf test_uploads*", - "seed": "ts-node src/seed.ts", - "typeorm": "typeorm-ts-node-commonjs -d src/ormconfig.ts" + "knex": "tsx ../node_modules/.bin/knex --migrations-directory src/database/migrations --knexfile ./knexfile.ts" }, "dependencies": { "@azure/storage-blob": "12.25.0", @@ -108,8 +107,8 @@ "supertest": "6.3.4", "ts-jest": "29.2.5", "ts-mockery": "1.2.0", - "ts-node": "11.0.0-beta.1", "tsconfig-paths": "4.2.0", + "tsx": "^4.19.3", "typescript": "5.6.3" }, "jest": { diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index 675e64a24..1a8318a7d 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -76,6 +76,9 @@ const routes: Routes = [ deprecate: knexLoggerService.deprecate.bind(knexLoggerService), debug: knexLoggerService.debug.bind(knexLoggerService), }, + migrations: { + directory: 'src/database/migrations/', + }, }, }), }), diff --git a/backend/src/database/identity.ts b/backend/src/database/identity.ts deleted file mode 100644 index d3afda6a2..000000000 --- a/backend/src/database/identity.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { ProviderType } from '../auth/provider-type.enum'; - -/** - * An auth identity holds the information how a {@link User} can authenticate themself using a certain auth provider - */ -export interface Identity { - /** The id of the user */ - userId: number; - - /** The type of the auth provider */ - providerType: ProviderType; - - /** The identifier of the auth provider, e.g. gitlab */ - providerIdentifier: string | null; - - /** Timestamp when this identity was created */ - createdAt: Date; - - /** Timestamp when this identity was last updated */ - updatedAt: Date; - - /** The remote id of the user at the auth provider or null for local identities */ - providerUserId: string | null; - - /** The hashed password for local identities or null for other auth providers */ - passwordHash: string | null; -} diff --git a/backend/src/database/knex.types.ts b/backend/src/database/knex.types.ts deleted file mode 100644 index 218cf5be6..000000000 --- a/backend/src/database/knex.types.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { Knex } from 'knex'; - -import { Alias } from './alias'; -import { ApiToken } from './api-token'; -import { AuthorshipInfo } from './authorship-info'; -import { Group } from './group'; -import { GroupUser } from './group-user'; -import { Identity } from './identity'; -import { MediaUpload } from './media-upload'; -import { Note } from './note'; -import { NoteGroupPermission } from './note-group-permission'; -import { NoteUserPermission } from './note-user-permission'; -import { Revision } from './revision'; -import { RevisionTag } from './revision-tag'; -import { User } from './user'; -import { UserPinnedNote } from './user-pinned-note'; - -/* eslint-disable @typescript-eslint/naming-convention */ -declare module 'knex/types/tables' { - interface Tables { - alias_composite: Knex.CompositeTableType< - Alias, - Alias, - Pick - >; - api_token_composite: Knex.CompositeTableType< - ApiToken, - Omit, - Pick - >; - authorship_info_composite: Knex.CompositeTableType< - AuthorshipInfo, - Omit - >; - group_composite: Knex.CompositeTableType< - Group, - Omit, - Pick - >; - group_user_composite: Knex.CompositeTableType; - identity_composite: Knex.CompositeTableType< - Identity, - Omit, - Pick - >; - media_upload_composite: Knex.CompositeTableType< - MediaUpload, - Omit, - Pick - >; - note_composite: Knex.CompositeTableType< - Note, - Omit, - Pick - >; - note_group_permission_composite: Knex.CompositeTableType< - NoteGroupPermission, - NoteGroupPermission, - Pick - >; - note_user_permission_composite: Knex.CompositeTableType< - NoteUserPermission, - NoteUserPermission, - Pick - >; - revision_composite: Knex.CompositeTableType< - Revision, - Omit - >; - revision_tag_composite: Knex.CompositeTableType; - user_composite: Knex.CompositeTableType< - User, - Omit, - Pick - >; - user_pinned_note_composite: Knex.CompositeTableType; - } -} diff --git a/backend/src/database/migrations/20250312211152_initial.ts b/backend/src/database/migrations/20250312211152_initial.ts new file mode 100644 index 000000000..fbae668ee --- /dev/null +++ b/backend/src/database/migrations/20250312211152_initial.ts @@ -0,0 +1,351 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { NoteType } from '@hedgedoc/commons'; +import type { Knex } from 'knex'; + +import { ProviderType } from '../../auth/provider-type.enum'; +import { SpecialGroup } from '../../groups/groups.special'; +import { BackendType } from '../../media/backends/backend-type.enum'; +import { + FieldNameAlias, + FieldNameApiToken, + FieldNameAuthorshipInfo, + FieldNameGroup, + FieldNameGroupUser, + FieldNameIdentity, + FieldNameMediaUpload, + FieldNameNote, + FieldNameNoteGroupPermission, + FieldNameNoteUserPermission, + FieldNameRevision, + FieldNameRevisionTag, + FieldNameUser, + FieldNameUserPinnedNote, + TableAlias, + TableApiToken, + TableAuthorshipInfo, + TableGroup, + TableGroupUser, + TableIdentity, + TableMediaUpload, + TableNote, + TableNoteGroupPermission, + TableNoteUserPermission, + TableRevision, + TableRevisionTag, + TableUser, + TableUserPinnedNote, +} from '../types'; + +export async function up(knex: Knex): Promise { + // Create user table first as it's referenced by other tables + await knex.schema.createTable(TableUser, (table) => { + table.increments(FieldNameUser.id).primary(); + table.string(FieldNameUser.username).nullable().unique(); + table.string(FieldNameUser.displayName).nullable(); + table.string(FieldNameUser.photoUrl).nullable(); + table.string(FieldNameUser.email).nullable(); + table.integer(FieldNameUser.authorStyle).notNullable(); + table.uuid(FieldNameUser.guestUuid).nullable().unique(); + table.timestamp(FieldNameUser.createdAt).defaultTo(knex.fn.now()); + }); + + // Create group table + await knex.schema.createTable(TableGroup, (table) => { + table.increments(FieldNameGroup.id).primary(); + table.string(FieldNameGroup.name).notNullable(); + table.string(FieldNameGroup.displayName).notNullable(); + table.boolean(FieldNameGroup.isSpecial).notNullable().defaultTo(false); + }); + + // Create special groups _EVERYONE and _LOGGED_IN + await knex(TableGroup).insert([ + { + name: SpecialGroup.EVERYONE, + display_name: SpecialGroup.EVERYONE, + is_special: true, + }, + { + name: SpecialGroup.LOGGED_IN, + display_name: SpecialGroup.EVERYONE, + is_special: true, + }, + ]); + + // Create note table + await knex.schema.createTable(TableNote, (table) => { + table.increments(FieldNameNote.id).primary(); + table.integer(FieldNameNote.version).notNullable().defaultTo(2); + table.timestamp(FieldNameNote.createdAt).defaultTo(knex.fn.now()); + table + .integer(FieldNameNote.ownerId) + .unsigned() + .notNullable() + .references(FieldNameUser.id) + .inTable(TableUser); + }); + + // Create alias table + await knex.schema.createTable(TableAlias, (table) => { + table.comment( + 'Stores aliases of notes, only on alias per note can be is_primary == true, all other need to have is_primary == null ', + ); + table.string(FieldNameAlias.alias).primary(); + table + .integer(FieldNameAlias.noteId) + .unsigned() + .notNullable() + .references(FieldNameNote.id) + .inTable(TableNote); + table.boolean(FieldNameAlias.isPrimary).nullable(); + table.unique([FieldNameAlias.noteId, FieldNameAlias.isPrimary], { + indexName: 'only_one_note_can_be_primary', + useConstraint: true, + }); + }); + + // Create api_token table + await knex.schema.createTable(TableApiToken, (table) => { + table.string(FieldNameApiToken.id).primary(); + table + .integer(FieldNameApiToken.userId) + .unsigned() + .notNullable() + .references(FieldNameUser.id) + .inTable(TableUser); + table.string(FieldNameApiToken.label).notNullable(); + table.string(FieldNameApiToken.secretHash).notNullable(); + table.timestamp(FieldNameApiToken.validUntil).notNullable(); + table.timestamp(FieldNameApiToken.lastUsedAt).nullable(); + }); + + // Create identity table + await knex.schema.createTable(TableIdentity, (table) => { + table + .integer(FieldNameIdentity.userId) + .unsigned() + .notNullable() + .references(FieldNameUser.id) + .inTable(TableUser); + table.enu( + FieldNameIdentity.providerType, + [ProviderType.LDAP, ProviderType.LOCAL, ProviderType.OIDC], // ProviderType.GUEST is not relevant for the DB + { + useNative: true, + enumName: FieldNameIdentity.providerType, + }, + ); + table.string(FieldNameIdentity.providerIdentifier).nullable(); + table.string(FieldNameIdentity.providerUserId).nullable(); + table.string(FieldNameIdentity.passwordHash).nullable(); + table.timestamp(FieldNameIdentity.createdAt).defaultTo(knex.fn.now()); + table.timestamp(FieldNameIdentity.updatedAt).defaultTo(knex.fn.now()); + table.unique( + [ + FieldNameIdentity.userId, + FieldNameIdentity.providerType, + FieldNameIdentity.providerIdentifier, + ], + { + indexName: 'each_user_has_only_one_account_per_provider', + useConstraint: true, + }, + ); + }); + + // Create group_user join table + await knex.schema.createTable(TableGroupUser, (table) => { + table + .integer(FieldNameGroupUser.userId) + .unsigned() + .notNullable() + .references(FieldNameUser.id) + .inTable(TableUser); + table + .integer(FieldNameGroupUser.groupId) + .unsigned() + .notNullable() + .references(FieldNameGroup.id) + .inTable(TableGroup); + table.primary([FieldNameGroupUser.userId, FieldNameGroupUser.groupId]); + }); + + // Create revision table + await knex.schema.createTable(TableRevision, (table) => { + table.increments(FieldNameRevision.id).primary(); + table + .integer(FieldNameRevision.noteId) + .unsigned() + .notNullable() + .references(FieldNameNote.id) + .inTable(TableNote); + table.text(FieldNameRevision.patch).notNullable(); + table.text(FieldNameRevision.content).notNullable(); + table.string(FieldNameRevision.title).notNullable(); + table.text(FieldNameRevision.description).notNullable(); + table.binary(FieldNameRevision.yjsStateVector).nullable(); + table.enu(FieldNameRevision.noteType, [NoteType.DOCUMENT, NoteType.SLIDE], { + useNative: true, + enumName: FieldNameRevision.noteType, + }); + table.timestamp(FieldNameRevision.createdAt).defaultTo(knex.fn.now()); + }); + + // Create revision_tag table + await knex.schema.createTable(TableRevisionTag, (table) => { + table + .integer(FieldNameRevisionTag.revisionId) + .unsigned() + .notNullable() + .references(FieldNameRevision.id) + .inTable(TableRevision); + table.string(FieldNameRevisionTag.tag).notNullable(); + table.primary([FieldNameRevisionTag.revisionId, FieldNameRevisionTag.tag]); + }); + + // Create authorship_info table + await knex.schema.createTable(TableAuthorshipInfo, (table) => { + table + .integer(FieldNameAuthorshipInfo.revisionId) + .unsigned() + .notNullable() + .references(FieldNameRevision.id) + .inTable(TableRevision); + table + .integer(FieldNameAuthorshipInfo.authorId) + .unsigned() + .notNullable() + .references(FieldNameUser.id) + .inTable(TableUser); + table + .integer(FieldNameAuthorshipInfo.startPosition) + .unsigned() + .notNullable(); + table.integer(FieldNameAuthorshipInfo.endPosition).unsigned().notNullable(); + }); + + // Create note_user_permission table + await knex.schema.createTable(TableNoteUserPermission, (table) => { + table + .integer(FieldNameNoteUserPermission.noteId) + .unsigned() + .notNullable() + .references(FieldNameNote.id) + .inTable(TableNote); + table + .integer(FieldNameNoteUserPermission.userId) + .unsigned() + .notNullable() + .references(FieldNameUser.id) + .inTable(TableUser); + table + .boolean(FieldNameNoteUserPermission.canEdit) + .notNullable() + .defaultTo(false); + table.primary([ + FieldNameNoteUserPermission.noteId, + FieldNameNoteUserPermission.userId, + ]); + }); + + // Create note_group_permission table + await knex.schema.createTable(TableNoteGroupPermission, (table) => { + table + .integer(FieldNameNoteGroupPermission.noteId) + .unsigned() + .notNullable() + .references(FieldNameNote.id) + .inTable(TableNote); + table + .integer(FieldNameNoteGroupPermission.groupId) + .unsigned() + .notNullable() + .references(FieldNameGroup.id) + .inTable(TableGroup); + table + .boolean(FieldNameNoteGroupPermission.canEdit) + .notNullable() + .defaultTo(false); + table.primary([ + FieldNameNoteGroupPermission.noteId, + FieldNameNoteGroupPermission.groupId, + ]); + }); + + // Create media_upload table + await knex.schema.createTable(TableMediaUpload, (table) => { + table.uuid(FieldNameMediaUpload.uuid).primary(); + table + .integer(FieldNameMediaUpload.noteId) + .unsigned() + .nullable() + .references(FieldNameNote.id) + .inTable(TableNote); + table + .integer(FieldNameMediaUpload.userId) + .unsigned() + .notNullable() + .references(FieldNameUser.id) + .inTable(TableUser); + table.string(FieldNameMediaUpload.fileName).notNullable(); + table + .enu( + FieldNameMediaUpload.backendType, + [ + BackendType.AZURE, + BackendType.FILESYSTEM, + BackendType.IMGUR, + BackendType.S3, + BackendType.WEBDAV, + ], + { + useNative: true, + enumName: FieldNameMediaUpload.backendType, + }, + ) + .notNullable(); + table.text(FieldNameMediaUpload.backendData).nullable(); + table.timestamp(FieldNameMediaUpload.createdAt).defaultTo(knex.fn.now()); + }); + + // Create user_pinned_note table + await knex.schema.createTable(TableUserPinnedNote, (table) => { + table + .integer(FieldNameUserPinnedNote.userId) + .unsigned() + .notNullable() + .references(FieldNameUser.id) + .inTable(TableUser); + table + .integer(FieldNameUserPinnedNote.noteId) + .unsigned() + .notNullable() + .references(FieldNameNote.id) + .inTable(TableNote); + table.primary([ + FieldNameUserPinnedNote.userId, + FieldNameUserPinnedNote.noteId, + ]); + }); +} + +export async function down(knex: Knex): Promise { + // Drop tables in reverse order of creation to avoid integer key constraints + await knex.schema.dropTableIfExists(TableUserPinnedNote); + await knex.schema.dropTableIfExists(TableMediaUpload); + await knex.schema.dropTableIfExists(TableNoteGroupPermission); + await knex.schema.dropTableIfExists(TableNoteUserPermission); + await knex.schema.dropTableIfExists(TableAuthorshipInfo); + await knex.schema.dropTableIfExists(TableRevisionTag); + await knex.schema.dropTableIfExists(TableRevision); + await knex.schema.dropTableIfExists(TableGroupUser); + await knex.schema.dropTableIfExists(TableIdentity); + await knex.schema.dropTableIfExists(TableApiToken); + await knex.schema.dropTableIfExists(TableApiToken); + await knex.schema.dropTableIfExists(TableNote); + await knex.schema.dropTableIfExists(TableGroup); + await knex.schema.dropTableIfExists(TableUser); +} diff --git a/backend/src/database/alias.ts b/backend/src/database/types/alias.ts similarity index 71% rename from backend/src/database/alias.ts rename to backend/src/database/types/alias.ts index 594a04a9f..f58cc3631 100644 --- a/backend/src/database/alias.ts +++ b/backend/src/database/types/alias.ts @@ -12,11 +12,19 @@ */ export interface Alias { /** The alias as defined by the user. Is unique. */ - alias: string; + [FieldNameAlias.alias]: string; /** The id of the associated {@link Note}. */ - noteId: number; + [FieldNameAlias.noteId]: number; /** Whether the alias is the primary one for the note. */ - isPrimary: boolean; + [FieldNameAlias.isPrimary]: boolean; } + +export enum FieldNameAlias { + alias = 'alias', + noteId = 'note_id', + isPrimary = 'is_primary', +} + +export const TableAlias = 'alias'; diff --git a/backend/src/database/api-token.ts b/backend/src/database/types/api-token.ts similarity index 61% rename from backend/src/database/api-token.ts rename to backend/src/database/types/api-token.ts index 52eca966a..1deb30165 100644 --- a/backend/src/database/api-token.ts +++ b/backend/src/database/types/api-token.ts @@ -11,20 +11,31 @@ */ export interface ApiToken { /** The id of the token, a short random ASCII string. Is unique */ - id: string; + [FieldNameApiToken.id]: string; /** The {@link User} whose permissions the token has */ - userId: number; + [FieldNameApiToken.userId]: number; /** The user-defined label for the token, such as "CLI" */ - label: string; + [FieldNameApiToken.label]: string; /** Hashed version of the token's secret */ - secretHash: string; + [FieldNameApiToken.secretHash]: string; /** Expiry date of the token */ - validUntil: Date; + [FieldNameApiToken.validUntil]: Date; /** When the token was last used. When it was never used yet, this field is null */ - lastUsedAt: Date | null; + [FieldNameApiToken.lastUsedAt]: Date | null; } + +export enum FieldNameApiToken { + id = 'id', + userId = 'user_id', + label = 'label', + secretHash = 'secret_hash', + validUntil = 'valid_until', + lastUsedAt = 'last_used_at', +} + +export const TableApiToken = 'api_token'; diff --git a/backend/src/database/authorship-info.ts b/backend/src/database/types/authorship-info.ts similarity index 61% rename from backend/src/database/authorship-info.ts rename to backend/src/database/types/authorship-info.ts index 8be52e828..024932270 100644 --- a/backend/src/database/authorship-info.ts +++ b/backend/src/database/types/authorship-info.ts @@ -11,17 +11,23 @@ */ export interface AuthorshipInfo { /** The id of the {@link Revision} this belongs to. */ - revisionId: number; + [FieldNameAuthorshipInfo.revisionId]: number; /** The id of the author of the edit. */ - authorId: number; + [FieldNameAuthorshipInfo.authorId]: number; /** The start position of the change in the note as a positive index. */ - startPos: number; + [FieldNameAuthorshipInfo.startPosition]: number; /** The end position of the change in the note as a positive index. */ - endPos: number; - - /** The creation datetime of the edit. */ - createdAt: Date; + [FieldNameAuthorshipInfo.endPosition]: number; } + +export enum FieldNameAuthorshipInfo { + revisionId = 'revision_id', + authorId = 'author_id', + startPosition = 'start_position', + endPosition = 'end_position', +} + +export const TableAuthorshipInfo = 'authorship_info'; diff --git a/backend/src/database/group-user.ts b/backend/src/database/types/group-user.ts similarity index 59% rename from backend/src/database/group-user.ts rename to backend/src/database/types/group-user.ts index 71abbccf6..32f599a07 100644 --- a/backend/src/database/group-user.ts +++ b/backend/src/database/types/group-user.ts @@ -9,8 +9,15 @@ */ export interface GroupUser { /** The id of the {@link Group} a {@link User} is part of */ - groupId: number; + [FieldNameGroupUser.groupId]: number; /** The id of the {@link User} */ - userId: number; + [FieldNameGroupUser.userId]: number; } + +export enum FieldNameGroupUser { + groupId = 'group_id', + userId = 'user_id', +} + +export const TableGroupUser = 'group_user'; diff --git a/backend/src/database/group.ts b/backend/src/database/types/group.ts similarity index 68% rename from backend/src/database/group.ts rename to backend/src/database/types/group.ts index e8080068b..6e178496a 100644 --- a/backend/src/database/group.ts +++ b/backend/src/database/types/group.ts @@ -11,14 +11,23 @@ */ export interface Group { /** The unique id for internal referencing */ - id: number; + [FieldNameGroup.id]: number; /** The public identifier of the group (username for the group) */ - name: string; + [FieldNameGroup.name]: string; /** The display name of the group */ - displayName: string; + [FieldNameGroup.displayName]: string; /** Whether the group is one of the special groups */ - isSpecial: boolean; + [FieldNameGroup.isSpecial]: boolean; } + +export enum FieldNameGroup { + id = 'id', + name = 'name', + displayName = 'display_name', + isSpecial = 'is_special', +} + +export const TableGroup = 'group'; diff --git a/backend/src/database/types/identity.ts b/backend/src/database/types/identity.ts new file mode 100644 index 000000000..a3802c1e5 --- /dev/null +++ b/backend/src/database/types/identity.ts @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { ProviderType } from '../../auth/provider-type.enum'; + +/** + * An auth identity holds the information how a {@link User} can authenticate themselves using a certain auth provider + */ +export interface Identity { + /** The id of the user */ + [FieldNameIdentity.userId]: number; + + /** The type of the auth provider */ + [FieldNameIdentity.providerType]: ProviderType; + + /** The identifier of the auth provider, e.g. gitlab */ + [FieldNameIdentity.providerIdentifier]: string | null; + + /** Timestamp when this identity was created */ + [FieldNameIdentity.createdAt]: Date; + + /** Timestamp when this identity was last updated */ + [FieldNameIdentity.updatedAt]: Date; + + /** The remote id of the user at the auth provider or null for local identities */ + [FieldNameIdentity.providerUserId]: string | null; + + /** The hashed password for local identities or null for other auth providers */ + [FieldNameIdentity.passwordHash]: string | null; +} + +export enum FieldNameIdentity { + userId = 'user_id', + providerType = 'provider_type', + providerIdentifier = 'provider_identifier', + createdAt = 'created_at', + updatedAt = 'updated_at', + providerUserId = 'provider_user_id', + passwordHash = 'password_hash', +} + +export const TableIdentity = 'identity'; diff --git a/backend/src/database/types/index.ts b/backend/src/database/types/index.ts new file mode 100644 index 000000000..17f46106e --- /dev/null +++ b/backend/src/database/types/index.ts @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export { Alias, FieldNameAlias, TableAlias } from './alias'; +export { ApiToken, FieldNameApiToken, TableApiToken } from './api-token'; +export { + AuthorshipInfo, + FieldNameAuthorshipInfo, + TableAuthorshipInfo, +} from './authorship-info'; +export { Group, FieldNameGroup, TableGroup } from './group'; +export { GroupUser, FieldNameGroupUser, TableGroupUser } from './group-user'; +export { Identity, FieldNameIdentity, TableIdentity } from './identity'; +export { + MediaUpload, + FieldNameMediaUpload, + TableMediaUpload, +} from './media-upload'; +export { Note, FieldNameNote, TableNote } from './note'; +export { + NoteGroupPermission, + FieldNameNoteGroupPermission, + TableNoteGroupPermission, +} from './note-group-permission'; +export { + NoteUserPermission, + FieldNameNoteUserPermission, + TableNoteUserPermission, +} from './note-user-permission'; +export { Revision, FieldNameRevision, TableRevision } from './revision'; +export { + RevisionTag, + FieldNameRevisionTag, + TableRevisionTag, +} from './revision-tag'; +export { User, FieldNameUser, TableUser } from './user'; +export { + UserPinnedNote, + FieldNameUserPinnedNote, + TableUserPinnedNote, +} from './user-pinned-note'; diff --git a/backend/src/database/types/knex.types.ts b/backend/src/database/types/knex.types.ts new file mode 100644 index 000000000..9bb8c7534 --- /dev/null +++ b/backend/src/database/types/knex.types.ts @@ -0,0 +1,118 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { Knex } from 'knex'; + +import { + Alias, + ApiToken, + AuthorshipInfo, + FieldNameAlias, + FieldNameApiToken, + FieldNameGroup, + FieldNameIdentity, + FieldNameMediaUpload, + FieldNameNote, + FieldNameNoteGroupPermission, + FieldNameNoteUserPermission, + FieldNameRevision, + FieldNameUser, + Group, + GroupUser, + Identity, + MediaUpload, + Note, + NoteGroupPermission, + NoteUserPermission, + Revision, + RevisionTag, + TableAlias, + TableApiToken, + TableAuthorshipInfo, + TableGroup, + TableGroupUser, + TableIdentity, + TableMediaUpload, + TableNote, + TableNoteGroupPermission, + TableNoteUserPermission, + TableRevision, + TableRevisionTag, + TableUser, + TableUserPinnedNote, + User, + UserPinnedNote, +} from './index'; + +/* eslint-disable @typescript-eslint/naming-convention */ +declare module 'knex/types/tables.js' { + interface Tables { + [TableAlias]: Knex.CompositeTableType< + Alias, + Alias, + Pick + >; + [TableApiToken]: Knex.CompositeTableType< + ApiToken, + Omit, + Pick + >; + [TableAuthorshipInfo]: AuthorshipInfo; + [TableGroup]: Knex.CompositeTableType< + Group, + Omit, + Pick + >; + [TableGroupUser]: GroupUser; + [TableIdentity]: Knex.CompositeTableType< + Identity, + Omit, + Pick< + Identity, + FieldNameIdentity.passwordHash | FieldNameIdentity.updatedAt + > + >; + [TableMediaUpload]: Knex.CompositeTableType< + MediaUpload, + Omit< + MediaUpload, + FieldNameMediaUpload.createdAt | FieldNameMediaUpload.uuid + >, + Pick + >; + [TableNote]: Knex.CompositeTableType< + Note, + Omit, + Pick + >; + [TableNoteGroupPermission]: Knex.CompositeTableType< + NoteGroupPermission, + NoteGroupPermission, + Pick + >; + [TableNoteUserPermission]: Knex.CompositeTableType< + NoteUserPermission, + NoteUserPermission, + Pick + >; + [TableRevision]: Knex.CompositeTableType< + Revision, + Omit + >; + [TableRevisionTag]: RevisionTag; + [TableUser]: Knex.CompositeTableType< + User, + Omit, + Pick< + User, + | FieldNameUser.displayName + | FieldNameUser.photoUrl + | FieldNameUser.email + | FieldNameUser.authorStyle + > + >; + [TableUserPinnedNote]: UserPinnedNote; + } +} diff --git a/backend/src/database/media-upload.ts b/backend/src/database/types/media-upload.ts similarity index 59% rename from backend/src/database/media-upload.ts rename to backend/src/database/types/media-upload.ts index d0acd747c..01eeba071 100644 --- a/backend/src/database/media-upload.ts +++ b/backend/src/database/types/media-upload.ts @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import { BackendType } from '../media/backends/backend-type.enum'; +import { BackendType } from '../../media/backends/backend-type.enum'; /** * A media upload object represents an uploaded file. While the file itself is stored in the configured storage backend, @@ -12,23 +12,35 @@ import { BackendType } from '../media/backends/backend-type.enum'; */ export interface MediaUpload { /** UUID (v7) identifying the media upload. Is public and unique */ - uuid: string; + [FieldNameMediaUpload.uuid]: string; /** The id of the attached {@link Note} or null if the media upload was detached from a note */ - noteId: number | null; + [FieldNameMediaUpload.noteId]: number | null; /** The id of the {@link User} who uploaded the media file */ - userId: number; + [FieldNameMediaUpload.userId]: number; /** The name of the uploaded file */ - fileName: string; + [FieldNameMediaUpload.fileName]: string; /** The backend where this upload is stored */ - backendType: BackendType; + [FieldNameMediaUpload.backendType]: BackendType; /** Additional data required by the backend storage to identify the uploaded file */ - backendData: string | null; + [FieldNameMediaUpload.backendData]: string | null; /** Timestamp when the file was uploaded */ - createdAt: Date; + [FieldNameMediaUpload.createdAt]: Date; } + +export enum FieldNameMediaUpload { + uuid = 'uuid', + noteId = 'note_id', + userId = 'user_id', + fileName = 'file_name', + backendType = 'backend_type', + backendData = 'backend_data', + createdAt = 'created_at', +} + +export const TableMediaUpload = 'media_upload'; diff --git a/backend/src/database/note-group-permission.ts b/backend/src/database/types/note-group-permission.ts similarity index 59% rename from backend/src/database/note-group-permission.ts rename to backend/src/database/types/note-group-permission.ts index 89c910ca7..53c98465b 100644 --- a/backend/src/database/note-group-permission.ts +++ b/backend/src/database/types/note-group-permission.ts @@ -8,11 +8,19 @@ */ export interface NoteGroupPermission { /** The id of the {@link Group} to give the {@link Note} permission to. */ - groupId: number; + [FieldNameNoteGroupPermission.groupId]: number; /** The id of the {@link Note} to give the {@link Group} permission to. */ - noteId: number; + [FieldNameNoteGroupPermission.noteId]: number; /** Whether the {@link Group} can edit the {@link Note} or not. */ - canEdit: boolean; + [FieldNameNoteGroupPermission.canEdit]: boolean; } + +export enum FieldNameNoteGroupPermission { + groupId = 'group_id', + noteId = 'note_id', + canEdit = 'can_edit', +} + +export const TableNoteGroupPermission = 'note_group_permission'; diff --git a/backend/src/database/note-user-permission.ts b/backend/src/database/types/note-user-permission.ts similarity index 59% rename from backend/src/database/note-user-permission.ts rename to backend/src/database/types/note-user-permission.ts index e69ec40d7..7bf03325d 100644 --- a/backend/src/database/note-user-permission.ts +++ b/backend/src/database/types/note-user-permission.ts @@ -8,11 +8,19 @@ */ export interface NoteUserPermission { /** The id of the {@link User} to give the {@link Note} permission to. */ - noteId: number; + [FieldNameNoteUserPermission.userId]: number; /** The id of the {@link Note} to give the {@link User} permission to. */ - userId: number; + [FieldNameNoteUserPermission.noteId]: number; /** Whether the {@link User} can edit the {@link Note} or not. */ - canEdit: boolean; + [FieldNameNoteUserPermission.canEdit]: boolean; } + +export enum FieldNameNoteUserPermission { + userId = 'user_id', + noteId = 'note_id', + canEdit = 'can_edit', +} + +export const TableNoteUserPermission = 'note_user_permission'; diff --git a/backend/src/database/note.ts b/backend/src/database/types/note.ts similarity index 72% rename from backend/src/database/note.ts rename to backend/src/database/types/note.ts index b0dca9c02..898de1b08 100644 --- a/backend/src/database/note.ts +++ b/backend/src/database/types/note.ts @@ -11,14 +11,23 @@ */ export interface Note { /** The unique id of the note for internal referencing */ - id: number; + [FieldNameNote.id]: number; /** The {@link User} id of the note owner */ - ownerId: string; + [FieldNameNote.ownerId]: number; /** The HedgeDoc major version this note was created in. This is used to migrate certain features from HD1 to HD2 */ - version: number; + [FieldNameNote.version]: number; /** Timestamp when the note was created */ - createdAt: Date; + [FieldNameNote.createdAt]: Date; } + +export enum FieldNameNote { + id = 'id', + ownerId = 'owner_id', + version = 'version', + createdAt = 'created_at', +} + +export const TableNote = 'note'; diff --git a/backend/src/database/revision-tag.ts b/backend/src/database/types/revision-tag.ts similarity index 63% rename from backend/src/database/revision-tag.ts rename to backend/src/database/types/revision-tag.ts index 0c87f27ab..4d3525626 100644 --- a/backend/src/database/revision-tag.ts +++ b/backend/src/database/types/revision-tag.ts @@ -8,8 +8,15 @@ */ export interface RevisionTag { /** The id of {@link Revision} the {@link RevisionTag Tags} are asspcoated with. */ - revisionId: number; + [FieldNameRevisionTag.revisionId]: number; /** The {@link RevisionTag Tag} text. */ - tag: string; + [FieldNameRevisionTag.tag]: string; } + +export enum FieldNameRevisionTag { + revisionId = 'revision_id', + tag = 'tag', +} + +export const TableRevisionTag = 'revision_tag'; diff --git a/backend/src/database/revision.ts b/backend/src/database/types/revision.ts similarity index 55% rename from backend/src/database/revision.ts rename to backend/src/database/types/revision.ts index 39c9f5676..73eadec6f 100644 --- a/backend/src/database/revision.ts +++ b/backend/src/database/types/revision.ts @@ -10,29 +10,43 @@ import { NoteType } from '@hedgedoc/commons'; */ export interface Revision { /** The unique id of the revision for internal referencing */ - id: number; + [FieldNameRevision.id]: number; /** The id of the note that this revision belongs to */ - noteId: number; + [FieldNameRevision.noteId]: number; /** The changes between this revision and the previous one in patch file format */ - patch: string; + [FieldNameRevision.patch]: string; /** The content of the note at this revision */ - content: string; + [FieldNameRevision.content]: string; /** The stored Y.js state for realtime editing */ - yjsStateVector: null | ArrayBuffer; + [FieldNameRevision.yjsStateVector]: null | ArrayBuffer; /** Whether the note is a document or presentation at this revision */ - noteType: NoteType; + [FieldNameRevision.noteType]: NoteType; /** The extracted note title from this revision */ - title: string; + [FieldNameRevision.title]: string; /** The extracted description from this revision */ - description: string; + [FieldNameRevision.description]: string; /** Timestamp when this revision was created */ - createdAt: Date; + [FieldNameRevision.createdAt]: Date; } + +export enum FieldNameRevision { + id = 'id', + noteId = 'note_id', + patch = 'patch', + content = 'content', + yjsStateVector = 'yjs_state_vector', + noteType = 'note_type', + title = 'title', + description = 'description', + createdAt = 'created_at', +} + +export const TableRevision = 'revision'; diff --git a/backend/src/database/user-pinned-note.ts b/backend/src/database/types/user-pinned-note.ts similarity index 69% rename from backend/src/database/user-pinned-note.ts rename to backend/src/database/types/user-pinned-note.ts index 429afbb66..fd9206ec4 100644 --- a/backend/src/database/user-pinned-note.ts +++ b/backend/src/database/types/user-pinned-note.ts @@ -10,8 +10,15 @@ */ export interface UserPinnedNote { /** The id of the {@link User} */ - userId: number; + user_id: number; /** The id of the {@link Note} */ - noteId: number; + note_id: number; } + +export enum FieldNameUserPinnedNote { + userId = 'user_id', + noteId = 'note_id', +} + +export const TableUserPinnedNote = 'user_pinned_note'; diff --git a/backend/src/database/user.ts b/backend/src/database/types/user.ts similarity index 67% rename from backend/src/database/user.ts rename to backend/src/database/types/user.ts index 653dc47c0..18fbfd36f 100644 --- a/backend/src/database/user.ts +++ b/backend/src/database/types/user.ts @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import type { Username } from '../utils/username'; +import type { Username } from '../../utils/username'; /** * The user object represents either a registered user in the instance or a guest user. @@ -18,26 +18,39 @@ import type { Username } from '../utils/username'; */ export interface User { /** The unique id of the user for internal referencing */ - id: number; + [FieldNameUser.id]: number; /** The user's chosen username or null if it is a guest user */ - username: Username | null; + [FieldNameUser.username]: Username | null; /** The guest user's UUID or null if it is a registered user */ - guestUuid: string | null; + [FieldNameUser.guestUuid]: string | null; /** The user's chosen display name */ - displayName: string; + [FieldNameUser.displayName]: string | null; /** Timestamp when the user was created */ - createdAt: Date; + [FieldNameUser.createdAt]: Date; /** URL to the user's profile picture if present */ - photoUrl: string | null; + [FieldNameUser.photoUrl]: string | null; /** The user's email address if present */ - email: string | null; + [FieldNameUser.email]: string | null; /** The index which author style (e.g. color) should be used for this user */ - authorStyle: number; + [FieldNameUser.authorStyle]: number; } + +export enum FieldNameUser { + id = 'id', + username = 'username', + guestUuid = 'guest_uuid', + displayName = 'display_name', + createdAt = 'created_at', + photoUrl = 'photo_url', + email = 'email', + authorStyle = 'author_style', +} + +export const TableUser = 'user'; diff --git a/yarn.lock b/yarn.lock index 29e237e31..381b038ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2445,6 +2445,181 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/aix-ppc64@npm:0.25.1" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/android-arm64@npm:0.25.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/android-arm@npm:0.25.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/android-x64@npm:0.25.1" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/darwin-arm64@npm:0.25.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/darwin-x64@npm:0.25.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/freebsd-arm64@npm:0.25.1" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/freebsd-x64@npm:0.25.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/linux-arm64@npm:0.25.1" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/linux-arm@npm:0.25.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/linux-ia32@npm:0.25.1" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/linux-loong64@npm:0.25.1" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/linux-mips64el@npm:0.25.1" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/linux-ppc64@npm:0.25.1" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/linux-riscv64@npm:0.25.1" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/linux-s390x@npm:0.25.1" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/linux-x64@npm:0.25.1" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/netbsd-arm64@npm:0.25.1" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/netbsd-x64@npm:0.25.1" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/openbsd-arm64@npm:0.25.1" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/openbsd-x64@npm:0.25.1" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/sunos-x64@npm:0.25.1" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/win32-arm64@npm:0.25.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/win32-ia32@npm:0.25.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.25.1": + version: 0.25.1 + resolution: "@esbuild/win32-x64@npm:0.25.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.1.2, @eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": version: 4.4.0 resolution: "@eslint-community/eslint-utils@npm:4.4.0" @@ -2609,8 +2784,8 @@ __metadata: supertest: "npm:6.3.4" ts-jest: "npm:29.2.5" ts-mockery: "npm:1.2.0" - ts-node: "npm:11.0.0-beta.1" tsconfig-paths: "npm:4.2.0" + tsx: "npm:^4.19.3" typescript: "npm:5.6.3" uuid: "npm:11.0.5" ws: "npm:8.18.0" @@ -10241,6 +10416,92 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:~0.25.0": + version: 0.25.1 + resolution: "esbuild@npm:0.25.1" + dependencies: + "@esbuild/aix-ppc64": "npm:0.25.1" + "@esbuild/android-arm": "npm:0.25.1" + "@esbuild/android-arm64": "npm:0.25.1" + "@esbuild/android-x64": "npm:0.25.1" + "@esbuild/darwin-arm64": "npm:0.25.1" + "@esbuild/darwin-x64": "npm:0.25.1" + "@esbuild/freebsd-arm64": "npm:0.25.1" + "@esbuild/freebsd-x64": "npm:0.25.1" + "@esbuild/linux-arm": "npm:0.25.1" + "@esbuild/linux-arm64": "npm:0.25.1" + "@esbuild/linux-ia32": "npm:0.25.1" + "@esbuild/linux-loong64": "npm:0.25.1" + "@esbuild/linux-mips64el": "npm:0.25.1" + "@esbuild/linux-ppc64": "npm:0.25.1" + "@esbuild/linux-riscv64": "npm:0.25.1" + "@esbuild/linux-s390x": "npm:0.25.1" + "@esbuild/linux-x64": "npm:0.25.1" + "@esbuild/netbsd-arm64": "npm:0.25.1" + "@esbuild/netbsd-x64": "npm:0.25.1" + "@esbuild/openbsd-arm64": "npm:0.25.1" + "@esbuild/openbsd-x64": "npm:0.25.1" + "@esbuild/sunos-x64": "npm:0.25.1" + "@esbuild/win32-arm64": "npm:0.25.1" + "@esbuild/win32-ia32": "npm:0.25.1" + "@esbuild/win32-x64": "npm:0.25.1" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/80fca30dd0f21aec23fdfab34f0a8d5f55df5097dd7f475f2ab561d45662c32ee306f5649071cd1a0ba0614b164c48ca3dc3ee1551a4daf204b8af90e4d893f5 + languageName: node + linkType: hard + "escalade@npm:^3.1.1": version: 3.1.2 resolution: "escalade@npm:3.1.2" @@ -11462,7 +11723,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:^2.3.2, fsevents@npm:~2.3.2": +"fsevents@npm:^2.3.2, fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": version: 2.3.3 resolution: "fsevents@npm:2.3.3" dependencies: @@ -11472,7 +11733,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": +"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" dependencies: @@ -11577,6 +11838,15 @@ __metadata: languageName: node linkType: hard +"get-tsconfig@npm:^4.7.5": + version: 4.10.0 + resolution: "get-tsconfig@npm:4.10.0" + dependencies: + resolve-pkg-maps: "npm:^1.0.0" + checksum: 10c0/c9b5572c5118923c491c04285c73bd55b19e214992af957c502a3be0fc0043bb421386ffd45ca3433c0a7fba81221ca300479e8393960acf15d0ed4563f38a86 + languageName: node + linkType: hard + "get-tsconfig@npm:^4.8.1": version: 4.8.1 resolution: "get-tsconfig@npm:4.8.1" @@ -18531,6 +18801,22 @@ __metadata: languageName: node linkType: hard +"tsx@npm:^4.19.3": + version: 4.19.3 + resolution: "tsx@npm:4.19.3" + dependencies: + esbuild: "npm:~0.25.0" + fsevents: "npm:~2.3.3" + get-tsconfig: "npm:^4.7.5" + dependenciesMeta: + fsevents: + optional: true + bin: + tsx: dist/cli.mjs + checksum: 10c0/cacfb4cf1392ae10e8e4fe032ad26ccb07cd8a3b32e5a0da270d9c48d06ee74f743e4a84686cbc9d89b48032d59bbc56cd911e076f53cebe61dc24fa525ff790 + languageName: node + linkType: hard + "tunnel-agent@npm:^0.6.0": version: 0.6.0 resolution: "tunnel-agent@npm:0.6.0"