@nextlyhq/adapter-drizzle 0.0.2-alpha.65 → 0.0.2-alpha.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{adapter-C6ENLDau.d.cts → adapter-bUAaPzjV.d.cts} +307 -4
- package/dist/{adapter-D4GM1VAZ.d.ts → adapter-iJ5hbUvL.d.ts} +307 -4
- package/dist/index.cjs +570 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +571 -37
- package/dist/index.mjs.map +1 -1
- package/dist/{migration-R06fsTrz.d.ts → migration-BqHebSwM.d.ts} +65 -6
- package/dist/{migration-B6AmjCQ1.d.cts → migration-Bu0WLdX4.d.cts} +65 -6
- package/dist/migrations.d.cts +3 -3
- package/dist/migrations.d.ts +3 -3
- package/dist/schema-name.cjs +20 -0
- package/dist/schema-name.cjs.map +1 -0
- package/dist/schema-name.d.cts +24 -0
- package/dist/schema-name.d.ts +24 -0
- package/dist/schema-name.mjs +17 -0
- package/dist/schema-name.mjs.map +1 -0
- package/dist/text-match.cjs +16 -0
- package/dist/text-match.cjs.map +1 -0
- package/dist/text-match.d.cts +44 -0
- package/dist/text-match.d.ts +44 -0
- package/dist/text-match.mjs +13 -0
- package/dist/text-match.mjs.map +1 -0
- package/dist/types/index.cjs +7 -0
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.d.cts +16 -2
- package/dist/types/index.d.ts +16 -2
- package/dist/types/index.mjs +7 -0
- package/dist/types/index.mjs.map +1 -1
- package/dist/version-check.cjs +10 -1
- package/dist/version-check.cjs.map +1 -1
- package/dist/version-check.d.cts +18 -2
- package/dist/version-check.d.ts +18 -2
- package/dist/version-check.mjs +10 -2
- package/dist/version-check.mjs.map +1 -1
- package/package.json +14 -4
package/dist/types/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as SqlParam } from '../core-CVO7WYDj.js';
|
|
2
2
|
export { J as JsonArray, b as JsonObject, c as JsonValue, S as SupportedDialect, T as TableResolver } from '../core-CVO7WYDj.js';
|
|
3
|
-
export { C as CountOptions,
|
|
3
|
+
export { C as CountOptions, f as DatabaseCapabilities, D as DeleteOptions, I as InsertOptions, J as JoinSpec, a as Migration, c as MigrationOptions, M as MigrationRecord, d as MigrationResult, b as MigrationStatus, O as OrderBySpec, P as PoolStats, S as SelectOptions, T as TransactionContext, h as TransactionIsolationLevel, e as TransactionOptions, g as UpdateOptions, U as UpsertOptions, i as WHERE_OPERATORS, W as WhereClause, j as WhereCondition, k as WhereOperator } from '../migration-BqHebSwM.js';
|
|
4
4
|
export { A as AlterTableOperation, a as AlterTableOptions, b as ColumnDefinition, C as CreateTableOptions, D as DropTableOptions, I as IndexDefinition, c as TableConstraint, T as TableDefinition } from '../schema-BIQ0YQZ_.js';
|
|
5
5
|
export { a as DatabaseError, D as DatabaseErrorKind, b as DatabaseErrorOptions, c as createDatabaseError, i as isApplicationError, d as isDatabaseError } from '../error-BrdknH2s.js';
|
|
6
6
|
import 'drizzle-orm';
|
|
@@ -130,7 +130,21 @@ interface BaseAdapterConfig {
|
|
|
130
130
|
pool?: PoolConfig;
|
|
131
131
|
/** Logger instance for queries and errors */
|
|
132
132
|
logger?: AdapterLogger;
|
|
133
|
-
/**
|
|
133
|
+
/**
|
|
134
|
+
* Schema name (PostgreSQL only).
|
|
135
|
+
*
|
|
136
|
+
* Applied as `search_path` in the pool's startup options, which is the
|
|
137
|
+
* pooled-safe way to set it: a `SET search_path` issued as a query belongs
|
|
138
|
+
* to whichever pooled connection happened to run it, so the next checkout
|
|
139
|
+
* would see the default again.
|
|
140
|
+
*
|
|
141
|
+
* The adapter creates the schema if it is missing, because `search_path`
|
|
142
|
+
* naming a schema that does not exist does not fail — it falls through, and
|
|
143
|
+
* the tables land somewhere else.
|
|
144
|
+
*
|
|
145
|
+
* Declared here since the first release and read by nothing until now, which
|
|
146
|
+
* is why setting it appeared to work and changed nothing.
|
|
147
|
+
*/
|
|
134
148
|
schema?: string;
|
|
135
149
|
/** SSL/TLS configuration */
|
|
136
150
|
ssl?: SslConfig | boolean;
|
package/dist/types/index.mjs
CHANGED
|
@@ -21,6 +21,13 @@ var WHERE_OPERATORS = [
|
|
|
21
21
|
// coerce, and there it searches the serialized text — which hits a key name as readily as a
|
|
22
22
|
// value. Text columns only, and a search rather than a containment check.
|
|
23
23
|
"CONTAINS",
|
|
24
|
+
// CONTAINS without regard to case: a substring match on a TEXT column with the value taken
|
|
25
|
+
// literally (`%` and `_` are characters to find, not wildcards), ignoring case on every
|
|
26
|
+
// dialect even where the column's collation is case-sensitive. Which letters fold is the
|
|
27
|
+
// database's `lower()` under the column's collation: the collation's Unicode version on
|
|
28
|
+
// MySQL; non-ASCII too under a linguistic PostgreSQL collation; ASCII letters only on
|
|
29
|
+
// SQLite and under PostgreSQL's `C` or `POSIX`.
|
|
30
|
+
"ICONTAINS",
|
|
24
31
|
// Declared, never implemented — the builder throws `Unsupported operator: OVERLAPS`. Kept in
|
|
25
32
|
// the union because removing it would be a breaking change to a published type; the refusal
|
|
26
33
|
// is pinned by a test so implementing it has to come through here.
|
package/dist/types/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/types/query.ts","../../src/types/error.ts"],"names":[],"mappings":";AAoCO,IAAM,eAAA,GAAkB;AAAA,EAC7B,GAAA;AAAA,EACA,IAAA;AAAA,EACA,GAAA;AAAA,EACA,GAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,SAAA;AAAA,EACA,aAAA;AAAA,EACA,SAAA;AAAA,EACA,aAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAA;AAAA;AAAA;AAAA;AAAA,EAIA;AACF;;;ACyBO,SAAS,gBAAgB,KAAA,EAAwC;AACtE,EAAA,OACE,OAAO,UAAU,QAAA,IACjB,KAAA,KAAU,QACV,MAAA,IAAU,KAAA,IACV,OAAQ,KAAA,CAAwB,IAAA,KAAS,QAAA;AAE7C;AAYA,IAAM,uBAAA,GAAyC,MAAA,CAAO,GAAA,CAAI,oBAAoB,CAAA;AAkBvE,SAAS,mBAAmB,KAAA,EAAyB;AAC1D,EAAA,IACE,UAAU,IAAA,IACT,OAAO,UAAU,QAAA,IAAY,OAAO,UAAU,UAAA,EAC/C;AACA,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,OAAQ,KAAA,CAAkC,uBAAuB,CAAA,KAAM,IAAA;AACzE;AA+CO,SAAS,oBACd,OAAA,EACe;AACf,EAAA,MAAM,KAAA,GAAQ,IAAI,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA;AACvC,EAAA,KAAA,CAAM,IAAA,GAAO,eAAA;AACb,EAAA,KAAA,CAAM,OAAO,OAAA,CAAQ,IAAA;AAErB,EAAA,IAAI,OAAA,CAAQ,IAAA,KAAS,MAAA,EAAW,KAAA,CAAM,OAAO,OAAA,CAAQ,IAAA;AACrD,EAAA,IAAI,OAAA,CAAQ,UAAA,KAAe,MAAA,EAAW,KAAA,CAAM,aAAa,OAAA,CAAQ,UAAA;AACjE,EAAA,IAAI,OAAA,CAAQ,KAAA,KAAU,MAAA,EAAW,KAAA,CAAM,QAAQ,OAAA,CAAQ,KAAA;AACvD,EAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,MAAA,EAAW,KAAA,CAAM,SAAS,OAAA,CAAQ,MAAA;AACzD,EAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,MAAA,EAAW,KAAA,CAAM,SAAS,OAAA,CAAQ,MAAA;AACzD,EAAA,IAAI,OAAA,CAAQ,IAAA,KAAS,MAAA,EAAW,KAAA,CAAM,OAAO,OAAA,CAAQ,IAAA;AACrD,EAAA,IAAI,OAAA,CAAQ,KAAA,KAAU,MAAA,EAAW,KAAA,CAAM,QAAQ,OAAA,CAAQ,KAAA;AAEvD,EAAA,OAAO,KAAA;AACT","file":"index.mjs","sourcesContent":["/**\n * Query building type definitions for database-agnostic queries.\n *\n * @packageDocumentation\n */\n\nimport type { SqlParam } from \"./core\";\n\n/**\n * Every WHERE clause operator this package DECLARES, as a value.\n *\n * @remarks\n * Declared, not guaranteed executable. Membership here means a `WhereCondition`\n * carrying the operator type-checks — it does NOT mean the adapter can run it.\n * `OVERLAPS` is declared and unimplemented, and throws at build time, so a\n * validator written against this list will admit input the query layer then\n * rejects. Validate against it to catch a misspelled operator; do not read\n * acceptance here as a capability.\n *\n * - Standard comparison: =, !=, <, >, <=, >=\n * - Set operations: IN, NOT IN\n * - Pattern matching: LIKE, ILIKE (case-insensitive, PostgreSQL/emulated)\n * - NULL checks: IS NULL, IS NOT NULL\n * - Range: BETWEEN, NOT BETWEEN\n * - Substring: CONTAINS — a LITERAL match on a TEXT column; see below\n * - Declared but NOT implemented: OVERLAPS, which throws\n *\n * This list is the source of truth and {@link WhereOperator} is derived from it,\n * rather than the two being written out separately. A guard that has to enumerate\n * the operators — a validator over caller input, or a test asserting the builder\n * handles or refuses each one — can only do that from a runtime value, and a\n * hand-kept copy of the union would agree with it on the day it was written and\n * silently stop agreeing the next time a member is added.\n *\n * @public\n */\nexport const WHERE_OPERATORS = [\n \"=\",\n \"!=\",\n \"<\",\n \">\",\n \"<=\",\n \">=\",\n \"IN\",\n \"NOT IN\",\n \"LIKE\",\n \"ILIKE\",\n \"IS NULL\",\n \"IS NOT NULL\",\n \"BETWEEN\",\n \"NOT BETWEEN\",\n // Substring match on a TEXT column, with the value taken literally. Despite the name it is\n // not JSON containment, and it is not safe to point at a JSON column: the builder emits a\n // bare `LIKE` with no cast, so on PostgreSQL a `json`/`jsonb` column has no matching operator\n // and the statement ERRORS (`operator does not exist: jsonb ~~ text`). MySQL and SQLite\n // coerce, and there it searches the serialized text — which hits a key name as readily as a\n // value. Text columns only, and a search rather than a containment check.\n \"CONTAINS\",\n // Declared, never implemented — the builder throws `Unsupported operator: OVERLAPS`. Kept in\n // the union because removing it would be a breaking change to a published type; the refusal\n // is pinned by a test so implementing it has to come through here.\n \"OVERLAPS\",\n] as const;\n\n/**\n * Every WHERE clause operator this package declares. Derived from\n * {@link WHERE_OPERATORS}, and carrying the same caveat: a value of this type\n * type-checks, which is not the same as the adapter being able to execute it.\n *\n * @public\n */\nexport type WhereOperator = (typeof WHERE_OPERATORS)[number];\n\n/**\n * Individual WHERE condition.\n *\n * @remarks\n * Represents a single condition in a WHERE clause. For IS NULL and IS NOT NULL\n * operators, the value field is optional.\n *\n * @public\n */\nexport interface WhereCondition {\n /** Column name to filter on */\n column: string;\n\n /** Comparison operator */\n op: WhereOperator;\n\n /** Value(s) to compare against (optional for IS NULL/IS NOT NULL) */\n value?: SqlParam | SqlParam[];\n\n /** Second value for BETWEEN operator */\n valueTo?: SqlParam;\n}\n\n/**\n * Complex WHERE clause with logical operators.\n *\n * @remarks\n * Supports nested conditions with AND, OR, and NOT logical operators.\n * Can be recursively nested for complex queries.\n *\n * @example\n * ```typescript\n * const where: WhereClause = {\n * and: [\n * { column: \"status\", op: \"=\", value: \"published\" },\n * {\n * or: [\n * { column: \"author\", op: \"=\", value: \"john\" },\n * { column: \"author\", op: \"=\", value: \"jane\" }\n * ]\n * }\n * ]\n * };\n * ```\n *\n * @public\n */\nexport interface WhereClause {\n /** All conditions must be true (AND) */\n and?: (WhereCondition | WhereClause)[];\n\n /** At least one condition must be true (OR) */\n or?: (WhereCondition | WhereClause)[];\n\n /** Negate a condition (NOT) */\n not?: WhereCondition | WhereClause;\n}\n\n/**\n * ORDER BY specification for query results.\n *\n * @remarks\n * Controls the sorting of query results. NULL handling varies by database\n * but can be explicitly controlled with the nulls field.\n *\n * @public\n */\nexport interface OrderBySpec {\n /** Column name to sort by */\n column: string;\n\n /** Sort direction (default: asc) */\n direction?: \"asc\" | \"desc\";\n\n /** NULL value ordering (database-specific defaults vary) */\n nulls?: \"first\" | \"last\";\n}\n\n/**\n * JOIN specification for table joins.\n *\n * @remarks\n * Supports different types of JOINs. Note that complex joins may require\n * dialect-specific handling.\n *\n * @public\n */\nexport interface JoinSpec {\n /** Type of join */\n type: \"inner\" | \"left\" | \"right\" | \"full\";\n\n /** Table name to join */\n table: string;\n\n /** Join condition */\n on: {\n /** Column from the left table */\n leftColumn: string;\n /** Column from the right table */\n rightColumn: string;\n };\n\n /** Optional alias for the joined table */\n alias?: string;\n}\n","/**\n * Database error type definitions.\n *\n * @packageDocumentation\n */\n\n/**\n * Database error classification.\n *\n * @remarks\n * Categorizes database errors for consistent error handling across adapters.\n * Each adapter translates database-specific error codes to these kinds.\n *\n * @public\n */\nexport type DatabaseErrorKind =\n | \"connection\" // Connection/network errors\n | \"query\" // Syntax or execution errors\n | \"constraint\" // Generic constraint violation\n | \"unique_violation\" // Unique constraint violation\n | \"foreign_key_violation\" // Foreign key constraint violation\n | \"check_violation\" // Check constraint violation\n | \"not_null_violation\" // NOT NULL constraint violation\n | \"deadlock\" // Transaction deadlock\n | \"timeout\" // Query or connection timeout\n | \"serialization_failure\" // Serializable transaction conflict\n | \"unsupported_version\" // F17: DB version below minimum or unparseable at connect\n | \"unknown\"; // Unclassified error\n\n/**\n * Enhanced database error interface.\n *\n * @remarks\n * Extends the standard Error interface with database-specific context.\n * Adapters should throw errors implementing this interface for consistent\n * error handling.\n *\n * @example\n * ```typescript\n * try {\n * await adapter.insert('users', { email: 'duplicate@example.com' });\n * } catch (error) {\n * if (isDatabaseError(error) && error.kind === 'unique_violation') {\n * console.log(`Duplicate ${error.column} in ${error.table}`);\n * }\n * }\n * ```\n *\n * @public\n */\nexport interface DatabaseError extends Error {\n /** Error classification */\n kind: DatabaseErrorKind;\n\n /** Database-specific error code (e.g., \"23505\" for PostgreSQL unique violation) */\n code?: string;\n\n /** Constraint name that was violated (if applicable) */\n constraint?: string;\n\n /** Table name involved in the error */\n table?: string;\n\n /** Column name involved in the error */\n column?: string;\n\n /** Detailed error description from the database */\n detail?: string;\n\n /** Hint for resolving the error */\n hint?: string;\n\n /** Original error from the database driver */\n cause?: Error;\n}\n\n/**\n * Type guard for DatabaseError.\n *\n * @remarks\n * Checks if an error is a DatabaseError with proper typing.\n *\n * @param error - Error to check\n * @returns True if error is a DatabaseError\n *\n * @public\n */\nexport function isDatabaseError(error: unknown): error is DatabaseError {\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"kind\" in error &&\n typeof (error as DatabaseError).kind === \"string\"\n );\n}\n\n/**\n * The brand every Nextly application error carries.\n *\n * Read through the global symbol registry rather than imported, because the\n * adapters must not depend on the package that defines the error: `Symbol.for`\n * resolves to the same symbol in every module instance, which is why the brand\n * exists in that form to begin with.\n *\n * @internal\n */\nconst APPLICATION_ERROR_BRAND: unique symbol = Symbol.for(\"nextly/NextlyError\");\n\n/**\n * Whether an error is the application's verdict rather than the database's\n * failure.\n *\n * Work running inside a transaction may throw to roll the write back — a\n * refused value, a permission denial — and such an error is not something the\n * driver produced. Classifying it as a database error replaces its code and its\n * payload with a generic one, so a caller that asked for a refusal is handed an\n * unexplained failure instead, and per-field validation detail is lost on the\n * way out.\n *\n * @param error - Error to check\n * @returns True if the error was raised by application code\n *\n * @public\n */\nexport function isApplicationError(error: unknown): boolean {\n if (\n error === null ||\n (typeof error !== \"object\" && typeof error !== \"function\")\n ) {\n return false;\n }\n return (error as Record<symbol, unknown>)[APPLICATION_ERROR_BRAND] === true;\n}\n\n/**\n * Database error constructor options.\n *\n * @public\n */\nexport interface DatabaseErrorOptions {\n /** Error classification */\n kind: DatabaseErrorKind;\n\n /** Error message */\n message: string;\n\n /** Database-specific error code */\n code?: string;\n\n /** Constraint name */\n constraint?: string;\n\n /** Table name */\n table?: string;\n\n /** Column name */\n column?: string;\n\n /** Detailed description */\n detail?: string;\n\n /** Resolution hint */\n hint?: string;\n\n /** Original error */\n cause?: Error;\n}\n\n/**\n * Create a DatabaseError instance.\n *\n * @remarks\n * Helper function to create properly structured DatabaseError objects.\n *\n * @param options - Error options\n * @returns DatabaseError instance\n *\n * @public\n */\nexport function createDatabaseError(\n options: DatabaseErrorOptions\n): DatabaseError {\n const error = new Error(options.message) as DatabaseError;\n error.name = \"DatabaseError\";\n error.kind = options.kind;\n\n if (options.code !== undefined) error.code = options.code;\n if (options.constraint !== undefined) error.constraint = options.constraint;\n if (options.table !== undefined) error.table = options.table;\n if (options.column !== undefined) error.column = options.column;\n if (options.detail !== undefined) error.detail = options.detail;\n if (options.hint !== undefined) error.hint = options.hint;\n if (options.cause !== undefined) error.cause = options.cause;\n\n return error;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/types/query.ts","../../src/types/error.ts"],"names":[],"mappings":";AAoCO,IAAM,eAAA,GAAkB;AAAA,EAC7B,GAAA;AAAA,EACA,IAAA;AAAA,EACA,GAAA;AAAA,EACA,GAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,SAAA;AAAA,EACA,aAAA;AAAA,EACA,SAAA;AAAA,EACA,aAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,WAAA;AAAA;AAAA;AAAA;AAAA,EAIA;AACF;;;ACkBO,SAAS,gBAAgB,KAAA,EAAwC;AACtE,EAAA,OACE,OAAO,UAAU,QAAA,IACjB,KAAA,KAAU,QACV,MAAA,IAAU,KAAA,IACV,OAAQ,KAAA,CAAwB,IAAA,KAAS,QAAA;AAE7C;AAYA,IAAM,uBAAA,GAAyC,MAAA,CAAO,GAAA,CAAI,oBAAoB,CAAA;AAkBvE,SAAS,mBAAmB,KAAA,EAAyB;AAC1D,EAAA,IACE,UAAU,IAAA,IACT,OAAO,UAAU,QAAA,IAAY,OAAO,UAAU,UAAA,EAC/C;AACA,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,OAAQ,KAAA,CAAkC,uBAAuB,CAAA,KAAM,IAAA;AACzE;AA+CO,SAAS,oBACd,OAAA,EACe;AACf,EAAA,MAAM,KAAA,GAAQ,IAAI,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA;AACvC,EAAA,KAAA,CAAM,IAAA,GAAO,eAAA;AACb,EAAA,KAAA,CAAM,OAAO,OAAA,CAAQ,IAAA;AAErB,EAAA,IAAI,OAAA,CAAQ,IAAA,KAAS,MAAA,EAAW,KAAA,CAAM,OAAO,OAAA,CAAQ,IAAA;AACrD,EAAA,IAAI,OAAA,CAAQ,UAAA,KAAe,MAAA,EAAW,KAAA,CAAM,aAAa,OAAA,CAAQ,UAAA;AACjE,EAAA,IAAI,OAAA,CAAQ,KAAA,KAAU,MAAA,EAAW,KAAA,CAAM,QAAQ,OAAA,CAAQ,KAAA;AACvD,EAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,MAAA,EAAW,KAAA,CAAM,SAAS,OAAA,CAAQ,MAAA;AACzD,EAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,MAAA,EAAW,KAAA,CAAM,SAAS,OAAA,CAAQ,MAAA;AACzD,EAAA,IAAI,OAAA,CAAQ,IAAA,KAAS,MAAA,EAAW,KAAA,CAAM,OAAO,OAAA,CAAQ,IAAA;AACrD,EAAA,IAAI,OAAA,CAAQ,KAAA,KAAU,MAAA,EAAW,KAAA,CAAM,QAAQ,OAAA,CAAQ,KAAA;AAEvD,EAAA,OAAO,KAAA;AACT","file":"index.mjs","sourcesContent":["/**\n * Query building type definitions for database-agnostic queries.\n *\n * @packageDocumentation\n */\n\nimport type { SqlParam } from \"./core\";\n\n/**\n * Every WHERE clause operator this package DECLARES, as a value.\n *\n * @remarks\n * Declared, not guaranteed executable. Membership here means a `WhereCondition`\n * carrying the operator type-checks — it does NOT mean the adapter can run it.\n * `OVERLAPS` is declared and unimplemented, and throws at build time, so a\n * validator written against this list will admit input the query layer then\n * rejects. Validate against it to catch a misspelled operator; do not read\n * acceptance here as a capability.\n *\n * - Standard comparison: =, !=, <, >, <=, >=\n * - Set operations: IN, NOT IN\n * - Pattern matching: LIKE, ILIKE (case-insensitive, PostgreSQL/emulated)\n * - NULL checks: IS NULL, IS NOT NULL\n * - Range: BETWEEN, NOT BETWEEN\n * - Substring: CONTAINS — a LITERAL match on a TEXT column; see below\n * - Declared but NOT implemented: OVERLAPS, which throws\n *\n * This list is the source of truth and {@link WhereOperator} is derived from it,\n * rather than the two being written out separately. A guard that has to enumerate\n * the operators — a validator over caller input, or a test asserting the builder\n * handles or refuses each one — can only do that from a runtime value, and a\n * hand-kept copy of the union would agree with it on the day it was written and\n * silently stop agreeing the next time a member is added.\n *\n * @public\n */\nexport const WHERE_OPERATORS = [\n \"=\",\n \"!=\",\n \"<\",\n \">\",\n \"<=\",\n \">=\",\n \"IN\",\n \"NOT IN\",\n \"LIKE\",\n \"ILIKE\",\n \"IS NULL\",\n \"IS NOT NULL\",\n \"BETWEEN\",\n \"NOT BETWEEN\",\n // Substring match on a TEXT column, with the value taken literally. Despite the name it is\n // not JSON containment, and it is not safe to point at a JSON column: the builder emits a\n // bare `LIKE` with no cast, so on PostgreSQL a `json`/`jsonb` column has no matching operator\n // and the statement ERRORS (`operator does not exist: jsonb ~~ text`). MySQL and SQLite\n // coerce, and there it searches the serialized text — which hits a key name as readily as a\n // value. Text columns only, and a search rather than a containment check.\n \"CONTAINS\",\n // CONTAINS without regard to case: a substring match on a TEXT column with the value taken\n // literally (`%` and `_` are characters to find, not wildcards), ignoring case on every\n // dialect even where the column's collation is case-sensitive. Which letters fold is the\n // database's `lower()` under the column's collation: the collation's Unicode version on\n // MySQL; non-ASCII too under a linguistic PostgreSQL collation; ASCII letters only on\n // SQLite and under PostgreSQL's `C` or `POSIX`.\n \"ICONTAINS\",\n // Declared, never implemented — the builder throws `Unsupported operator: OVERLAPS`. Kept in\n // the union because removing it would be a breaking change to a published type; the refusal\n // is pinned by a test so implementing it has to come through here.\n \"OVERLAPS\",\n] as const;\n\n/**\n * Every WHERE clause operator this package declares. Derived from\n * {@link WHERE_OPERATORS}, and carrying the same caveat: a value of this type\n * type-checks, which is not the same as the adapter being able to execute it.\n *\n * @public\n */\nexport type WhereOperator = (typeof WHERE_OPERATORS)[number];\n\n/**\n * Individual WHERE condition.\n *\n * @remarks\n * Represents a single condition in a WHERE clause. For IS NULL and IS NOT NULL\n * operators, the value field is optional.\n *\n * @public\n */\nexport interface WhereCondition {\n /** Column name to filter on */\n column: string;\n\n /** Comparison operator */\n op: WhereOperator;\n\n /** Value(s) to compare against (optional for IS NULL/IS NOT NULL) */\n value?: SqlParam | SqlParam[];\n\n /** Second value for BETWEEN operator */\n valueTo?: SqlParam;\n}\n\n/**\n * Complex WHERE clause with logical operators.\n *\n * @remarks\n * Supports nested conditions with AND, OR, and NOT logical operators.\n * Can be recursively nested for complex queries.\n *\n * @example\n * ```typescript\n * const where: WhereClause = {\n * and: [\n * { column: \"status\", op: \"=\", value: \"published\" },\n * {\n * or: [\n * { column: \"author\", op: \"=\", value: \"john\" },\n * { column: \"author\", op: \"=\", value: \"jane\" }\n * ]\n * }\n * ]\n * };\n * ```\n *\n * @public\n */\nexport interface WhereClause {\n /** All conditions must be true (AND) */\n and?: (WhereCondition | WhereClause)[];\n\n /** At least one condition must be true (OR) */\n or?: (WhereCondition | WhereClause)[];\n\n /** Negate a condition (NOT) */\n not?: WhereCondition | WhereClause;\n}\n\n/**\n * ORDER BY specification for query results.\n *\n * @remarks\n * Controls the sorting of query results. NULL handling varies by database\n * but can be explicitly controlled with the nulls field.\n *\n * @public\n */\nexport interface OrderBySpec {\n /** Column name to sort by */\n column: string;\n\n /** Sort direction (default: asc) */\n direction?: \"asc\" | \"desc\";\n\n /** NULL value ordering (database-specific defaults vary) */\n nulls?: \"first\" | \"last\";\n}\n\n/**\n * JOIN specification for table joins.\n *\n * @remarks\n * Supports different types of JOINs. Note that complex joins may require\n * dialect-specific handling.\n *\n * @public\n */\nexport interface JoinSpec {\n /** Type of join */\n type: \"inner\" | \"left\" | \"right\" | \"full\";\n\n /** Table name to join */\n table: string;\n\n /** Join condition */\n on: {\n /** Column from the left table */\n leftColumn: string;\n /** Column from the right table */\n rightColumn: string;\n };\n\n /** Optional alias for the joined table */\n alias?: string;\n}\n","/**\n * Database error type definitions.\n *\n * @packageDocumentation\n */\n\n/**\n * Database error classification.\n *\n * @remarks\n * Categorizes database errors for consistent error handling across adapters.\n * Each adapter translates database-specific error codes to these kinds.\n *\n * @public\n */\nexport type DatabaseErrorKind =\n | \"connection\" // Connection/network errors\n | \"query\" // Syntax or execution errors\n | \"constraint\" // Generic constraint violation\n | \"unique_violation\" // Unique constraint violation\n | \"foreign_key_violation\" // Foreign key constraint violation\n | \"check_violation\" // Check constraint violation\n | \"not_null_violation\" // NOT NULL constraint violation\n | \"deadlock\" // Transaction deadlock\n | \"timeout\" // Query or connection timeout\n | \"serialization_failure\" // Serializable transaction conflict\n | \"unsupported_version\" // F17: DB version below minimum or unparseable at connect\n | \"unknown\"; // Unclassified error\n\n/**\n * Enhanced database error interface.\n *\n * @remarks\n * Extends the standard Error interface with database-specific context.\n * Adapters should throw errors implementing this interface for consistent\n * error handling.\n *\n * @example\n * ```typescript\n * try {\n * await adapter.insert('users', { email: 'duplicate@example.com' });\n * } catch (error) {\n * if (isDatabaseError(error) && error.kind === 'unique_violation') {\n * console.log(`Duplicate ${error.column} in ${error.table}`);\n * }\n * }\n * ```\n *\n * @public\n */\nexport interface DatabaseError extends Error {\n /** Error classification */\n kind: DatabaseErrorKind;\n\n /** Database-specific error code (e.g., \"23505\" for PostgreSQL unique violation) */\n code?: string;\n\n /** Constraint name that was violated (if applicable) */\n constraint?: string;\n\n /** Table name involved in the error */\n table?: string;\n\n /** Column name involved in the error */\n column?: string;\n\n /** Detailed error description from the database */\n detail?: string;\n\n /** Hint for resolving the error */\n hint?: string;\n\n /** Original error from the database driver */\n cause?: Error;\n}\n\n/**\n * Type guard for DatabaseError.\n *\n * @remarks\n * Checks if an error is a DatabaseError with proper typing.\n *\n * @param error - Error to check\n * @returns True if error is a DatabaseError\n *\n * @public\n */\nexport function isDatabaseError(error: unknown): error is DatabaseError {\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"kind\" in error &&\n typeof (error as DatabaseError).kind === \"string\"\n );\n}\n\n/**\n * The brand every Nextly application error carries.\n *\n * Read through the global symbol registry rather than imported, because the\n * adapters must not depend on the package that defines the error: `Symbol.for`\n * resolves to the same symbol in every module instance, which is why the brand\n * exists in that form to begin with.\n *\n * @internal\n */\nconst APPLICATION_ERROR_BRAND: unique symbol = Symbol.for(\"nextly/NextlyError\");\n\n/**\n * Whether an error is the application's verdict rather than the database's\n * failure.\n *\n * Work running inside a transaction may throw to roll the write back — a\n * refused value, a permission denial — and such an error is not something the\n * driver produced. Classifying it as a database error replaces its code and its\n * payload with a generic one, so a caller that asked for a refusal is handed an\n * unexplained failure instead, and per-field validation detail is lost on the\n * way out.\n *\n * @param error - Error to check\n * @returns True if the error was raised by application code\n *\n * @public\n */\nexport function isApplicationError(error: unknown): boolean {\n if (\n error === null ||\n (typeof error !== \"object\" && typeof error !== \"function\")\n ) {\n return false;\n }\n return (error as Record<symbol, unknown>)[APPLICATION_ERROR_BRAND] === true;\n}\n\n/**\n * Database error constructor options.\n *\n * @public\n */\nexport interface DatabaseErrorOptions {\n /** Error classification */\n kind: DatabaseErrorKind;\n\n /** Error message */\n message: string;\n\n /** Database-specific error code */\n code?: string;\n\n /** Constraint name */\n constraint?: string;\n\n /** Table name */\n table?: string;\n\n /** Column name */\n column?: string;\n\n /** Detailed description */\n detail?: string;\n\n /** Resolution hint */\n hint?: string;\n\n /** Original error */\n cause?: Error;\n}\n\n/**\n * Create a DatabaseError instance.\n *\n * @remarks\n * Helper function to create properly structured DatabaseError objects.\n *\n * @param options - Error options\n * @returns DatabaseError instance\n *\n * @public\n */\nexport function createDatabaseError(\n options: DatabaseErrorOptions\n): DatabaseError {\n const error = new Error(options.message) as DatabaseError;\n error.name = \"DatabaseError\";\n error.kind = options.kind;\n\n if (options.code !== undefined) error.code = options.code;\n if (options.constraint !== undefined) error.constraint = options.constraint;\n if (options.table !== undefined) error.table = options.table;\n if (options.column !== undefined) error.column = options.column;\n if (options.detail !== undefined) error.detail = options.detail;\n if (options.hint !== undefined) error.hint = options.hint;\n if (options.cause !== undefined) error.cause = options.cause;\n\n return error;\n}\n"]}
|
package/dist/version-check.cjs
CHANGED
|
@@ -29,6 +29,13 @@ var VARIANT_TOKENS = [
|
|
|
29
29
|
"planetscale",
|
|
30
30
|
"vitess"
|
|
31
31
|
];
|
|
32
|
+
var VARIANTS_WITHOUT_FOR_SHARE = /* @__PURE__ */ new Set([
|
|
33
|
+
"mariadb",
|
|
34
|
+
"tidb"
|
|
35
|
+
]);
|
|
36
|
+
function acceptsForShare(variant) {
|
|
37
|
+
return variant === null || !VARIANTS_WITHOUT_FOR_SHARE.has(variant);
|
|
38
|
+
}
|
|
32
39
|
var POSTGRES_REGEX = /^PostgreSQL (\d+)\.(\d+)/;
|
|
33
40
|
var MYSQL_REGEX = /^(\d+)\.(\d+)\.(\d+)/;
|
|
34
41
|
var SQLITE_REGEX = /^(\d+)\.(\d+)\.(\d+)/;
|
|
@@ -74,7 +81,7 @@ async function checkDialectVersion(client, dialect, options) {
|
|
|
74
81
|
if (variant) {
|
|
75
82
|
const warning = `Detected ${variant} ('${raw}'). Nextly is regression-tested against real MySQL 8.0+ only; this MySQL-compatible variant is not officially supported in v1 but most operations should work. See ${DOCS_URL}.`;
|
|
76
83
|
if (options?.onWarning) options.onWarning(warning);
|
|
77
|
-
return;
|
|
84
|
+
return { variant };
|
|
78
85
|
}
|
|
79
86
|
let parsed;
|
|
80
87
|
if (dialect === "postgresql") parsed = parsePostgres(raw);
|
|
@@ -101,6 +108,7 @@ async function checkDialectVersion(client, dialect, options) {
|
|
|
101
108
|
message: `${labels[dialect]} ${requiredStr} required; detected ${parsed.major}.${parsed.minor}. See ${DOCS_URL}.`
|
|
102
109
|
});
|
|
103
110
|
}
|
|
111
|
+
return { variant: null };
|
|
104
112
|
}
|
|
105
113
|
async function queryPostgresVersion(client) {
|
|
106
114
|
if (!client.query) {
|
|
@@ -149,6 +157,7 @@ function querySqliteVersion(client) {
|
|
|
149
157
|
|
|
150
158
|
exports.NEXTLY_MIN_DB_VERSIONS = NEXTLY_MIN_DB_VERSIONS;
|
|
151
159
|
exports.UnsupportedDialectVersionError = UnsupportedDialectVersionError;
|
|
160
|
+
exports.acceptsForShare = acceptsForShare;
|
|
152
161
|
exports.checkDialectVersion = checkDialectVersion;
|
|
153
162
|
//# sourceMappingURL=version-check.cjs.map
|
|
154
163
|
//# sourceMappingURL=version-check.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/version-check.ts"],"names":[],"mappings":"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","file":"version-check.cjs","sourcesContent":["// What: shared connect-time DB version check.\n// Why: each dialect has version-dependent capabilities (RENAME COLUMN on\n// SQLite 3.25+, transactional DDL on MySQL 8+, etc). This helper hard-fails\n// at connect on real dialects below minimum, lets recognized cloud variants\n// (MariaDB, TiDB, Aurora, PlanetScale, Vitess) proceed with a warning, and\n// hard-fails on completely unparseable strings. F17 in the schema\n// architecture plan.\n\n// Why local imports only: adapter-drizzle is upstream of nextly\n// in the dep graph. Importing from nextly here would create a\n// circular dependency. SupportedDialect and DatabaseError already exist\n// in adapter-drizzle's own type system.\nimport type { SupportedDialect } from \"./types\";\nimport type { DatabaseError } from \"./types/error\";\n\n// Why these minimums: PG 15 ships native MERGE + nbtree dedup needed for\n// our schema rename pipeline. MySQL 8.0 ships native RENAME COLUMN.\n// SQLite 3.38 ships strict tables + native unixepoch(). Older versions\n// would force fallback paths that v1 explicitly does not implement.\nexport const NEXTLY_MIN_DB_VERSIONS = {\n postgresql: { major: 15, minor: 0 },\n mysql: { major: 8, minor: 0 },\n sqlite: { major: 3, minor: 38 },\n} as const;\n\n// What: typed error thrown when a real DB version is below minimum or a\n// version string cannot be parsed.\n// Why: implements adapter-drizzle's DatabaseError interface so existing\n// isDatabaseError() type guards work. Stores dialect + detected/required\n// versions on the instance so callers can build upgrade-guidance UI from\n// the error fields without parsing the message.\nexport class UnsupportedDialectVersionError\n extends Error\n implements DatabaseError\n{\n public readonly kind = \"unsupported_version\" as const;\n public readonly dialect: SupportedDialect;\n public readonly detectedVersion: string;\n public readonly requiredVersion: string;\n public override readonly cause?: Error;\n\n constructor(args: {\n dialect: SupportedDialect;\n detectedVersion: string;\n requiredVersion: string;\n message: string;\n cause?: Error;\n }) {\n super(args.message);\n this.name = \"UnsupportedDialectVersionError\";\n this.dialect = args.dialect;\n this.detectedVersion = args.detectedVersion;\n this.requiredVersion = args.requiredVersion;\n this.cause = args.cause;\n }\n}\n\n// What: minimal duck-typed query interface so this helper does not pull in\n// pg / mysql2 / better-sqlite3 types directly.\n// Why: keeps adapter-drizzle dependency-free of the per-dialect drivers.\n// query() returns unknown because Promise<unknown> is already a subtype of\n// unknown; the helper awaits at the call site, which works for both sync\n// and async return shapes.\nexport interface VersionQueryClient {\n query?: (sql: string) => unknown;\n prepare?: (sql: string) => { get: () => unknown };\n}\n\n// What: optional callback so adapters can route the variant warning through\n// their own logger.\n// Why: the helper itself does not depend on a logger module; the adapter\n// owns logger config and is the right place to surface the warning.\nexport interface CheckDialectVersionOptions {\n onWarning?: (message: string) => void;\n}\n\n// What: doc URL embedded in error messages.\n// Why: gives users a single landing page with upgrade instructions per\n// dialect + cloud provider. Filled in PR-2.\nconst DOCS_URL = \"https://nextlyhq.com/docs/database/support\";\n\n// What: recognized MySQL-protocol-compatible variants. Detection of any of\n// these tokens in the version string causes a warning instead of a hard-\n// fail.\n// Why: these databases speak the MySQL wire protocol but use their own\n// version schemes (MariaDB 10.x, TiDB 6.x advertising MySQL 5.7, Aurora's\n// custom suffix). Hard-failing them would lock out a large fraction of\n// real cloud users on first install. We warn so the user knows we have not\n// regression-tested against their variant; we proceed because the wire\n// protocol is compatible enough that most operations work.\nconst VARIANT_TOKENS = [\n \"mariadb\",\n \"tidb\",\n \"aurora\",\n \"planetscale\",\n \"vitess\",\n] as const;\n\nconst POSTGRES_REGEX = /^PostgreSQL (\\d+)\\.(\\d+)/;\nconst MYSQL_REGEX = /^(\\d+)\\.(\\d+)\\.(\\d+)/;\nconst SQLITE_REGEX = /^(\\d+)\\.(\\d+)\\.(\\d+)/;\n\ninterface ParsedVersion {\n major: number;\n minor: number;\n}\n\nfunction parsePostgres(raw: string): ParsedVersion | null {\n const match = POSTGRES_REGEX.exec(raw);\n if (!match) return null;\n return { major: Number(match[1]), minor: Number(match[2]) };\n}\n\nfunction parseMySQL(raw: string): ParsedVersion | null {\n const match = MYSQL_REGEX.exec(raw);\n if (!match) return null;\n return { major: Number(match[1]), minor: Number(match[2]) };\n}\n\nfunction parseSQLite(raw: string): ParsedVersion | null {\n const match = SQLITE_REGEX.exec(raw);\n if (!match) return null;\n return { major: Number(match[1]), minor: Number(match[2]) };\n}\n\nfunction detectVariant(raw: string): string | null {\n const lower = raw.toLowerCase();\n for (const token of VARIANT_TOKENS) {\n if (lower.includes(token)) return token;\n }\n return null;\n}\n\n// What: compares detected version to required minimum.\n// Why: simple major-minor comparison is sufficient; we do not gate on patch.\nfunction meetsMinimum(\n detected: ParsedVersion,\n required: { major: number; minor: number }\n): boolean {\n if (detected.major > required.major) return true;\n if (detected.major < required.major) return false;\n return detected.minor >= required.minor;\n}\n\n// What: queries the DB for its version string and validates it under the\n// hybrid policy.\n// Why: called as the first DB-content step inside each adapter's connect()\n// method.\n//\n// Decision tree:\n// 1. Variant token present (MariaDB/TiDB/Aurora/PlanetScale/Vitess)?\n// -> emit warning via options.onWarning, return successfully.\n// 2. Strict regex matches and version >= minimum?\n// -> return successfully.\n// 3. Strict regex matches but version < minimum?\n// -> throw UnsupportedDialectVersionError (real-DB-too-old path).\n// 4. Strict regex does not match AND no variant token detected?\n// -> throw UnsupportedDialectVersionError (truly unparseable path).\nexport async function checkDialectVersion(\n client: VersionQueryClient,\n dialect: SupportedDialect,\n options?: CheckDialectVersionOptions\n): Promise<void> {\n const required = NEXTLY_MIN_DB_VERSIONS[dialect];\n const requiredStr = `${required.major}.${required.minor}+`;\n\n let raw: string;\n if (dialect === \"sqlite\") {\n raw = await querySqliteVersion(client);\n } else if (dialect === \"postgresql\") {\n raw = await queryPostgresVersion(client);\n } else {\n raw = await queryMysqlVersion(client);\n }\n\n // Step 1: variant check. Only meaningful for MySQL — Postgres-flavored\n // services (Neon, Supabase, RDS PG, Aurora PG) all return canonical\n // \"PostgreSQL X.Y...\" strings and pass the strict regex naturally.\n const variant = dialect === \"mysql\" ? detectVariant(raw) : null;\n if (variant) {\n const warning =\n `Detected ${variant} ('${raw}'). Nextly is regression-tested ` +\n `against real MySQL 8.0+ only; this MySQL-compatible variant is not ` +\n `officially supported in v1 but most operations should work. ` +\n `See ${DOCS_URL}.`;\n if (options?.onWarning) options.onWarning(warning);\n return;\n }\n\n // Step 2/3: strict regex parse for real dialects.\n let parsed: ParsedVersion | null;\n if (dialect === \"postgresql\") parsed = parsePostgres(raw);\n else if (dialect === \"mysql\") parsed = parseMySQL(raw);\n else parsed = parseSQLite(raw);\n\n if (!parsed) {\n // Step 4: truly unparseable. Hard-fail so we surface the issue at boot\n // rather than silently letting the user proceed and hit cryptic errors\n // later.\n throw new UnsupportedDialectVersionError({\n dialect,\n detectedVersion: raw,\n requiredVersion: requiredStr,\n message:\n `Could not parse ${dialect} version from '${raw}'. ` +\n `Nextly requires ${dialect} ${requiredStr}. ` +\n `If you are on a known MySQL-compatible variant we did not detect, ` +\n `please file an issue. See ${DOCS_URL}.`,\n });\n }\n\n if (!meetsMinimum(parsed, required)) {\n const labels: Record<SupportedDialect, string> = {\n postgresql: \"PostgreSQL\",\n mysql: \"MySQL\",\n sqlite: \"SQLite\",\n };\n throw new UnsupportedDialectVersionError({\n dialect,\n detectedVersion: `${parsed.major}.${parsed.minor}`,\n requiredVersion: requiredStr,\n message:\n `${labels[dialect]} ${requiredStr} required; detected ` +\n `${parsed.major}.${parsed.minor}. See ${DOCS_URL}.`,\n });\n }\n}\n\n// What: PG version query.\n// Why: pg's Client/PoolClient.query() returns { rows: [{ version: \"...\" }] }.\nasync function queryPostgresVersion(\n client: VersionQueryClient\n): Promise<string> {\n if (!client.query) {\n throw new Error(\"PostgreSQL client missing query() method\");\n }\n const result = (await client.query(\"SELECT version()\")) as {\n rows?: Array<{ version?: unknown }>;\n };\n const row = result.rows?.[0];\n const version = row?.version;\n if (typeof version !== \"string\") {\n throw new Error(\n `Unexpected response shape from SELECT version(): ${JSON.stringify(result)}`\n );\n }\n return version;\n}\n\n// What: MySQL version query.\n// Why: mysql2's Connection.query() returns [rows, fields] where rows[0]\n// has the column aliased as 'version'.\nasync function queryMysqlVersion(client: VersionQueryClient): Promise<string> {\n if (!client.query) {\n throw new Error(\"MySQL client missing query() method\");\n }\n const result = (await client.query(\"SELECT VERSION() AS version\")) as Array<\n Array<{ version?: unknown }>\n >;\n const row = result?.[0]?.[0];\n const version = row?.version;\n if (typeof version !== \"string\") {\n throw new Error(\n `Unexpected response shape from SELECT VERSION(): ${JSON.stringify(result)}`\n );\n }\n return version;\n}\n\n// What: SQLite version query via better-sqlite3's prepare/get.\n// Why: better-sqlite3 is synchronous, so this is a plain function returning\n// a resolved Promise. The helper awaits it uniformly with PG/MySQL paths.\nfunction querySqliteVersion(client: VersionQueryClient): Promise<string> {\n if (!client.prepare) {\n return Promise.reject(new Error(\"SQLite client missing prepare() method\"));\n }\n const stmt = client.prepare(\"SELECT sqlite_version() AS version\");\n const row = stmt.get() as { version?: unknown } | undefined;\n const version = row?.version;\n if (typeof version !== \"string\") {\n return Promise.reject(\n new Error(\n `Unexpected response shape from SELECT sqlite_version(): ${JSON.stringify(row)}`\n )\n );\n }\n return Promise.resolve(version);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/version-check.ts"],"names":[],"mappings":"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","file":"version-check.cjs","sourcesContent":["// What: shared connect-time DB version check.\n// Why: each dialect has version-dependent capabilities (RENAME COLUMN on\n// SQLite 3.25+, transactional DDL on MySQL 8+, etc). This helper hard-fails\n// at connect on real dialects below minimum, lets recognized cloud variants\n// (MariaDB, TiDB, Aurora, PlanetScale, Vitess) proceed with a warning, and\n// hard-fails on completely unparseable strings. F17 in the schema\n// architecture plan.\n\n// Why local imports only: adapter-drizzle is upstream of nextly\n// in the dep graph. Importing from nextly here would create a\n// circular dependency. SupportedDialect and DatabaseError already exist\n// in adapter-drizzle's own type system.\nimport type { SupportedDialect } from \"./types\";\nimport type { DatabaseError } from \"./types/error\";\n\n// Why these minimums: PG 15 ships native MERGE + nbtree dedup needed for\n// our schema rename pipeline. MySQL 8.0 ships native RENAME COLUMN.\n// SQLite 3.38 ships strict tables + native unixepoch(). Older versions\n// would force fallback paths that v1 explicitly does not implement.\nexport const NEXTLY_MIN_DB_VERSIONS = {\n postgresql: { major: 15, minor: 0 },\n mysql: { major: 8, minor: 0 },\n sqlite: { major: 3, minor: 38 },\n} as const;\n\n// What: typed error thrown when a real DB version is below minimum or a\n// version string cannot be parsed.\n// Why: implements adapter-drizzle's DatabaseError interface so existing\n// isDatabaseError() type guards work. Stores dialect + detected/required\n// versions on the instance so callers can build upgrade-guidance UI from\n// the error fields without parsing the message.\nexport class UnsupportedDialectVersionError\n extends Error\n implements DatabaseError\n{\n public readonly kind = \"unsupported_version\" as const;\n public readonly dialect: SupportedDialect;\n public readonly detectedVersion: string;\n public readonly requiredVersion: string;\n public override readonly cause?: Error;\n\n constructor(args: {\n dialect: SupportedDialect;\n detectedVersion: string;\n requiredVersion: string;\n message: string;\n cause?: Error;\n }) {\n super(args.message);\n this.name = \"UnsupportedDialectVersionError\";\n this.dialect = args.dialect;\n this.detectedVersion = args.detectedVersion;\n this.requiredVersion = args.requiredVersion;\n this.cause = args.cause;\n }\n}\n\n// What: minimal duck-typed query interface so this helper does not pull in\n// pg / mysql2 / better-sqlite3 types directly.\n// Why: keeps adapter-drizzle dependency-free of the per-dialect drivers.\n// query() returns unknown because Promise<unknown> is already a subtype of\n// unknown; the helper awaits at the call site, which works for both sync\n// and async return shapes.\nexport interface VersionQueryClient {\n query?: (sql: string) => unknown;\n prepare?: (sql: string) => { get: () => unknown };\n}\n\n// What: optional callback so adapters can route the variant warning through\n// their own logger.\n// Why: the helper itself does not depend on a logger module; the adapter\n// owns logger config and is the right place to surface the warning.\nexport interface CheckDialectVersionOptions {\n onWarning?: (message: string) => void;\n}\n\n// What: doc URL embedded in error messages.\n// Why: gives users a single landing page with upgrade instructions per\n// dialect + cloud provider. Filled in PR-2.\nconst DOCS_URL = \"https://nextlyhq.com/docs/database/support\";\n\n// What: recognized MySQL-protocol-compatible variants. Detection of any of\n// these tokens in the version string causes a warning instead of a hard-\n// fail.\n// Why: these databases speak the MySQL wire protocol but use their own\n// version schemes (MariaDB 10.x, TiDB 6.x advertising MySQL 5.7, Aurora's\n// custom suffix). Hard-failing them would lock out a large fraction of\n// real cloud users on first install. We warn so the user knows we have not\n// regression-tested against their variant; we proceed because the wire\n// protocol is compatible enough that most operations work.\nconst VARIANT_TOKENS = [\n \"mariadb\",\n \"tidb\",\n \"aurora\",\n \"planetscale\",\n \"vitess\",\n] as const;\n\n/** A MySQL-compatible server the version string names. */\nexport type DialectVariant = (typeof VARIANT_TOKENS)[number];\n\n/** What the version check learned about the server it connected to. */\nexport interface DialectVersionInfo {\n /**\n * The MySQL-compatible variant the version string names, or null for the\n * dialect's own server (and always null on PostgreSQL and SQLite).\n */\n variant: DialectVariant | null;\n}\n\n// What: the variants that reject `SELECT ... FOR SHARE` as a syntax error.\n// Why: MariaDB spells the shared lock `LOCK IN SHARE MODE` only, and TiDB\n// refuses both spellings unless its no-op functions are switched on. MySQL\n// 8, Aurora MySQL and Vitess/PlanetScale accept `FOR SHARE`.\nconst VARIANTS_WITHOUT_FOR_SHARE: ReadonlySet<DialectVariant> = new Set([\n \"mariadb\",\n \"tidb\",\n]);\n\n/**\n * Whether a MySQL-dialect server accepts `SELECT ... FOR SHARE`: true for\n * MySQL itself and the variants that do, false for MariaDB and TiDB.\n */\nexport function acceptsForShare(variant: DialectVariant | null): boolean {\n return variant === null || !VARIANTS_WITHOUT_FOR_SHARE.has(variant);\n}\n\nconst POSTGRES_REGEX = /^PostgreSQL (\\d+)\\.(\\d+)/;\nconst MYSQL_REGEX = /^(\\d+)\\.(\\d+)\\.(\\d+)/;\nconst SQLITE_REGEX = /^(\\d+)\\.(\\d+)\\.(\\d+)/;\n\ninterface ParsedVersion {\n major: number;\n minor: number;\n}\n\nfunction parsePostgres(raw: string): ParsedVersion | null {\n const match = POSTGRES_REGEX.exec(raw);\n if (!match) return null;\n return { major: Number(match[1]), minor: Number(match[2]) };\n}\n\nfunction parseMySQL(raw: string): ParsedVersion | null {\n const match = MYSQL_REGEX.exec(raw);\n if (!match) return null;\n return { major: Number(match[1]), minor: Number(match[2]) };\n}\n\nfunction parseSQLite(raw: string): ParsedVersion | null {\n const match = SQLITE_REGEX.exec(raw);\n if (!match) return null;\n return { major: Number(match[1]), minor: Number(match[2]) };\n}\n\nfunction detectVariant(raw: string): DialectVariant | null {\n const lower = raw.toLowerCase();\n for (const token of VARIANT_TOKENS) {\n if (lower.includes(token)) return token;\n }\n return null;\n}\n\n// What: compares detected version to required minimum.\n// Why: simple major-minor comparison is sufficient; we do not gate on patch.\nfunction meetsMinimum(\n detected: ParsedVersion,\n required: { major: number; minor: number }\n): boolean {\n if (detected.major > required.major) return true;\n if (detected.major < required.major) return false;\n return detected.minor >= required.minor;\n}\n\n// What: queries the DB for its version string and validates it under the\n// hybrid policy.\n// Why: called as the first DB-content step inside each adapter's connect()\n// method.\n//\n// Decision tree:\n// 1. Variant token present (MariaDB/TiDB/Aurora/PlanetScale/Vitess)?\n// -> emit warning via options.onWarning, return the variant.\n// 2. Strict regex matches and version >= minimum?\n// -> return successfully.\n// 3. Strict regex matches but version < minimum?\n// -> throw UnsupportedDialectVersionError (real-DB-too-old path).\n// 4. Strict regex does not match AND no variant token detected?\n// -> throw UnsupportedDialectVersionError (truly unparseable path).\nexport async function checkDialectVersion(\n client: VersionQueryClient,\n dialect: SupportedDialect,\n options?: CheckDialectVersionOptions\n): Promise<DialectVersionInfo> {\n const required = NEXTLY_MIN_DB_VERSIONS[dialect];\n const requiredStr = `${required.major}.${required.minor}+`;\n\n let raw: string;\n if (dialect === \"sqlite\") {\n raw = await querySqliteVersion(client);\n } else if (dialect === \"postgresql\") {\n raw = await queryPostgresVersion(client);\n } else {\n raw = await queryMysqlVersion(client);\n }\n\n // Step 1: variant check. Only meaningful for MySQL — Postgres-flavored\n // services (Neon, Supabase, RDS PG, Aurora PG) all return canonical\n // \"PostgreSQL X.Y...\" strings and pass the strict regex naturally.\n const variant = dialect === \"mysql\" ? detectVariant(raw) : null;\n if (variant) {\n const warning =\n `Detected ${variant} ('${raw}'). Nextly is regression-tested ` +\n `against real MySQL 8.0+ only; this MySQL-compatible variant is not ` +\n `officially supported in v1 but most operations should work. ` +\n `See ${DOCS_URL}.`;\n if (options?.onWarning) options.onWarning(warning);\n return { variant };\n }\n\n // Step 2/3: strict regex parse for real dialects.\n let parsed: ParsedVersion | null;\n if (dialect === \"postgresql\") parsed = parsePostgres(raw);\n else if (dialect === \"mysql\") parsed = parseMySQL(raw);\n else parsed = parseSQLite(raw);\n\n if (!parsed) {\n // Step 4: truly unparseable. Hard-fail so we surface the issue at boot\n // rather than silently letting the user proceed and hit cryptic errors\n // later.\n throw new UnsupportedDialectVersionError({\n dialect,\n detectedVersion: raw,\n requiredVersion: requiredStr,\n message:\n `Could not parse ${dialect} version from '${raw}'. ` +\n `Nextly requires ${dialect} ${requiredStr}. ` +\n `If you are on a known MySQL-compatible variant we did not detect, ` +\n `please file an issue. See ${DOCS_URL}.`,\n });\n }\n\n if (!meetsMinimum(parsed, required)) {\n const labels: Record<SupportedDialect, string> = {\n postgresql: \"PostgreSQL\",\n mysql: \"MySQL\",\n sqlite: \"SQLite\",\n };\n throw new UnsupportedDialectVersionError({\n dialect,\n detectedVersion: `${parsed.major}.${parsed.minor}`,\n requiredVersion: requiredStr,\n message:\n `${labels[dialect]} ${requiredStr} required; detected ` +\n `${parsed.major}.${parsed.minor}. See ${DOCS_URL}.`,\n });\n }\n return { variant: null };\n}\n\n// What: PG version query.\n// Why: pg's Client/PoolClient.query() returns { rows: [{ version: \"...\" }] }.\nasync function queryPostgresVersion(\n client: VersionQueryClient\n): Promise<string> {\n if (!client.query) {\n throw new Error(\"PostgreSQL client missing query() method\");\n }\n const result = (await client.query(\"SELECT version()\")) as {\n rows?: Array<{ version?: unknown }>;\n };\n const row = result.rows?.[0];\n const version = row?.version;\n if (typeof version !== \"string\") {\n throw new Error(\n `Unexpected response shape from SELECT version(): ${JSON.stringify(result)}`\n );\n }\n return version;\n}\n\n// What: MySQL version query.\n// Why: mysql2's Connection.query() returns [rows, fields] where rows[0]\n// has the column aliased as 'version'.\nasync function queryMysqlVersion(client: VersionQueryClient): Promise<string> {\n if (!client.query) {\n throw new Error(\"MySQL client missing query() method\");\n }\n const result = (await client.query(\"SELECT VERSION() AS version\")) as Array<\n Array<{ version?: unknown }>\n >;\n const row = result?.[0]?.[0];\n const version = row?.version;\n if (typeof version !== \"string\") {\n throw new Error(\n `Unexpected response shape from SELECT VERSION(): ${JSON.stringify(result)}`\n );\n }\n return version;\n}\n\n// What: SQLite version query via better-sqlite3's prepare/get.\n// Why: better-sqlite3 is synchronous, so this is a plain function returning\n// a resolved Promise. The helper awaits it uniformly with PG/MySQL paths.\nfunction querySqliteVersion(client: VersionQueryClient): Promise<string> {\n if (!client.prepare) {\n return Promise.reject(new Error(\"SQLite client missing prepare() method\"));\n }\n const stmt = client.prepare(\"SELECT sqlite_version() AS version\");\n const row = stmt.get() as { version?: unknown } | undefined;\n const version = row?.version;\n if (typeof version !== \"string\") {\n return Promise.reject(\n new Error(\n `Unexpected response shape from SELECT sqlite_version(): ${JSON.stringify(row)}`\n )\n );\n }\n return Promise.resolve(version);\n}\n"]}
|
package/dist/version-check.d.cts
CHANGED
|
@@ -38,6 +38,22 @@ interface VersionQueryClient {
|
|
|
38
38
|
interface CheckDialectVersionOptions {
|
|
39
39
|
onWarning?: (message: string) => void;
|
|
40
40
|
}
|
|
41
|
-
declare
|
|
41
|
+
declare const VARIANT_TOKENS: readonly ["mariadb", "tidb", "aurora", "planetscale", "vitess"];
|
|
42
|
+
/** A MySQL-compatible server the version string names. */
|
|
43
|
+
type DialectVariant = (typeof VARIANT_TOKENS)[number];
|
|
44
|
+
/** What the version check learned about the server it connected to. */
|
|
45
|
+
interface DialectVersionInfo {
|
|
46
|
+
/**
|
|
47
|
+
* The MySQL-compatible variant the version string names, or null for the
|
|
48
|
+
* dialect's own server (and always null on PostgreSQL and SQLite).
|
|
49
|
+
*/
|
|
50
|
+
variant: DialectVariant | null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Whether a MySQL-dialect server accepts `SELECT ... FOR SHARE`: true for
|
|
54
|
+
* MySQL itself and the variants that do, false for MariaDB and TiDB.
|
|
55
|
+
*/
|
|
56
|
+
declare function acceptsForShare(variant: DialectVariant | null): boolean;
|
|
57
|
+
declare function checkDialectVersion(client: VersionQueryClient, dialect: SupportedDialect, options?: CheckDialectVersionOptions): Promise<DialectVersionInfo>;
|
|
42
58
|
|
|
43
|
-
export { type CheckDialectVersionOptions, NEXTLY_MIN_DB_VERSIONS, UnsupportedDialectVersionError, type VersionQueryClient, checkDialectVersion };
|
|
59
|
+
export { type CheckDialectVersionOptions, type DialectVariant, type DialectVersionInfo, NEXTLY_MIN_DB_VERSIONS, UnsupportedDialectVersionError, type VersionQueryClient, acceptsForShare, checkDialectVersion };
|
package/dist/version-check.d.ts
CHANGED
|
@@ -38,6 +38,22 @@ interface VersionQueryClient {
|
|
|
38
38
|
interface CheckDialectVersionOptions {
|
|
39
39
|
onWarning?: (message: string) => void;
|
|
40
40
|
}
|
|
41
|
-
declare
|
|
41
|
+
declare const VARIANT_TOKENS: readonly ["mariadb", "tidb", "aurora", "planetscale", "vitess"];
|
|
42
|
+
/** A MySQL-compatible server the version string names. */
|
|
43
|
+
type DialectVariant = (typeof VARIANT_TOKENS)[number];
|
|
44
|
+
/** What the version check learned about the server it connected to. */
|
|
45
|
+
interface DialectVersionInfo {
|
|
46
|
+
/**
|
|
47
|
+
* The MySQL-compatible variant the version string names, or null for the
|
|
48
|
+
* dialect's own server (and always null on PostgreSQL and SQLite).
|
|
49
|
+
*/
|
|
50
|
+
variant: DialectVariant | null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Whether a MySQL-dialect server accepts `SELECT ... FOR SHARE`: true for
|
|
54
|
+
* MySQL itself and the variants that do, false for MariaDB and TiDB.
|
|
55
|
+
*/
|
|
56
|
+
declare function acceptsForShare(variant: DialectVariant | null): boolean;
|
|
57
|
+
declare function checkDialectVersion(client: VersionQueryClient, dialect: SupportedDialect, options?: CheckDialectVersionOptions): Promise<DialectVersionInfo>;
|
|
42
58
|
|
|
43
|
-
export { type CheckDialectVersionOptions, NEXTLY_MIN_DB_VERSIONS, UnsupportedDialectVersionError, type VersionQueryClient, checkDialectVersion };
|
|
59
|
+
export { type CheckDialectVersionOptions, type DialectVariant, type DialectVersionInfo, NEXTLY_MIN_DB_VERSIONS, UnsupportedDialectVersionError, type VersionQueryClient, acceptsForShare, checkDialectVersion };
|
package/dist/version-check.mjs
CHANGED
|
@@ -27,6 +27,13 @@ var VARIANT_TOKENS = [
|
|
|
27
27
|
"planetscale",
|
|
28
28
|
"vitess"
|
|
29
29
|
];
|
|
30
|
+
var VARIANTS_WITHOUT_FOR_SHARE = /* @__PURE__ */ new Set([
|
|
31
|
+
"mariadb",
|
|
32
|
+
"tidb"
|
|
33
|
+
]);
|
|
34
|
+
function acceptsForShare(variant) {
|
|
35
|
+
return variant === null || !VARIANTS_WITHOUT_FOR_SHARE.has(variant);
|
|
36
|
+
}
|
|
30
37
|
var POSTGRES_REGEX = /^PostgreSQL (\d+)\.(\d+)/;
|
|
31
38
|
var MYSQL_REGEX = /^(\d+)\.(\d+)\.(\d+)/;
|
|
32
39
|
var SQLITE_REGEX = /^(\d+)\.(\d+)\.(\d+)/;
|
|
@@ -72,7 +79,7 @@ async function checkDialectVersion(client, dialect, options) {
|
|
|
72
79
|
if (variant) {
|
|
73
80
|
const warning = `Detected ${variant} ('${raw}'). Nextly is regression-tested against real MySQL 8.0+ only; this MySQL-compatible variant is not officially supported in v1 but most operations should work. See ${DOCS_URL}.`;
|
|
74
81
|
if (options?.onWarning) options.onWarning(warning);
|
|
75
|
-
return;
|
|
82
|
+
return { variant };
|
|
76
83
|
}
|
|
77
84
|
let parsed;
|
|
78
85
|
if (dialect === "postgresql") parsed = parsePostgres(raw);
|
|
@@ -99,6 +106,7 @@ async function checkDialectVersion(client, dialect, options) {
|
|
|
99
106
|
message: `${labels[dialect]} ${requiredStr} required; detected ${parsed.major}.${parsed.minor}. See ${DOCS_URL}.`
|
|
100
107
|
});
|
|
101
108
|
}
|
|
109
|
+
return { variant: null };
|
|
102
110
|
}
|
|
103
111
|
async function queryPostgresVersion(client) {
|
|
104
112
|
if (!client.query) {
|
|
@@ -145,6 +153,6 @@ function querySqliteVersion(client) {
|
|
|
145
153
|
return Promise.resolve(version);
|
|
146
154
|
}
|
|
147
155
|
|
|
148
|
-
export { NEXTLY_MIN_DB_VERSIONS, UnsupportedDialectVersionError, checkDialectVersion };
|
|
156
|
+
export { NEXTLY_MIN_DB_VERSIONS, UnsupportedDialectVersionError, acceptsForShare, checkDialectVersion };
|
|
149
157
|
//# sourceMappingURL=version-check.mjs.map
|
|
150
158
|
//# sourceMappingURL=version-check.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/version-check.ts"],"names":[],"mappings":"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","file":"version-check.mjs","sourcesContent":["// What: shared connect-time DB version check.\n// Why: each dialect has version-dependent capabilities (RENAME COLUMN on\n// SQLite 3.25+, transactional DDL on MySQL 8+, etc). This helper hard-fails\n// at connect on real dialects below minimum, lets recognized cloud variants\n// (MariaDB, TiDB, Aurora, PlanetScale, Vitess) proceed with a warning, and\n// hard-fails on completely unparseable strings. F17 in the schema\n// architecture plan.\n\n// Why local imports only: adapter-drizzle is upstream of nextly\n// in the dep graph. Importing from nextly here would create a\n// circular dependency. SupportedDialect and DatabaseError already exist\n// in adapter-drizzle's own type system.\nimport type { SupportedDialect } from \"./types\";\nimport type { DatabaseError } from \"./types/error\";\n\n// Why these minimums: PG 15 ships native MERGE + nbtree dedup needed for\n// our schema rename pipeline. MySQL 8.0 ships native RENAME COLUMN.\n// SQLite 3.38 ships strict tables + native unixepoch(). Older versions\n// would force fallback paths that v1 explicitly does not implement.\nexport const NEXTLY_MIN_DB_VERSIONS = {\n postgresql: { major: 15, minor: 0 },\n mysql: { major: 8, minor: 0 },\n sqlite: { major: 3, minor: 38 },\n} as const;\n\n// What: typed error thrown when a real DB version is below minimum or a\n// version string cannot be parsed.\n// Why: implements adapter-drizzle's DatabaseError interface so existing\n// isDatabaseError() type guards work. Stores dialect + detected/required\n// versions on the instance so callers can build upgrade-guidance UI from\n// the error fields without parsing the message.\nexport class UnsupportedDialectVersionError\n extends Error\n implements DatabaseError\n{\n public readonly kind = \"unsupported_version\" as const;\n public readonly dialect: SupportedDialect;\n public readonly detectedVersion: string;\n public readonly requiredVersion: string;\n public override readonly cause?: Error;\n\n constructor(args: {\n dialect: SupportedDialect;\n detectedVersion: string;\n requiredVersion: string;\n message: string;\n cause?: Error;\n }) {\n super(args.message);\n this.name = \"UnsupportedDialectVersionError\";\n this.dialect = args.dialect;\n this.detectedVersion = args.detectedVersion;\n this.requiredVersion = args.requiredVersion;\n this.cause = args.cause;\n }\n}\n\n// What: minimal duck-typed query interface so this helper does not pull in\n// pg / mysql2 / better-sqlite3 types directly.\n// Why: keeps adapter-drizzle dependency-free of the per-dialect drivers.\n// query() returns unknown because Promise<unknown> is already a subtype of\n// unknown; the helper awaits at the call site, which works for both sync\n// and async return shapes.\nexport interface VersionQueryClient {\n query?: (sql: string) => unknown;\n prepare?: (sql: string) => { get: () => unknown };\n}\n\n// What: optional callback so adapters can route the variant warning through\n// their own logger.\n// Why: the helper itself does not depend on a logger module; the adapter\n// owns logger config and is the right place to surface the warning.\nexport interface CheckDialectVersionOptions {\n onWarning?: (message: string) => void;\n}\n\n// What: doc URL embedded in error messages.\n// Why: gives users a single landing page with upgrade instructions per\n// dialect + cloud provider. Filled in PR-2.\nconst DOCS_URL = \"https://nextlyhq.com/docs/database/support\";\n\n// What: recognized MySQL-protocol-compatible variants. Detection of any of\n// these tokens in the version string causes a warning instead of a hard-\n// fail.\n// Why: these databases speak the MySQL wire protocol but use their own\n// version schemes (MariaDB 10.x, TiDB 6.x advertising MySQL 5.7, Aurora's\n// custom suffix). Hard-failing them would lock out a large fraction of\n// real cloud users on first install. We warn so the user knows we have not\n// regression-tested against their variant; we proceed because the wire\n// protocol is compatible enough that most operations work.\nconst VARIANT_TOKENS = [\n \"mariadb\",\n \"tidb\",\n \"aurora\",\n \"planetscale\",\n \"vitess\",\n] as const;\n\nconst POSTGRES_REGEX = /^PostgreSQL (\\d+)\\.(\\d+)/;\nconst MYSQL_REGEX = /^(\\d+)\\.(\\d+)\\.(\\d+)/;\nconst SQLITE_REGEX = /^(\\d+)\\.(\\d+)\\.(\\d+)/;\n\ninterface ParsedVersion {\n major: number;\n minor: number;\n}\n\nfunction parsePostgres(raw: string): ParsedVersion | null {\n const match = POSTGRES_REGEX.exec(raw);\n if (!match) return null;\n return { major: Number(match[1]), minor: Number(match[2]) };\n}\n\nfunction parseMySQL(raw: string): ParsedVersion | null {\n const match = MYSQL_REGEX.exec(raw);\n if (!match) return null;\n return { major: Number(match[1]), minor: Number(match[2]) };\n}\n\nfunction parseSQLite(raw: string): ParsedVersion | null {\n const match = SQLITE_REGEX.exec(raw);\n if (!match) return null;\n return { major: Number(match[1]), minor: Number(match[2]) };\n}\n\nfunction detectVariant(raw: string): string | null {\n const lower = raw.toLowerCase();\n for (const token of VARIANT_TOKENS) {\n if (lower.includes(token)) return token;\n }\n return null;\n}\n\n// What: compares detected version to required minimum.\n// Why: simple major-minor comparison is sufficient; we do not gate on patch.\nfunction meetsMinimum(\n detected: ParsedVersion,\n required: { major: number; minor: number }\n): boolean {\n if (detected.major > required.major) return true;\n if (detected.major < required.major) return false;\n return detected.minor >= required.minor;\n}\n\n// What: queries the DB for its version string and validates it under the\n// hybrid policy.\n// Why: called as the first DB-content step inside each adapter's connect()\n// method.\n//\n// Decision tree:\n// 1. Variant token present (MariaDB/TiDB/Aurora/PlanetScale/Vitess)?\n// -> emit warning via options.onWarning, return successfully.\n// 2. Strict regex matches and version >= minimum?\n// -> return successfully.\n// 3. Strict regex matches but version < minimum?\n// -> throw UnsupportedDialectVersionError (real-DB-too-old path).\n// 4. Strict regex does not match AND no variant token detected?\n// -> throw UnsupportedDialectVersionError (truly unparseable path).\nexport async function checkDialectVersion(\n client: VersionQueryClient,\n dialect: SupportedDialect,\n options?: CheckDialectVersionOptions\n): Promise<void> {\n const required = NEXTLY_MIN_DB_VERSIONS[dialect];\n const requiredStr = `${required.major}.${required.minor}+`;\n\n let raw: string;\n if (dialect === \"sqlite\") {\n raw = await querySqliteVersion(client);\n } else if (dialect === \"postgresql\") {\n raw = await queryPostgresVersion(client);\n } else {\n raw = await queryMysqlVersion(client);\n }\n\n // Step 1: variant check. Only meaningful for MySQL — Postgres-flavored\n // services (Neon, Supabase, RDS PG, Aurora PG) all return canonical\n // \"PostgreSQL X.Y...\" strings and pass the strict regex naturally.\n const variant = dialect === \"mysql\" ? detectVariant(raw) : null;\n if (variant) {\n const warning =\n `Detected ${variant} ('${raw}'). Nextly is regression-tested ` +\n `against real MySQL 8.0+ only; this MySQL-compatible variant is not ` +\n `officially supported in v1 but most operations should work. ` +\n `See ${DOCS_URL}.`;\n if (options?.onWarning) options.onWarning(warning);\n return;\n }\n\n // Step 2/3: strict regex parse for real dialects.\n let parsed: ParsedVersion | null;\n if (dialect === \"postgresql\") parsed = parsePostgres(raw);\n else if (dialect === \"mysql\") parsed = parseMySQL(raw);\n else parsed = parseSQLite(raw);\n\n if (!parsed) {\n // Step 4: truly unparseable. Hard-fail so we surface the issue at boot\n // rather than silently letting the user proceed and hit cryptic errors\n // later.\n throw new UnsupportedDialectVersionError({\n dialect,\n detectedVersion: raw,\n requiredVersion: requiredStr,\n message:\n `Could not parse ${dialect} version from '${raw}'. ` +\n `Nextly requires ${dialect} ${requiredStr}. ` +\n `If you are on a known MySQL-compatible variant we did not detect, ` +\n `please file an issue. See ${DOCS_URL}.`,\n });\n }\n\n if (!meetsMinimum(parsed, required)) {\n const labels: Record<SupportedDialect, string> = {\n postgresql: \"PostgreSQL\",\n mysql: \"MySQL\",\n sqlite: \"SQLite\",\n };\n throw new UnsupportedDialectVersionError({\n dialect,\n detectedVersion: `${parsed.major}.${parsed.minor}`,\n requiredVersion: requiredStr,\n message:\n `${labels[dialect]} ${requiredStr} required; detected ` +\n `${parsed.major}.${parsed.minor}. See ${DOCS_URL}.`,\n });\n }\n}\n\n// What: PG version query.\n// Why: pg's Client/PoolClient.query() returns { rows: [{ version: \"...\" }] }.\nasync function queryPostgresVersion(\n client: VersionQueryClient\n): Promise<string> {\n if (!client.query) {\n throw new Error(\"PostgreSQL client missing query() method\");\n }\n const result = (await client.query(\"SELECT version()\")) as {\n rows?: Array<{ version?: unknown }>;\n };\n const row = result.rows?.[0];\n const version = row?.version;\n if (typeof version !== \"string\") {\n throw new Error(\n `Unexpected response shape from SELECT version(): ${JSON.stringify(result)}`\n );\n }\n return version;\n}\n\n// What: MySQL version query.\n// Why: mysql2's Connection.query() returns [rows, fields] where rows[0]\n// has the column aliased as 'version'.\nasync function queryMysqlVersion(client: VersionQueryClient): Promise<string> {\n if (!client.query) {\n throw new Error(\"MySQL client missing query() method\");\n }\n const result = (await client.query(\"SELECT VERSION() AS version\")) as Array<\n Array<{ version?: unknown }>\n >;\n const row = result?.[0]?.[0];\n const version = row?.version;\n if (typeof version !== \"string\") {\n throw new Error(\n `Unexpected response shape from SELECT VERSION(): ${JSON.stringify(result)}`\n );\n }\n return version;\n}\n\n// What: SQLite version query via better-sqlite3's prepare/get.\n// Why: better-sqlite3 is synchronous, so this is a plain function returning\n// a resolved Promise. The helper awaits it uniformly with PG/MySQL paths.\nfunction querySqliteVersion(client: VersionQueryClient): Promise<string> {\n if (!client.prepare) {\n return Promise.reject(new Error(\"SQLite client missing prepare() method\"));\n }\n const stmt = client.prepare(\"SELECT sqlite_version() AS version\");\n const row = stmt.get() as { version?: unknown } | undefined;\n const version = row?.version;\n if (typeof version !== \"string\") {\n return Promise.reject(\n new Error(\n `Unexpected response shape from SELECT sqlite_version(): ${JSON.stringify(row)}`\n )\n );\n }\n return Promise.resolve(version);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/version-check.ts"],"names":[],"mappings":"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","file":"version-check.mjs","sourcesContent":["// What: shared connect-time DB version check.\n// Why: each dialect has version-dependent capabilities (RENAME COLUMN on\n// SQLite 3.25+, transactional DDL on MySQL 8+, etc). This helper hard-fails\n// at connect on real dialects below minimum, lets recognized cloud variants\n// (MariaDB, TiDB, Aurora, PlanetScale, Vitess) proceed with a warning, and\n// hard-fails on completely unparseable strings. F17 in the schema\n// architecture plan.\n\n// Why local imports only: adapter-drizzle is upstream of nextly\n// in the dep graph. Importing from nextly here would create a\n// circular dependency. SupportedDialect and DatabaseError already exist\n// in adapter-drizzle's own type system.\nimport type { SupportedDialect } from \"./types\";\nimport type { DatabaseError } from \"./types/error\";\n\n// Why these minimums: PG 15 ships native MERGE + nbtree dedup needed for\n// our schema rename pipeline. MySQL 8.0 ships native RENAME COLUMN.\n// SQLite 3.38 ships strict tables + native unixepoch(). Older versions\n// would force fallback paths that v1 explicitly does not implement.\nexport const NEXTLY_MIN_DB_VERSIONS = {\n postgresql: { major: 15, minor: 0 },\n mysql: { major: 8, minor: 0 },\n sqlite: { major: 3, minor: 38 },\n} as const;\n\n// What: typed error thrown when a real DB version is below minimum or a\n// version string cannot be parsed.\n// Why: implements adapter-drizzle's DatabaseError interface so existing\n// isDatabaseError() type guards work. Stores dialect + detected/required\n// versions on the instance so callers can build upgrade-guidance UI from\n// the error fields without parsing the message.\nexport class UnsupportedDialectVersionError\n extends Error\n implements DatabaseError\n{\n public readonly kind = \"unsupported_version\" as const;\n public readonly dialect: SupportedDialect;\n public readonly detectedVersion: string;\n public readonly requiredVersion: string;\n public override readonly cause?: Error;\n\n constructor(args: {\n dialect: SupportedDialect;\n detectedVersion: string;\n requiredVersion: string;\n message: string;\n cause?: Error;\n }) {\n super(args.message);\n this.name = \"UnsupportedDialectVersionError\";\n this.dialect = args.dialect;\n this.detectedVersion = args.detectedVersion;\n this.requiredVersion = args.requiredVersion;\n this.cause = args.cause;\n }\n}\n\n// What: minimal duck-typed query interface so this helper does not pull in\n// pg / mysql2 / better-sqlite3 types directly.\n// Why: keeps adapter-drizzle dependency-free of the per-dialect drivers.\n// query() returns unknown because Promise<unknown> is already a subtype of\n// unknown; the helper awaits at the call site, which works for both sync\n// and async return shapes.\nexport interface VersionQueryClient {\n query?: (sql: string) => unknown;\n prepare?: (sql: string) => { get: () => unknown };\n}\n\n// What: optional callback so adapters can route the variant warning through\n// their own logger.\n// Why: the helper itself does not depend on a logger module; the adapter\n// owns logger config and is the right place to surface the warning.\nexport interface CheckDialectVersionOptions {\n onWarning?: (message: string) => void;\n}\n\n// What: doc URL embedded in error messages.\n// Why: gives users a single landing page with upgrade instructions per\n// dialect + cloud provider. Filled in PR-2.\nconst DOCS_URL = \"https://nextlyhq.com/docs/database/support\";\n\n// What: recognized MySQL-protocol-compatible variants. Detection of any of\n// these tokens in the version string causes a warning instead of a hard-\n// fail.\n// Why: these databases speak the MySQL wire protocol but use their own\n// version schemes (MariaDB 10.x, TiDB 6.x advertising MySQL 5.7, Aurora's\n// custom suffix). Hard-failing them would lock out a large fraction of\n// real cloud users on first install. We warn so the user knows we have not\n// regression-tested against their variant; we proceed because the wire\n// protocol is compatible enough that most operations work.\nconst VARIANT_TOKENS = [\n \"mariadb\",\n \"tidb\",\n \"aurora\",\n \"planetscale\",\n \"vitess\",\n] as const;\n\n/** A MySQL-compatible server the version string names. */\nexport type DialectVariant = (typeof VARIANT_TOKENS)[number];\n\n/** What the version check learned about the server it connected to. */\nexport interface DialectVersionInfo {\n /**\n * The MySQL-compatible variant the version string names, or null for the\n * dialect's own server (and always null on PostgreSQL and SQLite).\n */\n variant: DialectVariant | null;\n}\n\n// What: the variants that reject `SELECT ... FOR SHARE` as a syntax error.\n// Why: MariaDB spells the shared lock `LOCK IN SHARE MODE` only, and TiDB\n// refuses both spellings unless its no-op functions are switched on. MySQL\n// 8, Aurora MySQL and Vitess/PlanetScale accept `FOR SHARE`.\nconst VARIANTS_WITHOUT_FOR_SHARE: ReadonlySet<DialectVariant> = new Set([\n \"mariadb\",\n \"tidb\",\n]);\n\n/**\n * Whether a MySQL-dialect server accepts `SELECT ... FOR SHARE`: true for\n * MySQL itself and the variants that do, false for MariaDB and TiDB.\n */\nexport function acceptsForShare(variant: DialectVariant | null): boolean {\n return variant === null || !VARIANTS_WITHOUT_FOR_SHARE.has(variant);\n}\n\nconst POSTGRES_REGEX = /^PostgreSQL (\\d+)\\.(\\d+)/;\nconst MYSQL_REGEX = /^(\\d+)\\.(\\d+)\\.(\\d+)/;\nconst SQLITE_REGEX = /^(\\d+)\\.(\\d+)\\.(\\d+)/;\n\ninterface ParsedVersion {\n major: number;\n minor: number;\n}\n\nfunction parsePostgres(raw: string): ParsedVersion | null {\n const match = POSTGRES_REGEX.exec(raw);\n if (!match) return null;\n return { major: Number(match[1]), minor: Number(match[2]) };\n}\n\nfunction parseMySQL(raw: string): ParsedVersion | null {\n const match = MYSQL_REGEX.exec(raw);\n if (!match) return null;\n return { major: Number(match[1]), minor: Number(match[2]) };\n}\n\nfunction parseSQLite(raw: string): ParsedVersion | null {\n const match = SQLITE_REGEX.exec(raw);\n if (!match) return null;\n return { major: Number(match[1]), minor: Number(match[2]) };\n}\n\nfunction detectVariant(raw: string): DialectVariant | null {\n const lower = raw.toLowerCase();\n for (const token of VARIANT_TOKENS) {\n if (lower.includes(token)) return token;\n }\n return null;\n}\n\n// What: compares detected version to required minimum.\n// Why: simple major-minor comparison is sufficient; we do not gate on patch.\nfunction meetsMinimum(\n detected: ParsedVersion,\n required: { major: number; minor: number }\n): boolean {\n if (detected.major > required.major) return true;\n if (detected.major < required.major) return false;\n return detected.minor >= required.minor;\n}\n\n// What: queries the DB for its version string and validates it under the\n// hybrid policy.\n// Why: called as the first DB-content step inside each adapter's connect()\n// method.\n//\n// Decision tree:\n// 1. Variant token present (MariaDB/TiDB/Aurora/PlanetScale/Vitess)?\n// -> emit warning via options.onWarning, return the variant.\n// 2. Strict regex matches and version >= minimum?\n// -> return successfully.\n// 3. Strict regex matches but version < minimum?\n// -> throw UnsupportedDialectVersionError (real-DB-too-old path).\n// 4. Strict regex does not match AND no variant token detected?\n// -> throw UnsupportedDialectVersionError (truly unparseable path).\nexport async function checkDialectVersion(\n client: VersionQueryClient,\n dialect: SupportedDialect,\n options?: CheckDialectVersionOptions\n): Promise<DialectVersionInfo> {\n const required = NEXTLY_MIN_DB_VERSIONS[dialect];\n const requiredStr = `${required.major}.${required.minor}+`;\n\n let raw: string;\n if (dialect === \"sqlite\") {\n raw = await querySqliteVersion(client);\n } else if (dialect === \"postgresql\") {\n raw = await queryPostgresVersion(client);\n } else {\n raw = await queryMysqlVersion(client);\n }\n\n // Step 1: variant check. Only meaningful for MySQL — Postgres-flavored\n // services (Neon, Supabase, RDS PG, Aurora PG) all return canonical\n // \"PostgreSQL X.Y...\" strings and pass the strict regex naturally.\n const variant = dialect === \"mysql\" ? detectVariant(raw) : null;\n if (variant) {\n const warning =\n `Detected ${variant} ('${raw}'). Nextly is regression-tested ` +\n `against real MySQL 8.0+ only; this MySQL-compatible variant is not ` +\n `officially supported in v1 but most operations should work. ` +\n `See ${DOCS_URL}.`;\n if (options?.onWarning) options.onWarning(warning);\n return { variant };\n }\n\n // Step 2/3: strict regex parse for real dialects.\n let parsed: ParsedVersion | null;\n if (dialect === \"postgresql\") parsed = parsePostgres(raw);\n else if (dialect === \"mysql\") parsed = parseMySQL(raw);\n else parsed = parseSQLite(raw);\n\n if (!parsed) {\n // Step 4: truly unparseable. Hard-fail so we surface the issue at boot\n // rather than silently letting the user proceed and hit cryptic errors\n // later.\n throw new UnsupportedDialectVersionError({\n dialect,\n detectedVersion: raw,\n requiredVersion: requiredStr,\n message:\n `Could not parse ${dialect} version from '${raw}'. ` +\n `Nextly requires ${dialect} ${requiredStr}. ` +\n `If you are on a known MySQL-compatible variant we did not detect, ` +\n `please file an issue. See ${DOCS_URL}.`,\n });\n }\n\n if (!meetsMinimum(parsed, required)) {\n const labels: Record<SupportedDialect, string> = {\n postgresql: \"PostgreSQL\",\n mysql: \"MySQL\",\n sqlite: \"SQLite\",\n };\n throw new UnsupportedDialectVersionError({\n dialect,\n detectedVersion: `${parsed.major}.${parsed.minor}`,\n requiredVersion: requiredStr,\n message:\n `${labels[dialect]} ${requiredStr} required; detected ` +\n `${parsed.major}.${parsed.minor}. See ${DOCS_URL}.`,\n });\n }\n return { variant: null };\n}\n\n// What: PG version query.\n// Why: pg's Client/PoolClient.query() returns { rows: [{ version: \"...\" }] }.\nasync function queryPostgresVersion(\n client: VersionQueryClient\n): Promise<string> {\n if (!client.query) {\n throw new Error(\"PostgreSQL client missing query() method\");\n }\n const result = (await client.query(\"SELECT version()\")) as {\n rows?: Array<{ version?: unknown }>;\n };\n const row = result.rows?.[0];\n const version = row?.version;\n if (typeof version !== \"string\") {\n throw new Error(\n `Unexpected response shape from SELECT version(): ${JSON.stringify(result)}`\n );\n }\n return version;\n}\n\n// What: MySQL version query.\n// Why: mysql2's Connection.query() returns [rows, fields] where rows[0]\n// has the column aliased as 'version'.\nasync function queryMysqlVersion(client: VersionQueryClient): Promise<string> {\n if (!client.query) {\n throw new Error(\"MySQL client missing query() method\");\n }\n const result = (await client.query(\"SELECT VERSION() AS version\")) as Array<\n Array<{ version?: unknown }>\n >;\n const row = result?.[0]?.[0];\n const version = row?.version;\n if (typeof version !== \"string\") {\n throw new Error(\n `Unexpected response shape from SELECT VERSION(): ${JSON.stringify(result)}`\n );\n }\n return version;\n}\n\n// What: SQLite version query via better-sqlite3's prepare/get.\n// Why: better-sqlite3 is synchronous, so this is a plain function returning\n// a resolved Promise. The helper awaits it uniformly with PG/MySQL paths.\nfunction querySqliteVersion(client: VersionQueryClient): Promise<string> {\n if (!client.prepare) {\n return Promise.reject(new Error(\"SQLite client missing prepare() method\"));\n }\n const stmt = client.prepare(\"SELECT sqlite_version() AS version\");\n const row = stmt.get() as { version?: unknown } | undefined;\n const version = row?.version;\n if (typeof version !== \"string\") {\n return Promise.reject(\n new Error(\n `Unexpected response shape from SELECT sqlite_version(): ${JSON.stringify(row)}`\n )\n );\n }\n return Promise.resolve(version);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextlyhq/adapter-drizzle",
|
|
3
|
-
"version": "0.0.2-alpha.
|
|
3
|
+
"version": "0.0.2-alpha.67",
|
|
4
4
|
"description": "Shared Drizzle ORM adapter logic for Nextly database adapters",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -28,6 +28,16 @@
|
|
|
28
28
|
"import": "./dist/migrations.mjs",
|
|
29
29
|
"require": "./dist/migrations.cjs"
|
|
30
30
|
},
|
|
31
|
+
"./schema-name": {
|
|
32
|
+
"types": "./dist/schema-name.d.ts",
|
|
33
|
+
"import": "./dist/schema-name.mjs",
|
|
34
|
+
"require": "./dist/schema-name.cjs"
|
|
35
|
+
},
|
|
36
|
+
"./text-match": {
|
|
37
|
+
"types": "./dist/text-match.d.ts",
|
|
38
|
+
"import": "./dist/text-match.mjs",
|
|
39
|
+
"require": "./dist/text-match.cjs"
|
|
40
|
+
},
|
|
31
41
|
"./version-check": {
|
|
32
42
|
"types": "./dist/version-check.d.ts",
|
|
33
43
|
"import": "./dist/version-check.mjs",
|
|
@@ -44,6 +54,8 @@
|
|
|
44
54
|
"drizzle-orm": "1.0.0-rc.4"
|
|
45
55
|
},
|
|
46
56
|
"devDependencies": {
|
|
57
|
+
"@nextlyhq/eslint-config": "0.0.2-alpha.67",
|
|
58
|
+
"@nextlyhq/tsconfig": "0.0.2-alpha.67",
|
|
47
59
|
"@types/node": "^20.19.17",
|
|
48
60
|
"@vitest/coverage-v8": "^4.1.0",
|
|
49
61
|
"@vitest/ui": "^4.1.0",
|
|
@@ -51,9 +63,7 @@
|
|
|
51
63
|
"tsup": "^8.5.0",
|
|
52
64
|
"typescript": "^5.9.3",
|
|
53
65
|
"vite-tsconfig-paths": "^5.1.4",
|
|
54
|
-
"vitest": "^4.1.0"
|
|
55
|
-
"@nextlyhq/eslint-config": "0.0.2-alpha.65",
|
|
56
|
-
"@nextlyhq/tsconfig": "0.0.2-alpha.65"
|
|
66
|
+
"vitest": "^4.1.0"
|
|
57
67
|
},
|
|
58
68
|
"repository": {
|
|
59
69
|
"type": "git",
|