UNPKG

@lucidcms/core

Version:

The core of the Lucid CMS. It's responsible for spinning up the API and serving the CMS.

1 lines 13.1 kB
{"version":3,"file":"adapter-base.mjs","names":["constants"],"sources":["../../../src/libs/db/adapter-base.ts"],"sourcesContent":["import { type ColumnDataType, type ColumnDefinitionBuilder, sql } from \"kysely\";\nimport type { jsonArrayFrom } from \"kysely/helpers/sqlite\";\nimport { type Migration, Migrator } from \"kysely/migration\";\nimport constants from \"../../constants/constants.js\";\nimport { LucidError } from \"../../utils/errors/index.js\";\nimport { translate } from \"../i18n/index.js\";\nimport logger from \"../logger/index.js\";\nimport type { EnvironmentVariables } from \"../runtime/types.js\";\n// Migrations\nimport Migration00000001 from \"./migrations/00000001-locales.js\";\nimport Migration00000002 from \"./migrations/00000002-options.js\";\nimport Migration00000003 from \"./migrations/00000003-users-and-permissions.js\";\nimport Migration00000004 from \"./migrations/00000004-queues.js\";\nimport Migration00000005 from \"./migrations/00000005-emails.js\";\nimport Migration00000006 from \"./migrations/00000006-media.js\";\nimport Migration00000007 from \"./migrations/00000007-collections.js\";\nimport Migration00000008 from \"./migrations/00000008-integrations.js\";\nimport Migration00000009 from \"./migrations/00000009-share-links.js\";\nimport Migration00000010 from \"./migrations/00000010-alerts.js\";\nimport Migration00000011 from \"./migrations/00000011-ai-generations.js\";\nimport Migration00000012 from \"./migrations/00000012-preview-sessions.js\";\nimport type {\n\tDatabaseConfig,\n\tDatabaseConnection,\n\tExternalMigrationFn,\n\tInferredTable,\n\tKyselyDB,\n} from \"./types.js\";\n\nexport default abstract class DatabaseAdapter {\n\tadapter: string;\n\tprivate externalMigrations: Record<string, ExternalMigrationFn> = {};\n\tconstructor(adapter: string) {\n\t\tthis.adapter = adapter;\n\t}\n\t/**\n\t * Returns the list of migration names that cannot be rolled back.\n\t * If migrate:rollback attempts to rollback a core migration, it will exit the process.\n\t *\n\t * For the initial release, all of these migrations are required to be run. There is no\n\t * valid version prior to this release, and as such you should never be able to rollback\n\t * the database to a previous version.\n\t */\n\treadonly protectedMigrations = [\n\t\t\"00000001-locales\",\n\t\t\"00000002-options\",\n\t\t\"00000003-users-and-permissions\",\n\t\t\"00000004-queues\",\n\t\t\"00000005-emails\",\n\t\t\"00000006-media\",\n\t\t\"00000007-collections\",\n\t\t\"00000008-integrations\",\n\t\t\"00000009-share-link\",\n\t\t\"00000010-alerts\",\n\t\t\"00000011-ai-generations\",\n\t\t\"00000012-preview-sessions\",\n\t];\n\t/**\n\t * Creates an initialized live connection for the supplied runtime environment.\n\t */\n\tabstract connect(\n\t\tenv?: EnvironmentVariables,\n\t): DatabaseConnection | Promise<DatabaseConnection>;\n\t/**\n\t * Return your Kysely DB's adapters jsonArrayFrom helper that aggregates a subquery into a JSON array\n\t */\n\tabstract get jsonArrayFrom(): typeof jsonArrayFrom;\n\t/**\n\t * Configure the features your DB supports, default values and fallback data types\n\t */\n\tabstract get config(): DatabaseConfig;\n\t/**\n\t * Infers the database schema using the supplied connection or transaction.\n\t */\n\tabstract inferSchema(db: KyselyDB): Promise<InferredTable[]>;\n\t/**\n\t * Drops all tables in the database\n\t */\n\tabstract dropAllTables(connection: DatabaseConnection): Promise<void>;\n\t/**\n\t * Handles formatting of certain values based on the columns data type. This is used specifically for default values\n\t */\n\tabstract formatDefaultValue(type: ColumnDataType, value: unknown): unknown;\n\t/**\n\t * Handles formatting of certain values based on the columns data type\n\t * - booleans are returned as either a boolean or 1/0 depending on adapter support\n\t * - json is stringified\n\t */\n\tformatInsertValue<T>(type: ColumnDataType, value: unknown): T {\n\t\tif (value === null || value === undefined) return value as T;\n\n\t\tif (type === \"integer\" && typeof value === \"boolean\") {\n\t\t\tif (this.supports(\"boolean\")) return value as T;\n\t\t\treturn (value ? 1 : 0) as T;\n\t\t}\n\t\tif (type === \"boolean\" && typeof value === \"boolean\") {\n\t\t\tif (this.supports(\"boolean\")) return value as T;\n\t\t\treturn (value ? 1 : 0) as T;\n\t\t}\n\t\tif (type === \"jsonb\" || type === \"json\") {\n\t\t\ttry {\n\t\t\t\tif (typeof value === \"object\" && value !== null) {\n\t\t\t\t\treturn JSON.stringify(value) as T;\n\t\t\t\t}\n\t\t\t\treturn null as T;\n\t\t\t} catch (_) {\n\t\t\t\treturn null as T;\n\t\t\t}\n\t\t}\n\n\t\treturn value as T;\n\t}\n\t/**\n\t * A helper for returning supported column data types\n\t */\n\tgetDataType(\n\t\ttype: keyof DatabaseConfig[\"dataTypes\"],\n\t\t...args: unknown[]\n\t): ColumnDataType {\n\t\tconst dataType = this.config.dataTypes[type];\n\t\tif (typeof dataType === \"function\") {\n\t\t\t// @ts-expect-error\n\t\t\treturn dataType(...args);\n\t\t}\n\t\treturn dataType;\n\t}\n\t/**\n\t * A helper for extending a column definition based on auto increment support\n\t */\n\tprimaryKeyColumnBuilder(col: ColumnDefinitionBuilder) {\n\t\treturn this.supports(\"autoIncrement\")\n\t\t\t? col.primaryKey().autoIncrement()\n\t\t\t: col.primaryKey();\n\t}\n\t/**\n\t * A helper for feature support\n\t */\n\tsupports(key: keyof DatabaseConfig[\"support\"]) {\n\t\treturn this.config.support[key];\n\t}\n\t/**\n\t * A helper for accessing the config default values\n\t */\n\tgetDefault<\n\t\tT extends keyof DatabaseConfig[\"defaults\"],\n\t\tK extends keyof DatabaseConfig[\"defaults\"][T] | undefined = undefined,\n\t>(\n\t\ttype: T,\n\t\tkey?: K,\n\t): K extends keyof DatabaseConfig[\"defaults\"][T]\n\t\t? DatabaseConfig[\"defaults\"][T][K]\n\t\t: DatabaseConfig[\"defaults\"][T] {\n\t\tconst defaultValue = this.config.defaults[type];\n\t\treturn (\n\t\t\tkey ? defaultValue[key] : defaultValue\n\t\t) as K extends keyof DatabaseConfig[\"defaults\"][T]\n\t\t\t? DatabaseConfig[\"defaults\"][T][K]\n\t\t\t: DatabaseConfig[\"defaults\"][T];\n\t}\n\n\t/**\n\t * Registers external (plugin/project) migrations, replacing any previously\n\t * registered set. These are merged with the core migrations.\n\t */\n\tregisterExternalMigrations(migrations: Record<string, ExternalMigrationFn>) {\n\t\tfor (const name of Object.keys(migrations)) {\n\t\t\tif (!constants.db.externalMigrationNameRegex.test(name)) {\n\t\t\t\tthrow new LucidError({\n\t\t\t\t\tmessage: `External migration \"${name}\" must start with a 13 digit timestamp, eg. \"1751400000000-example\".`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tthis.externalMigrations = migrations;\n\t}\n\t/**\n\t * Runs all migrations that have not been ran yet. This doesnt include the generated migrations for collections\n\t */\n\tasync migrateToLatest(connection: DatabaseConnection) {\n\t\tconst migrations = this.migrations;\n\n\t\tconst migrator = new Migrator({\n\t\t\tdb: connection.client,\n\t\t\tprovider: {\n\t\t\t\tasync getMigrations() {\n\t\t\t\t\treturn migrations;\n\t\t\t\t},\n\t\t\t},\n\t\t\t//* required so new core migrations can run after external migrations have executed\n\t\t\tallowUnorderedMigrations: true,\n\t\t});\n\n\t\tconst { error, results } = await migrator.migrateToLatest();\n\n\t\tif (results) {\n\t\t\tfor (const it of results) {\n\t\t\t\tif (it.status === \"Success\") {\n\t\t\t\t\tlogger.debug({\n\t\t\t\t\t\tmessage: `\"${it.migrationName}\" was executed successfully`,\n\t\t\t\t\t\tscope: constants.logScopes.migrations,\n\t\t\t\t\t});\n\t\t\t\t} else if (it.status === \"Error\") {\n\t\t\t\t\tlogger.error({\n\t\t\t\t\t\tmessage: `failed to execute migration \"${it.migrationName}\"`,\n\t\t\t\t\t\tscope: constants.logScopes.migrations,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (error) {\n\t\t\tconst errorData =\n\t\t\t\ttypeof error === \"object\" &&\n\t\t\t\terror !== null &&\n\t\t\t\t\"errors\" in error &&\n\t\t\t\ttypeof error.errors === \"object\" &&\n\t\t\t\terror.errors !== null\n\t\t\t\t\t? (error.errors as Record<string, unknown>)\n\t\t\t\t\t: undefined;\n\n\t\t\tif (\n\t\t\t\terror instanceof Error &&\n\t\t\t\terror.message.includes(\"previously executed migration\") &&\n\t\t\t\terror.message.includes(\"is missing\")\n\t\t\t) {\n\t\t\t\tthrow new LucidError({\n\t\t\t\t\tmessage: `${error.message}. A migration that has already run is no longer registered - if you removed a plugin or migration file, restore it, or roll its migrations back before removing it.`,\n\t\t\t\t\tdata: errorData,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tthrow new LucidError({\n\t\t\t\tmessage:\n\t\t\t\t\terror instanceof Error\n\t\t\t\t\t\t? error?.message\n\t\t\t\t\t\t: translate(\"server:core.database.migrations.failed\"),\n\t\t\t\tdata: errorData,\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Checks if there are any pending migrations that need to be executed\n\t */\n\tasync needsMigration(db: KyselyDB): Promise<boolean> {\n\t\ttry {\n\t\t\tconst availableMigrations = Object.keys(this.migrations);\n\n\t\t\tconst executedMigrations = await sql<{ name: string }>`\n\t\t\t\tSELECT name FROM kysely_migration\n\t\t\t`.execute(db);\n\n\t\t\tconst executedMigrationNames = executedMigrations.rows.map(\n\t\t\t\t(row) => row.name,\n\t\t\t);\n\n\t\t\treturn availableMigrations.some(\n\t\t\t\t(migrationName) => !executedMigrationNames.includes(migrationName),\n\t\t\t);\n\t\t} catch (_) {\n\t\t\treturn true;\n\t\t}\n\t}\n\t/**\n\t * Returns the migrations for the database, including any registered external migrations.\n\t * Core names must always use a zero-padded numeric prefix - external names start with a\n\t * 13 digit timestamp so they can never clash and always sort (and run) after core migrations.\n\t */\n\tget migrations(): Record<string, Migration> {\n\t\tconst migrations: Record<string, Migration> = {\n\t\t\t\"00000001-locales\": Migration00000001(this),\n\t\t\t\"00000002-options\": Migration00000002(this),\n\t\t\t\"00000003-users-and-permissions\": Migration00000003(this),\n\t\t\t\"00000004-queues\": Migration00000004(this),\n\t\t\t\"00000005-emails\": Migration00000005(this),\n\t\t\t\"00000006-media\": Migration00000006(this),\n\t\t\t\"00000007-collections\": Migration00000007(this),\n\t\t\t\"00000008-integrations\": Migration00000008(this),\n\t\t\t\"00000009-share-link\": Migration00000009(this),\n\t\t\t\"00000010-alerts\": Migration00000010(this),\n\t\t\t\"00000011-ai-generations\": Migration00000011(this),\n\t\t\t\"00000012-preview-sessions\": Migration00000012(this),\n\t\t};\n\n\t\tfor (const [name, migrationFn] of Object.entries(this.externalMigrations)) {\n\t\t\tmigrations[name] = migrationFn({ adapter: this });\n\t\t}\n\n\t\treturn migrations;\n\t}\n}\n"],"mappings":"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"}