@tinycloud/sdk-services 2.4.0-beta.8 → 2.4.1

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.
@@ -85,13 +85,54 @@ interface ExecuteResponse {
85
85
  interface BatchResponse {
86
86
  results: ExecuteResponse[];
87
87
  }
88
+ /**
89
+ * A versioned SQL migration owned by an application or SDK subsystem.
90
+ */
91
+ interface SqlMigration {
92
+ /**
93
+ * Stable migration id within the namespace, e.g. "001_initial".
94
+ */
95
+ id: string;
96
+ /**
97
+ * SQL statements to apply when this migration has not been recorded.
98
+ * Statements should be idempotent where SQLite supports it.
99
+ */
100
+ sql: Array<string | SqlStatement>;
101
+ }
102
+ /**
103
+ * Options for applying SQL migrations.
104
+ */
105
+ interface SqlMigrationApplyOptions {
106
+ /**
107
+ * Namespace for this migration set, usually an app id or SDK subsystem.
108
+ */
109
+ namespace: string;
110
+ /**
111
+ * Ordered migration list.
112
+ */
113
+ migrations: SqlMigration[];
114
+ /**
115
+ * Custom abort signal for migration operations.
116
+ */
117
+ signal?: AbortSignal;
118
+ }
119
+ /**
120
+ * Result from applying SQL migrations.
121
+ */
122
+ interface SqlMigrationApplyResponse {
123
+ database: string;
124
+ namespace: string;
125
+ status: "already_current" | "applied";
126
+ applied: string[];
127
+ skipped: string[];
128
+ }
88
129
  /**
89
130
  * SQL service action types.
90
131
  */
91
132
  declare const SQLAction: {
92
133
  readonly READ: "tinycloud.sql/read";
93
134
  readonly WRITE: "tinycloud.sql/write";
94
- readonly DDL: "tinycloud.sql/ddl";
135
+ readonly SCHEMA: "tinycloud.sql/schema";
95
136
  readonly ADMIN: "tinycloud.sql/admin";
96
137
  readonly SELECT: "tinycloud.sql/select";
97
138
  readonly INSERT: "tinycloud.sql/insert";
@@ -109,12 +150,23 @@ type SQLActionType = (typeof SQLAction)[keyof typeof SQLAction];
109
150
  * Platform-agnostic interface for SQL database operations.
110
151
  */
111
152
 
153
+ /**
154
+ * Migration helper bound to a specific database.
155
+ */
156
+ interface ISQLMigrations {
157
+ /**
158
+ * Apply ordered, app-owned migrations and record completed ids.
159
+ */
160
+ apply(options: SqlMigrationApplyOptions): Promise<Result<SqlMigrationApplyResponse>>;
161
+ }
112
162
  /**
113
163
  * Database handle interface for operations on a specific named database.
114
164
  */
115
165
  interface IDatabaseHandle {
116
166
  /** The database name */
117
167
  readonly name: string;
168
+ /** Versioned schema migration helper. */
169
+ readonly migrations: ISQLMigrations;
118
170
  /**
119
171
  * Execute a SQL query and return rows.
120
172
  */
@@ -179,6 +231,7 @@ interface ISQLService extends IService {
179
231
  declare class SQLService extends BaseService implements ISQLService {
180
232
  static readonly serviceName = "sql";
181
233
  protected _config: SQLServiceConfig;
234
+ private readonly migrationLocks;
182
235
  constructor(config?: SQLServiceConfig);
183
236
  get config(): SQLServiceConfig;
184
237
  private get defaultDbName();
@@ -199,11 +252,15 @@ declare class SQLService extends BaseService implements ISQLService {
199
252
  * Shortcut: batch on the default database.
200
253
  */
201
254
  batch(statements: SqlStatement[], options?: BatchOptions): Promise<Result<BatchResponse>>;
255
+ applyMigrationsOnDb(dbName: string, options: SqlMigrationApplyOptions): Promise<Result<SqlMigrationApplyResponse>>;
202
256
  queryOnDb<T = Record<string, unknown>>(dbName: string, sql: string, params?: SqlValue[], options?: QueryOptions): Promise<Result<QueryResponse<T>>>;
203
257
  executeOnDb(dbName: string, sql: string, params?: SqlValue[], options?: ExecuteOptions): Promise<Result<ExecuteResponse>>;
204
258
  batchOnDb(dbName: string, statements: SqlStatement[], options?: BatchOptions): Promise<Result<BatchResponse>>;
205
259
  executeStatementOnDb(dbName: string, name: string, params?: SqlValue[], options?: QueryOptions): Promise<Result<QueryResponse | ExecuteResponse>>;
206
260
  exportDb(dbName: string, options?: QueryOptions): Promise<Result<Blob>>;
261
+ private applyMigrationsOnDbUnlocked;
262
+ private applyOneMigration;
263
+ private ensureMigrationsTable;
207
264
  private invokeSQL;
208
265
  private actionForSql;
209
266
  private actionsForSqlBatch;
@@ -222,6 +279,7 @@ declare class SQLService extends BaseService implements ISQLService {
222
279
  declare class DatabaseHandle implements IDatabaseHandle {
223
280
  private service;
224
281
  readonly name: string;
282
+ readonly migrations: ISQLMigrations;
225
283
  constructor(service: SQLService, name: string);
226
284
  query<T = Record<string, unknown>>(sql: string, params?: SqlValue[], options?: QueryOptions): Promise<Result<QueryResponse<T>>>;
227
285
  execute(sql: string, params?: SqlValue[], options?: ExecuteOptions): Promise<Result<ExecuteResponse>>;
@@ -230,4 +288,11 @@ declare class DatabaseHandle implements IDatabaseHandle {
230
288
  export(options?: QueryOptions): Promise<Result<Blob>>;
231
289
  }
232
290
 
233
- export { type BatchOptions, type BatchResponse, DatabaseHandle, type ExecuteOptions, type ExecuteResponse, type IDatabaseHandle, type ISQLService, type QueryOptions, type QueryResponse, SQLAction, type SQLActionType, SQLService, type SQLServiceConfig, type SqlStatement, type SqlValue };
291
+ declare class SQLMigrations implements ISQLMigrations {
292
+ private readonly service;
293
+ private readonly dbName;
294
+ constructor(service: SQLService, dbName: string);
295
+ apply(options: SqlMigrationApplyOptions): Promise<Result<SqlMigrationApplyResponse>>;
296
+ }
297
+
298
+ export { type BatchOptions, type BatchResponse, DatabaseHandle, type ExecuteOptions, type ExecuteResponse, type IDatabaseHandle, type ISQLMigrations, type ISQLService, type QueryOptions, type QueryResponse, SQLAction, type SQLActionType, SQLMigrations, SQLService, type SQLServiceConfig, type SqlMigration, type SqlMigrationApplyOptions, type SqlMigrationApplyResponse, type SqlStatement, type SqlValue };
@@ -85,13 +85,54 @@ interface ExecuteResponse {
85
85
  interface BatchResponse {
86
86
  results: ExecuteResponse[];
87
87
  }
88
+ /**
89
+ * A versioned SQL migration owned by an application or SDK subsystem.
90
+ */
91
+ interface SqlMigration {
92
+ /**
93
+ * Stable migration id within the namespace, e.g. "001_initial".
94
+ */
95
+ id: string;
96
+ /**
97
+ * SQL statements to apply when this migration has not been recorded.
98
+ * Statements should be idempotent where SQLite supports it.
99
+ */
100
+ sql: Array<string | SqlStatement>;
101
+ }
102
+ /**
103
+ * Options for applying SQL migrations.
104
+ */
105
+ interface SqlMigrationApplyOptions {
106
+ /**
107
+ * Namespace for this migration set, usually an app id or SDK subsystem.
108
+ */
109
+ namespace: string;
110
+ /**
111
+ * Ordered migration list.
112
+ */
113
+ migrations: SqlMigration[];
114
+ /**
115
+ * Custom abort signal for migration operations.
116
+ */
117
+ signal?: AbortSignal;
118
+ }
119
+ /**
120
+ * Result from applying SQL migrations.
121
+ */
122
+ interface SqlMigrationApplyResponse {
123
+ database: string;
124
+ namespace: string;
125
+ status: "already_current" | "applied";
126
+ applied: string[];
127
+ skipped: string[];
128
+ }
88
129
  /**
89
130
  * SQL service action types.
90
131
  */
91
132
  declare const SQLAction: {
92
133
  readonly READ: "tinycloud.sql/read";
93
134
  readonly WRITE: "tinycloud.sql/write";
94
- readonly DDL: "tinycloud.sql/ddl";
135
+ readonly SCHEMA: "tinycloud.sql/schema";
95
136
  readonly ADMIN: "tinycloud.sql/admin";
96
137
  readonly SELECT: "tinycloud.sql/select";
97
138
  readonly INSERT: "tinycloud.sql/insert";
@@ -109,12 +150,23 @@ type SQLActionType = (typeof SQLAction)[keyof typeof SQLAction];
109
150
  * Platform-agnostic interface for SQL database operations.
110
151
  */
111
152
 
153
+ /**
154
+ * Migration helper bound to a specific database.
155
+ */
156
+ interface ISQLMigrations {
157
+ /**
158
+ * Apply ordered, app-owned migrations and record completed ids.
159
+ */
160
+ apply(options: SqlMigrationApplyOptions): Promise<Result<SqlMigrationApplyResponse>>;
161
+ }
112
162
  /**
113
163
  * Database handle interface for operations on a specific named database.
114
164
  */
115
165
  interface IDatabaseHandle {
116
166
  /** The database name */
117
167
  readonly name: string;
168
+ /** Versioned schema migration helper. */
169
+ readonly migrations: ISQLMigrations;
118
170
  /**
119
171
  * Execute a SQL query and return rows.
120
172
  */
@@ -179,6 +231,7 @@ interface ISQLService extends IService {
179
231
  declare class SQLService extends BaseService implements ISQLService {
180
232
  static readonly serviceName = "sql";
181
233
  protected _config: SQLServiceConfig;
234
+ private readonly migrationLocks;
182
235
  constructor(config?: SQLServiceConfig);
183
236
  get config(): SQLServiceConfig;
184
237
  private get defaultDbName();
@@ -199,11 +252,15 @@ declare class SQLService extends BaseService implements ISQLService {
199
252
  * Shortcut: batch on the default database.
200
253
  */
201
254
  batch(statements: SqlStatement[], options?: BatchOptions): Promise<Result<BatchResponse>>;
255
+ applyMigrationsOnDb(dbName: string, options: SqlMigrationApplyOptions): Promise<Result<SqlMigrationApplyResponse>>;
202
256
  queryOnDb<T = Record<string, unknown>>(dbName: string, sql: string, params?: SqlValue[], options?: QueryOptions): Promise<Result<QueryResponse<T>>>;
203
257
  executeOnDb(dbName: string, sql: string, params?: SqlValue[], options?: ExecuteOptions): Promise<Result<ExecuteResponse>>;
204
258
  batchOnDb(dbName: string, statements: SqlStatement[], options?: BatchOptions): Promise<Result<BatchResponse>>;
205
259
  executeStatementOnDb(dbName: string, name: string, params?: SqlValue[], options?: QueryOptions): Promise<Result<QueryResponse | ExecuteResponse>>;
206
260
  exportDb(dbName: string, options?: QueryOptions): Promise<Result<Blob>>;
261
+ private applyMigrationsOnDbUnlocked;
262
+ private applyOneMigration;
263
+ private ensureMigrationsTable;
207
264
  private invokeSQL;
208
265
  private actionForSql;
209
266
  private actionsForSqlBatch;
@@ -222,6 +279,7 @@ declare class SQLService extends BaseService implements ISQLService {
222
279
  declare class DatabaseHandle implements IDatabaseHandle {
223
280
  private service;
224
281
  readonly name: string;
282
+ readonly migrations: ISQLMigrations;
225
283
  constructor(service: SQLService, name: string);
226
284
  query<T = Record<string, unknown>>(sql: string, params?: SqlValue[], options?: QueryOptions): Promise<Result<QueryResponse<T>>>;
227
285
  execute(sql: string, params?: SqlValue[], options?: ExecuteOptions): Promise<Result<ExecuteResponse>>;
@@ -230,4 +288,11 @@ declare class DatabaseHandle implements IDatabaseHandle {
230
288
  export(options?: QueryOptions): Promise<Result<Blob>>;
231
289
  }
232
290
 
233
- export { type BatchOptions, type BatchResponse, DatabaseHandle, type ExecuteOptions, type ExecuteResponse, type IDatabaseHandle, type ISQLService, type QueryOptions, type QueryResponse, SQLAction, type SQLActionType, SQLService, type SQLServiceConfig, type SqlStatement, type SqlValue };
291
+ declare class SQLMigrations implements ISQLMigrations {
292
+ private readonly service;
293
+ private readonly dbName;
294
+ constructor(service: SQLService, dbName: string);
295
+ apply(options: SqlMigrationApplyOptions): Promise<Result<SqlMigrationApplyResponse>>;
296
+ }
297
+
298
+ export { type BatchOptions, type BatchResponse, DatabaseHandle, type ExecuteOptions, type ExecuteResponse, type IDatabaseHandle, type ISQLMigrations, type ISQLService, type QueryOptions, type QueryResponse, SQLAction, type SQLActionType, SQLMigrations, SQLService, type SQLServiceConfig, type SqlMigration, type SqlMigrationApplyOptions, type SqlMigrationApplyResponse, type SqlStatement, type SqlValue };
package/dist/sql/index.js CHANGED
@@ -324,11 +324,59 @@ var BaseService = class {
324
324
  }
325
325
  };
326
326
 
327
+ // src/responseErrors.ts
328
+ function parseServiceErrorBody(errorText) {
329
+ try {
330
+ return JSON.parse(errorText);
331
+ } catch {
332
+ return {};
333
+ }
334
+ }
335
+ function formatServiceResponseError(serviceLabel, operation, status, errorText, parsed) {
336
+ if (parsed.message) {
337
+ return compactMessage(parsed.message);
338
+ }
339
+ if (looksLikeHtml(errorText)) {
340
+ if (status === 524 || /524\s*[:-]/i.test(errorText) || /a timeout occurred/i.test(errorText)) {
341
+ return `${serviceLabel} ${operation} failed: upstream request timed out. Please retry.`;
342
+ }
343
+ return `${serviceLabel} ${operation} failed: upstream service returned an HTML error page (${status}).`;
344
+ }
345
+ return `${serviceLabel} ${operation} failed: ${status} - ${compactMessage(errorText)}`;
346
+ }
347
+ function responseErrorMeta(status, statusText, errorText) {
348
+ const meta = { status, statusText };
349
+ const snippet = compactMessage(errorText);
350
+ if (snippet) {
351
+ meta.responseSnippet = snippet.slice(0, 300);
352
+ }
353
+ return meta;
354
+ }
355
+ function looksLikeHtml(text) {
356
+ return /<!doctype html/i.test(text) || /<html[\s>]/i.test(text);
357
+ }
358
+ function compactMessage(message) {
359
+ const compact = message.replace(/\s+/g, " ").trim();
360
+ return compact.length > 500 ? `${compact.slice(0, 500)}...` : compact;
361
+ }
362
+
363
+ // src/sql/SQLMigrations.ts
364
+ var SQLMigrations = class {
365
+ constructor(service, dbName) {
366
+ this.service = service;
367
+ this.dbName = dbName;
368
+ }
369
+ apply(options) {
370
+ return this.service.applyMigrationsOnDb(this.dbName, options);
371
+ }
372
+ };
373
+
327
374
  // src/sql/DatabaseHandle.ts
328
375
  var DatabaseHandle = class {
329
376
  constructor(service, name) {
330
377
  this.service = service;
331
378
  this.name = name;
379
+ this.migrations = new SQLMigrations(service, name);
332
380
  }
333
381
  async query(sql, params, options) {
334
382
  return this.service.queryOnDb(this.name, sql, params, options);
@@ -351,7 +399,7 @@ var DatabaseHandle = class {
351
399
  var SQLAction = {
352
400
  READ: "tinycloud.sql/read",
353
401
  WRITE: "tinycloud.sql/write",
354
- DDL: "tinycloud.sql/ddl",
402
+ SCHEMA: "tinycloud.sql/schema",
355
403
  ADMIN: "tinycloud.sql/admin",
356
404
  SELECT: "tinycloud.sql/select",
357
405
  INSERT: "tinycloud.sql/insert",
@@ -364,9 +412,20 @@ var SQLAction = {
364
412
 
365
413
  // src/sql/SQLService.ts
366
414
  var DDL_TOKENS = /* @__PURE__ */ new Set(["alter", "create", "drop"]);
415
+ var MIGRATIONS_TABLE = "__tinycloud_sql_migrations";
416
+ var MIGRATIONS_SCHEMA = `CREATE TABLE IF NOT EXISTS ${MIGRATIONS_TABLE} (
417
+ key TEXT PRIMARY KEY,
418
+ namespace TEXT NOT NULL,
419
+ id TEXT NOT NULL,
420
+ applied_at TEXT NOT NULL,
421
+ statement_count INTEGER NOT NULL
422
+ )`;
423
+ var MIGRATIONS_META_NAMESPACE = "tinycloud.sql.migrations";
424
+ var MIGRATIONS_META_ID = "000_metadata";
367
425
  var SQLService = class extends BaseService {
368
426
  constructor(config = {}) {
369
427
  super();
428
+ this.migrationLocks = /* @__PURE__ */ new Map();
370
429
  this._config = config;
371
430
  }
372
431
  get config() {
@@ -402,6 +461,24 @@ var SQLService = class extends BaseService {
402
461
  async batch(statements, options) {
403
462
  return this.batchOnDb(this.defaultDbName, statements, options);
404
463
  }
464
+ async applyMigrationsOnDb(dbName, options) {
465
+ const validationError = validateMigrationOptions(options);
466
+ if (validationError) {
467
+ return err(serviceError(ErrorCodes.INVALID_INPUT, validationError, "sql"));
468
+ }
469
+ const lockKey = `${dbName}\0${options.namespace}`;
470
+ const existing = this.migrationLocks.get(lockKey);
471
+ if (existing) {
472
+ return existing;
473
+ }
474
+ const promise = this.applyMigrationsOnDbUnlocked(dbName, options);
475
+ this.migrationLocks.set(lockKey, promise);
476
+ try {
477
+ return await promise;
478
+ } finally {
479
+ this.migrationLocks.delete(lockKey);
480
+ }
481
+ }
405
482
  // === Internal methods called by DatabaseHandle ===
406
483
  async queryOnDb(dbName, sql, params, options) {
407
484
  return this.withTelemetry("query", dbName, async () => {
@@ -442,7 +519,7 @@ var SQLService = class extends BaseService {
442
519
  const actions = [
443
520
  this.actionForSql(sql, SQLAction.WRITE),
444
521
  ...(options?.schema ?? []).map(
445
- (statement) => this.actionForSql(statement, SQLAction.DDL)
522
+ (statement) => this.actionForSql(statement, SQLAction.SCHEMA)
446
523
  )
447
524
  ];
448
525
  const response = await this.invokeSQL(
@@ -532,6 +609,78 @@ var SQLService = class extends BaseService {
532
609
  }
533
610
  });
534
611
  }
612
+ async applyMigrationsOnDbUnlocked(dbName, options) {
613
+ return this.withTelemetry("migrations.apply", dbName, async () => {
614
+ const created = await this.ensureMigrationsTable(dbName, options.signal);
615
+ if (!created.ok) return created;
616
+ const listed = await this.queryOnDb(
617
+ dbName,
618
+ `SELECT id FROM ${MIGRATIONS_TABLE} WHERE namespace = ? ORDER BY applied_at, id`,
619
+ [options.namespace],
620
+ { signal: options.signal }
621
+ );
622
+ if (!listed.ok) return listed;
623
+ const appliedIds = new Set(
624
+ listed.data.rows.map((row) => rowValue(row, 0)).filter((id) => typeof id === "string")
625
+ );
626
+ const skipped = options.migrations.filter((migration) => appliedIds.has(migration.id)).map((migration) => migration.id);
627
+ const pending = options.migrations.filter((migration) => !appliedIds.has(migration.id));
628
+ const applied = [];
629
+ for (const migration of pending) {
630
+ const result = await this.applyOneMigration(dbName, options.namespace, migration, options.signal);
631
+ if (!result.ok) return result;
632
+ applied.push(migration.id);
633
+ }
634
+ return ok({
635
+ database: dbName,
636
+ namespace: options.namespace,
637
+ status: applied.length > 0 ? "applied" : "already_current",
638
+ applied,
639
+ skipped
640
+ });
641
+ });
642
+ }
643
+ async applyOneMigration(dbName, namespace, migration, signal) {
644
+ const statements = [
645
+ ...migration.sql.map(
646
+ (statement) => typeof statement === "string" ? { sql: statement } : statement
647
+ ),
648
+ {
649
+ sql: `INSERT OR REPLACE INTO ${MIGRATIONS_TABLE} (key, namespace, id, applied_at, statement_count) VALUES (?, ?, ?, ?, ?)`,
650
+ params: [
651
+ migrationKey(namespace, migration.id),
652
+ namespace,
653
+ migration.id,
654
+ (/* @__PURE__ */ new Date()).toISOString(),
655
+ migration.sql.length
656
+ ]
657
+ }
658
+ ];
659
+ const result = await this.batchOnDb(dbName, statements, { signal });
660
+ if (!result.ok) return result;
661
+ return ok(void 0);
662
+ }
663
+ async ensureMigrationsTable(dbName, signal) {
664
+ const result = await this.batchOnDb(
665
+ dbName,
666
+ [
667
+ { sql: MIGRATIONS_SCHEMA },
668
+ {
669
+ sql: `INSERT OR REPLACE INTO ${MIGRATIONS_TABLE} (key, namespace, id, applied_at, statement_count) VALUES (?, ?, ?, ?, ?)`,
670
+ params: [
671
+ migrationKey(MIGRATIONS_META_NAMESPACE, MIGRATIONS_META_ID),
672
+ MIGRATIONS_META_NAMESPACE,
673
+ MIGRATIONS_META_ID,
674
+ (/* @__PURE__ */ new Date()).toISOString(),
675
+ 1
676
+ ]
677
+ }
678
+ ],
679
+ { signal }
680
+ );
681
+ if (!result.ok) return result;
682
+ return ok(void 0);
683
+ }
535
684
  // === Private helpers ===
536
685
  async invokeSQL(dbName, actions, body, signal) {
537
686
  const session = this.context.session;
@@ -550,7 +699,7 @@ var SQLService = class extends BaseService {
550
699
  actionForSql(sql, fallback) {
551
700
  const token = firstSqlToken(sql);
552
701
  if (token === "pragma") return SQLAction.ADMIN;
553
- if (token !== void 0 && DDL_TOKENS.has(token)) return SQLAction.DDL;
702
+ if (token !== void 0 && DDL_TOKENS.has(token)) return SQLAction.SCHEMA;
554
703
  return fallback;
555
704
  }
556
705
  actionsForSqlBatch(statements) {
@@ -579,17 +728,19 @@ var SQLService = class extends BaseService {
579
728
  }
580
729
  async handleErrorResponse(response, operation) {
581
730
  const errorText = await response.text();
582
- let errorBody = {};
583
- try {
584
- errorBody = JSON.parse(errorText);
585
- } catch {
586
- }
731
+ const errorBody = parseServiceErrorBody(errorText);
587
732
  const errorCode = this.mapHttpStatusToErrorCode(
588
733
  response.status,
589
734
  errorBody.error
590
735
  );
591
- const message = errorBody.message || `SQL ${operation} failed: ${response.status} - ${errorText}`;
592
- const meta = { status: response.status, statusText: response.statusText };
736
+ const message = formatServiceResponseError(
737
+ "SQL",
738
+ operation,
739
+ response.status,
740
+ errorText,
741
+ errorBody
742
+ );
743
+ const meta = responseErrorMeta(response.status, response.statusText, errorText);
593
744
  if (response.status === 401) {
594
745
  const { resource, action } = parseAuthError(errorText);
595
746
  if (action) meta.requiredAction = action;
@@ -649,9 +800,57 @@ function firstSqlToken(sql) {
649
800
  const match = /^[A-Za-z_]+/.exec(sql.slice(index));
650
801
  return match?.[0].toLowerCase();
651
802
  }
803
+ function validateMigrationOptions(options) {
804
+ if (!options || typeof options !== "object") {
805
+ return "SQL migrations require options";
806
+ }
807
+ if (!options.namespace || typeof options.namespace !== "string") {
808
+ return "SQL migrations require a non-empty namespace";
809
+ }
810
+ if (!Array.isArray(options.migrations)) {
811
+ return "SQL migrations require an ordered migrations array";
812
+ }
813
+ const ids = /* @__PURE__ */ new Set();
814
+ for (const migration of options.migrations) {
815
+ if (!migration.id || typeof migration.id !== "string") {
816
+ return "SQL migrations require every migration to have a non-empty id";
817
+ }
818
+ if (ids.has(migration.id)) {
819
+ return `Duplicate SQL migration id: ${migration.id}`;
820
+ }
821
+ ids.add(migration.id);
822
+ if (!Array.isArray(migration.sql)) {
823
+ return `SQL migration ${migration.id} requires a sql array`;
824
+ }
825
+ for (const statement of migration.sql) {
826
+ if (typeof statement === "string") {
827
+ if (statement.trim() === "") {
828
+ return `SQL migration ${migration.id} contains an empty statement`;
829
+ }
830
+ continue;
831
+ }
832
+ if (!statement || typeof statement.sql !== "string" || statement.sql.trim() === "") {
833
+ return `SQL migration ${migration.id} contains an invalid statement`;
834
+ }
835
+ }
836
+ }
837
+ return null;
838
+ }
839
+ function migrationKey(namespace, id) {
840
+ return `${encodeURIComponent(namespace)}:${encodeURIComponent(id)}`;
841
+ }
842
+ function rowValue(row, index) {
843
+ if (Array.isArray(row)) return row[index];
844
+ if (row && typeof row === "object") {
845
+ const values = Object.values(row);
846
+ return values[index];
847
+ }
848
+ return void 0;
849
+ }
652
850
  export {
653
851
  DatabaseHandle,
654
852
  SQLAction,
853
+ SQLMigrations,
655
854
  SQLService
656
855
  };
657
856
  //# sourceMappingURL=index.js.map