@noormdev/sdk 1.5.1 → 1.6.0

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/index.d.ts CHANGED
@@ -1957,7 +1957,9 @@ declare class ChangeManager {
1957
1957
  * List all changes with status.
1958
1958
  *
1959
1959
  * Merges changes from disk with status from database.
1960
- * Includes orphaned changes (in DB but not on disk).
1960
+ * Includes orphaned changes (in DB but not on disk), and folders on disk
1961
+ * that cannot be parsed: `failed` with the parse error when they never
1962
+ * ran, otherwise their recorded status.
1961
1963
  *
1962
1964
  * @returns Array of changes with merged status
1963
1965
  */
@@ -2585,7 +2587,7 @@ export declare class RunNamespace {
2585
2587
  *
2586
2588
  * @example
2587
2589
  * ```typescript
2588
- * const files = await ctx.noorm.run.discover('sql/')
2590
+ * const files = await ctx.noorm.run.discover('db/sql/')
2589
2591
  * ```
2590
2592
  */
2591
2593
  discover(dirpath?: string): Promise<string[]>;
@@ -2594,7 +2596,7 @@ export declare class RunNamespace {
2594
2596
  *
2595
2597
  * @example
2596
2598
  * ```typescript
2597
- * const results = await ctx.noorm.run.preview(['sql/001.sql', 'sql/002.sql'])
2599
+ * const results = await ctx.noorm.run.preview(['db/sql/001.sql', 'db/sql/002.sql'])
2598
2600
  * ```
2599
2601
  */
2600
2602
  preview(filepaths: string[], output?: string | null): Promise<FileResult[]>;
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ import { __commonJS, __toESM } from './chunk-D2XW2UOP.js';
13
13
  import { existsSync, accessSync, constants, readFileSync, mkdirSync, chmodSync, createWriteStream, writeFileSync, openSync, writeSync, closeSync, unlinkSync, statSync, copyFileSync, fsyncSync, renameSync } from 'fs';
14
14
  import path, { join, dirname, resolve, relative, sep } from 'path';
15
15
  import { homedir, availableParallelism, userInfo } from 'os';
16
- import { stat, mkdir, writeFile, unlink, rename, rm, readdir, readFile, access } from 'fs/promises';
16
+ import { stat, mkdir, writeFile, unlink, rename, rm, readFile, readdir, access } from 'fs/promises';
17
17
  import { createHash, createPrivateKey, createPublicKey, createDecipheriv, randomBytes, createCipheriv, randomUUID, hkdfSync } from 'crypto';
18
18
  import { execSync } from 'child_process';
19
19
  import { sql } from 'kysely';
@@ -1731,9 +1731,12 @@ var DEFAULT_BUILD_CONFIG = {
1731
1731
  include: [],
1732
1732
  exclude: []
1733
1733
  };
1734
+ var DEFAULT_DB_DIR = "db";
1735
+ var DEFAULT_SQL_PATH = `./${DEFAULT_DB_DIR}/sql`;
1736
+ var DEFAULT_CHANGES_PATH = `./${DEFAULT_DB_DIR}/changes`;
1734
1737
  var DEFAULT_PATH_CONFIG = {
1735
- sql: "./sql",
1736
- changes: "./changes"
1738
+ sql: DEFAULT_SQL_PATH,
1739
+ changes: DEFAULT_CHANGES_PATH
1737
1740
  };
1738
1741
  var DEFAULT_STRICT_CONFIG = {
1739
1742
  enabled: false,
@@ -2108,6 +2111,10 @@ var SettingsManager = class {
2108
2111
  /**
2109
2112
  * Initialize a new settings file with defaults.
2110
2113
  *
2114
+ * An overwrite keeps the `paths` already in the file: they say where the
2115
+ * project's files are, not a preference, and resetting them would point a
2116
+ * project laid out elsewhere at directories that do not exist.
2117
+ *
2111
2118
  * @param force - Overwrite existing file if true
2112
2119
  * @throws Error if file exists and force is false
2113
2120
  */
@@ -2116,7 +2123,10 @@ var SettingsManager = class {
2116
2123
  if (fileExists2 && !force) {
2117
2124
  throw new Error("Settings file already exists. Use force=true to overwrite.");
2118
2125
  }
2126
+ const [previous] = fileExists2 ? await attempt(() => this.load()) : [void 0];
2127
+ const keptPaths = previous ? this.#document?.paths : void 0;
2119
2128
  this.#document = createDefaultSettings();
2129
+ if (keptPaths) this.#document.paths = clone(keptPaths);
2120
2130
  this.#refreshResolved();
2121
2131
  this.#loaded = true;
2122
2132
  await this.save();
@@ -4254,7 +4264,7 @@ function buildMssqlTvfWithTvp(rawName, params) {
4254
4264
 
4255
4265
  // src/core/change/types.ts
4256
4266
  function isPendingChange(item) {
4257
- return !item.orphaned && (item.status === "pending" || item.status === "reverted" || item.status === "stale");
4267
+ return !item.orphaned && (item.status === "pending" || item.status === "reverted" || item.status === "stale" || item.status === "failed" && item.isNew);
4258
4268
  }
4259
4269
  var ChangeValidationError = class extends Error {
4260
4270
  constructor(changeName, issue) {
@@ -4340,10 +4350,31 @@ async function parseChange(folderPath, sqlDir) {
4340
4350
  hasChangelog
4341
4351
  };
4342
4352
  }
4353
+ function failureListItem(failure, status) {
4354
+ const { date, description } = parseName(failure.name);
4355
+ return {
4356
+ name: failure.name,
4357
+ path: failure.path,
4358
+ date,
4359
+ description,
4360
+ status: status?.status ?? "failed",
4361
+ appliedAt: status?.appliedAt ?? null,
4362
+ appliedBy: status?.appliedBy ?? null,
4363
+ revertedAt: status?.revertedAt ?? null,
4364
+ errorMessage: status ? status.errorMessage : failure.message,
4365
+ appliedHistoryId: status?.appliedHistoryId ?? null,
4366
+ isNew: !status,
4367
+ orphaned: false
4368
+ };
4369
+ }
4343
4370
  async function discoverChanges(changesDir, sqlDir) {
4371
+ const { changes } = await scanChanges(changesDir, sqlDir);
4372
+ return changes;
4373
+ }
4374
+ async function scanChanges(changesDir, sqlDir) {
4344
4375
  const [exists] = await attempt(() => stat(changesDir));
4345
4376
  if (!exists) {
4346
- return [];
4377
+ return { changes: [], failures: [] };
4347
4378
  }
4348
4379
  const [entries, readErr] = await attempt(() => readdir(changesDir, { withFileTypes: true }));
4349
4380
  if (readErr) {
@@ -4352,12 +4383,14 @@ async function discoverChanges(changesDir, sqlDir) {
4352
4383
  error: readErr,
4353
4384
  context: { changesDir, operation: "discover" }
4354
4385
  });
4355
- return [];
4386
+ return { changes: [], failures: [] };
4356
4387
  }
4357
4388
  const folders = entries.filter((e) => e.isDirectory());
4358
4389
  const changes = [];
4390
+ const failures = [];
4359
4391
  for (const folder of folders) {
4360
4392
  const folderPath = path.join(changesDir, folder.name);
4393
+ if (!await looksLikeChange(folderPath)) continue;
4361
4394
  const [change, parseErr] = await attempt(() => parseChange(folderPath, sqlDir));
4362
4395
  if (parseErr) {
4363
4396
  observer.emit("error", {
@@ -4365,11 +4398,15 @@ async function discoverChanges(changesDir, sqlDir) {
4365
4398
  error: parseErr,
4366
4399
  context: { folder: folder.name, operation: "parse" }
4367
4400
  });
4401
+ failures.push({ name: folder.name, path: folderPath, message: parseErr.message });
4368
4402
  continue;
4369
4403
  }
4370
4404
  changes.push(change);
4371
4405
  }
4372
- return changes.sort((a, b) => a.name.localeCompare(b.name));
4406
+ return {
4407
+ changes: changes.sort((a, b) => a.name.localeCompare(b.name)),
4408
+ failures
4409
+ };
4373
4410
  }
4374
4411
  async function resolveManifest(manifestPath, sqlDir) {
4375
4412
  const [content, readErr] = await attempt(() => readFile(manifestPath, "utf-8"));
@@ -4428,6 +4465,13 @@ function parseName(name) {
4428
4465
  }
4429
4466
  return { date: null, description: name };
4430
4467
  }
4468
+ async function looksLikeChange(folderPath) {
4469
+ for (const subfolder of ["change", "revert"]) {
4470
+ const [entry] = await attempt(() => stat(path.join(folderPath, subfolder)));
4471
+ if (entry?.isDirectory()) return true;
4472
+ }
4473
+ return false;
4474
+ }
4431
4475
  async function scanFolder(folderPath, sqlDir) {
4432
4476
  const [entries, err] = await attempt(() => readdir(folderPath, { withFileTypes: true }));
4433
4477
  if (err) {
@@ -6319,6 +6363,165 @@ var StatementWatcher = class {
6319
6363
  }
6320
6364
  };
6321
6365
 
6366
+ // src/core/runner/sqlite-statements.ts
6367
+ var QUOTE_PAIRS = {
6368
+ "'": "'",
6369
+ '"': '"',
6370
+ "`": "`",
6371
+ "[": "]"
6372
+ };
6373
+ function keywordAt(content, index, keyword) {
6374
+ const end = index + keyword.length;
6375
+ if (content.slice(index, end).toUpperCase() !== keyword) return false;
6376
+ const before = index > 0 ? content[index - 1] : " ";
6377
+ const after = end < content.length ? content[end] : " ";
6378
+ return !/[A-Za-z0-9_$]/.test(before) && !/[A-Za-z0-9_$]/.test(after);
6379
+ }
6380
+ function splitSqliteStatements(content) {
6381
+ const statements = [];
6382
+ let current = "";
6383
+ let closingQuote = null;
6384
+ let inLineComment = false;
6385
+ let inBlockComment = false;
6386
+ let inTrigger = false;
6387
+ let blockDepth = 0;
6388
+ let i = 0;
6389
+ const flush = () => {
6390
+ const trimmed = current.trim();
6391
+ if (trimmed.length > 0 && !isCommentOnly(trimmed)) {
6392
+ statements.push(trimmed);
6393
+ }
6394
+ current = "";
6395
+ };
6396
+ while (i < content.length) {
6397
+ const char = content[i];
6398
+ if (inLineComment) {
6399
+ current += char;
6400
+ if (char === "\n") inLineComment = false;
6401
+ i++;
6402
+ continue;
6403
+ }
6404
+ if (inBlockComment) {
6405
+ current += char;
6406
+ if (char === "*" && content[i + 1] === "/") {
6407
+ current += "/";
6408
+ inBlockComment = false;
6409
+ i += 2;
6410
+ continue;
6411
+ }
6412
+ i++;
6413
+ continue;
6414
+ }
6415
+ if (closingQuote !== null) {
6416
+ current += char;
6417
+ if (char === closingQuote) {
6418
+ if (closingQuote !== "]" && content[i + 1] === closingQuote) {
6419
+ current += closingQuote;
6420
+ i += 2;
6421
+ continue;
6422
+ }
6423
+ closingQuote = null;
6424
+ }
6425
+ i++;
6426
+ continue;
6427
+ }
6428
+ if (char === "-" && content[i + 1] === "-") {
6429
+ inLineComment = true;
6430
+ current += "--";
6431
+ i += 2;
6432
+ continue;
6433
+ }
6434
+ if (char === "/" && content[i + 1] === "*") {
6435
+ inBlockComment = true;
6436
+ current += "/*";
6437
+ i += 2;
6438
+ continue;
6439
+ }
6440
+ if (QUOTE_PAIRS[char]) {
6441
+ closingQuote = QUOTE_PAIRS[char];
6442
+ current += char;
6443
+ i++;
6444
+ continue;
6445
+ }
6446
+ if (!inTrigger && keywordAt(content, i, "TRIGGER")) {
6447
+ inTrigger = true;
6448
+ current += content.slice(i, i + 7);
6449
+ i += 7;
6450
+ continue;
6451
+ }
6452
+ if (inTrigger && (keywordAt(content, i, "BEGIN") || keywordAt(content, i, "CASE"))) {
6453
+ const length = keywordAt(content, i, "BEGIN") ? 5 : 4;
6454
+ blockDepth++;
6455
+ current += content.slice(i, i + length);
6456
+ i += length;
6457
+ continue;
6458
+ }
6459
+ if (inTrigger && blockDepth > 0 && keywordAt(content, i, "END")) {
6460
+ blockDepth--;
6461
+ if (blockDepth === 0) inTrigger = false;
6462
+ current += content.slice(i, i + 3);
6463
+ i += 3;
6464
+ continue;
6465
+ }
6466
+ if (char === ";" && blockDepth === 0) {
6467
+ current += char;
6468
+ inTrigger = false;
6469
+ flush();
6470
+ i++;
6471
+ continue;
6472
+ }
6473
+ current += char;
6474
+ i++;
6475
+ }
6476
+ flush();
6477
+ return statements;
6478
+ }
6479
+ function isCommentOnly(fragment) {
6480
+ const withoutBlockComments = fragment.replace(/\/\*[\s\S]*?\*\//g, "");
6481
+ return withoutBlockComments.split("\n").every((line) => {
6482
+ const trimmed = line.trim();
6483
+ return trimmed.length === 0 || trimmed.startsWith("--");
6484
+ });
6485
+ }
6486
+
6487
+ // src/core/runner/mssql-batches.ts
6488
+ function splitMssqlBatches(content) {
6489
+ const batches = content.split(/^\s*GO\s*$/mi);
6490
+ return batches.map((b) => b.trim()).filter((b) => b.length > 0 && !isCommentOnly2(b));
6491
+ }
6492
+ function isCommentOnly2(batch) {
6493
+ for (const line of batch.split("\n")) {
6494
+ const trimmed = line.trim();
6495
+ if (trimmed.length > 0 && !trimmed.startsWith("--")) {
6496
+ return false;
6497
+ }
6498
+ }
6499
+ return true;
6500
+ }
6501
+ async function executeSqlBody(context, sqlContent) {
6502
+ if (context.dialect === "mssql") {
6503
+ return executeFragments(context, splitMssqlBatches(sqlContent), "batch");
6504
+ }
6505
+ if (context.dialect === "sqlite") {
6506
+ return executeFragments(context, splitSqliteStatements(sqlContent), "statement");
6507
+ }
6508
+ const [, execErr] = await attempt(() => sql.raw(sqlContent).execute(context.db));
6509
+ if (execErr) {
6510
+ return getSqlErrorMessage(execErr);
6511
+ }
6512
+ return null;
6513
+ }
6514
+ async function executeFragments(context, fragments, label) {
6515
+ for (let i = 0; i < fragments.length; i++) {
6516
+ const [, execErr] = await attempt(() => sql.raw(fragments[i]).execute(context.db));
6517
+ if (execErr) {
6518
+ const message = getSqlErrorMessage(execErr);
6519
+ return fragments.length === 1 ? message : `[${label} ${i + 1} of ${fragments.length}] ${message}`;
6520
+ }
6521
+ }
6522
+ return null;
6523
+ }
6524
+
6322
6525
  // src/core/lock/types.ts
6323
6526
  var DEFAULT_LOCK_OPTIONS = {
6324
6527
  dialect: "postgres",
@@ -7066,12 +7269,12 @@ async function runFileBatch(context, change, expandedFiles, direction, checksum,
7066
7269
  }
7067
7270
  break;
7068
7271
  }
7069
- const [, execErr] = await attempt(
7070
- () => watcher.run(file.path, executor, (conn) => sql.raw(sqlContent).execute(conn))
7272
+ const [execMessage, execErr] = await attempt(
7273
+ () => watcher.run(file.path, executor, (conn) => executeSqlBody({ db: conn, dialect: context.dialect }, sqlContent))
7071
7274
  );
7072
7275
  const durationMs = performance.now() - fileStart;
7073
- if (execErr) {
7074
- const errorMessage2 = getSqlErrorMessage(execErr);
7276
+ const errorMessage2 = execErr ? getSqlErrorMessage(execErr) : execMessage;
7277
+ if (errorMessage2) {
7075
7278
  failed = true;
7076
7279
  failedFile = file.path;
7077
7280
  failureError = errorMessage2;
@@ -7409,17 +7612,31 @@ var ChangeManager = class {
7409
7612
  * List all changes with status.
7410
7613
  *
7411
7614
  * Merges changes from disk with status from database.
7412
- * Includes orphaned changes (in DB but not on disk).
7615
+ * Includes orphaned changes (in DB but not on disk), and folders on disk
7616
+ * that cannot be parsed: `failed` with the parse error when they never
7617
+ * ran, otherwise their recorded status.
7413
7618
  *
7414
7619
  * @returns Array of changes with merged status
7415
7620
  */
7416
7621
  async list() {
7417
- const diskChanges = await discoverChanges(
7622
+ const { items } = await this.#scan();
7623
+ return items;
7624
+ }
7625
+ /**
7626
+ * Merge disk and database state, and report the folders that cannot be read.
7627
+ *
7628
+ * An unreadable folder that a batch would run (never run, or `reverted` or
7629
+ * `stale`) is pending work: the batch fails at its position rather than
7630
+ * skipping it and running later changes out of order. `unreadable` holds the
7631
+ * applied ones; they keep their status and only warrant a warning.
7632
+ */
7633
+ async #scan() {
7634
+ const { changes: diskChanges, failures } = await scanChanges(
7418
7635
  this.#context.changesDir,
7419
7636
  this.#context.sqlDir
7420
7637
  );
7421
7638
  const statuses = await this.#history.getAllStatuses();
7422
- const diskNames = new Set(diskChanges.map((cs) => cs.name));
7639
+ const diskNames = new Set([...diskChanges, ...failures].map((cs) => cs.name));
7423
7640
  const orphanedNames = await this.#history.getOrphaned(diskNames);
7424
7641
  const result = [];
7425
7642
  for (const cs of diskChanges) {
@@ -7437,6 +7654,15 @@ var ChangeManager = class {
7437
7654
  orphaned: false
7438
7655
  });
7439
7656
  }
7657
+ const unreadable = [];
7658
+ for (const failure of failures) {
7659
+ const status = statuses.get(failure.name);
7660
+ const item = failureListItem(failure, status);
7661
+ if (!isPendingChange(item)) {
7662
+ unreadable.push(failure);
7663
+ }
7664
+ result.push(item);
7665
+ }
7440
7666
  for (const name of orphanedNames) {
7441
7667
  const status = statuses.get(name);
7442
7668
  result.push({
@@ -7458,7 +7684,10 @@ var ChangeManager = class {
7458
7684
  hasChangelog: void 0
7459
7685
  });
7460
7686
  }
7461
- return result.sort((a, b) => a.name.localeCompare(b.name));
7687
+ return {
7688
+ items: result.sort((a, b) => a.name.localeCompare(b.name)),
7689
+ unreadable
7690
+ };
7462
7691
  }
7463
7692
  // ─────────────────────────────────────────────────────────
7464
7693
  // Run Single Change
@@ -7513,9 +7742,13 @@ var ChangeManager = class {
7513
7742
  async next(count = 1, options = {}) {
7514
7743
  const opts = { ...DEFAULT_BATCH, ...options };
7515
7744
  const start = performance.now();
7516
- const warnings = await this.#collectWarnings();
7517
- const list = await this.list();
7518
- const pending = list.filter(isPendingChange).slice(0, count);
7745
+ const { items, unreadable } = await this.#scan();
7746
+ const messages = [
7747
+ ...await this.#collectWarnings() ?? [],
7748
+ ...unreadable.map((f) => `Change '${f.name}' is applied but its files can no longer be read: ${f.message}`)
7749
+ ];
7750
+ const warnings = messages.length > 0 ? messages : void 0;
7751
+ const pending = items.filter(isPendingChange).slice(0, count);
7519
7752
  if (pending.length === 0) {
7520
7753
  return {
7521
7754
  status: "success",
@@ -7583,9 +7816,7 @@ var ChangeManager = class {
7583
7816
  * @returns Batch result
7584
7817
  */
7585
7818
  async ff(options = {}) {
7586
- const list = await this.list();
7587
- const pendingCount = list.filter(isPendingChange).length;
7588
- return this.next(pendingCount, options);
7819
+ return this.next(Number.POSITIVE_INFINITY, options);
7589
7820
  }
7590
7821
  /**
7591
7822
  * Rewind: revert changes in reverse order.
@@ -7754,6 +7985,8 @@ var ChangeManager = class {
7754
7985
  () => parseChange(changePath, this.#context.sqlDir)
7755
7986
  );
7756
7987
  if (err) {
7988
+ const [onDisk] = await attempt(() => stat(changePath));
7989
+ if (onDisk) throw err;
7757
7990
  const status = await this.#history.getStatus(name);
7758
7991
  if (status) {
7759
7992
  throw new ChangeOrphanedError(name);
@@ -8379,13 +8612,13 @@ var ChangesNamespace = class {
8379
8612
  get #changesDir() {
8380
8613
  return path.join(
8381
8614
  this.#state.projectRoot,
8382
- this.#state.settings.paths?.changes ?? "changes"
8615
+ this.#state.settings.paths?.changes ?? DEFAULT_CHANGES_PATH
8383
8616
  );
8384
8617
  }
8385
8618
  get #sqlDir() {
8386
8619
  return path.join(
8387
8620
  this.#state.projectRoot,
8388
- this.#state.settings.paths?.sql ?? "sql"
8621
+ this.#state.settings.paths?.sql ?? DEFAULT_SQL_PATH
8389
8622
  );
8390
8623
  }
8391
8624
  get #kysely() {
@@ -8418,167 +8651,6 @@ var ChangesNamespace = class {
8418
8651
  return this.#manager;
8419
8652
  }
8420
8653
  };
8421
-
8422
- // src/core/runner/sqlite-statements.ts
8423
- var QUOTE_PAIRS = {
8424
- "'": "'",
8425
- '"': '"',
8426
- "`": "`",
8427
- "[": "]"
8428
- };
8429
- function keywordAt(content, index, keyword) {
8430
- const end = index + keyword.length;
8431
- if (content.slice(index, end).toUpperCase() !== keyword) return false;
8432
- const before = index > 0 ? content[index - 1] : " ";
8433
- const after = end < content.length ? content[end] : " ";
8434
- return !/[A-Za-z0-9_$]/.test(before) && !/[A-Za-z0-9_$]/.test(after);
8435
- }
8436
- function splitSqliteStatements(content) {
8437
- const statements = [];
8438
- let current = "";
8439
- let closingQuote = null;
8440
- let inLineComment = false;
8441
- let inBlockComment = false;
8442
- let inTrigger = false;
8443
- let blockDepth = 0;
8444
- let i = 0;
8445
- const flush = () => {
8446
- const trimmed = current.trim();
8447
- if (trimmed.length > 0 && !isCommentOnly(trimmed)) {
8448
- statements.push(trimmed);
8449
- }
8450
- current = "";
8451
- };
8452
- while (i < content.length) {
8453
- const char = content[i];
8454
- if (inLineComment) {
8455
- current += char;
8456
- if (char === "\n") inLineComment = false;
8457
- i++;
8458
- continue;
8459
- }
8460
- if (inBlockComment) {
8461
- current += char;
8462
- if (char === "*" && content[i + 1] === "/") {
8463
- current += "/";
8464
- inBlockComment = false;
8465
- i += 2;
8466
- continue;
8467
- }
8468
- i++;
8469
- continue;
8470
- }
8471
- if (closingQuote !== null) {
8472
- current += char;
8473
- if (char === closingQuote) {
8474
- if (closingQuote !== "]" && content[i + 1] === closingQuote) {
8475
- current += closingQuote;
8476
- i += 2;
8477
- continue;
8478
- }
8479
- closingQuote = null;
8480
- }
8481
- i++;
8482
- continue;
8483
- }
8484
- if (char === "-" && content[i + 1] === "-") {
8485
- inLineComment = true;
8486
- current += "--";
8487
- i += 2;
8488
- continue;
8489
- }
8490
- if (char === "/" && content[i + 1] === "*") {
8491
- inBlockComment = true;
8492
- current += "/*";
8493
- i += 2;
8494
- continue;
8495
- }
8496
- if (QUOTE_PAIRS[char]) {
8497
- closingQuote = QUOTE_PAIRS[char];
8498
- current += char;
8499
- i++;
8500
- continue;
8501
- }
8502
- if (!inTrigger && keywordAt(content, i, "TRIGGER")) {
8503
- inTrigger = true;
8504
- current += content.slice(i, i + 7);
8505
- i += 7;
8506
- continue;
8507
- }
8508
- if (inTrigger && (keywordAt(content, i, "BEGIN") || keywordAt(content, i, "CASE"))) {
8509
- const length = keywordAt(content, i, "BEGIN") ? 5 : 4;
8510
- blockDepth++;
8511
- current += content.slice(i, i + length);
8512
- i += length;
8513
- continue;
8514
- }
8515
- if (inTrigger && blockDepth > 0 && keywordAt(content, i, "END")) {
8516
- blockDepth--;
8517
- if (blockDepth === 0) inTrigger = false;
8518
- current += content.slice(i, i + 3);
8519
- i += 3;
8520
- continue;
8521
- }
8522
- if (char === ";" && blockDepth === 0) {
8523
- current += char;
8524
- inTrigger = false;
8525
- flush();
8526
- i++;
8527
- continue;
8528
- }
8529
- current += char;
8530
- i++;
8531
- }
8532
- flush();
8533
- return statements;
8534
- }
8535
- function isCommentOnly(fragment) {
8536
- const withoutBlockComments = fragment.replace(/\/\*[\s\S]*?\*\//g, "");
8537
- return withoutBlockComments.split("\n").every((line) => {
8538
- const trimmed = line.trim();
8539
- return trimmed.length === 0 || trimmed.startsWith("--");
8540
- });
8541
- }
8542
-
8543
- // src/core/runner/mssql-batches.ts
8544
- function splitMssqlBatches(content) {
8545
- const batches = content.split(/^\s*GO\s*$/mi);
8546
- return batches.map((b) => b.trim()).filter((b) => b.length > 0 && !isCommentOnly2(b));
8547
- }
8548
- function isCommentOnly2(batch) {
8549
- for (const line of batch.split("\n")) {
8550
- const trimmed = line.trim();
8551
- if (trimmed.length > 0 && !trimmed.startsWith("--")) {
8552
- return false;
8553
- }
8554
- }
8555
- return true;
8556
- }
8557
- async function executeSqlBody(context, sqlContent) {
8558
- if (context.dialect === "mssql") {
8559
- return executeFragments(context, splitMssqlBatches(sqlContent), "batch");
8560
- }
8561
- if (context.dialect === "sqlite") {
8562
- return executeFragments(context, splitSqliteStatements(sqlContent), "statement");
8563
- }
8564
- const [, execErr] = await attempt(() => sql.raw(sqlContent).execute(context.db));
8565
- if (execErr) {
8566
- return getSqlErrorMessage(execErr);
8567
- }
8568
- return null;
8569
- }
8570
- async function executeFragments(context, fragments, label) {
8571
- for (let i = 0; i < fragments.length; i++) {
8572
- const [, execErr] = await attempt(() => sql.raw(fragments[i]).execute(context.db));
8573
- if (execErr) {
8574
- const message = getSqlErrorMessage(execErr);
8575
- return fragments.length === 1 ? message : `[${label} ${i + 1} of ${fragments.length}] ${message}`;
8576
- }
8577
- }
8578
- return null;
8579
- }
8580
-
8581
- // src/core/runner/runner.ts
8582
8654
  var SQL_EXTENSIONS2 = [".sql", ".sql.tmpl"];
8583
8655
  var FILE_HEADER_TEMPLATE = `-- ============================================================
8584
8656
  -- File: %FILE%
@@ -8918,8 +8990,8 @@ async function prepareFiles(context, files) {
8918
8990
  for (const file of files) {
8919
8991
  const relFilepath = path.relative(context.projectRoot, file.path);
8920
8992
  const [loaded, loadError] = await attempt(async () => {
8921
- const sql28 = await loadAndRenderFile2(context, file.path);
8922
- return { sql: sql28, checksum: computeChecksumFromContent(sql28) };
8993
+ const sql27 = await loadAndRenderFile2(context, file.path);
8994
+ return { sql: sql27, checksum: computeChecksumFromContent(sql27) };
8923
8995
  });
8924
8996
  if (loadError) {
8925
8997
  prepared.push({ file, relFilepath, checksum: "", sql: "", loadError });
@@ -9205,12 +9277,12 @@ var RunNamespace = class {
9205
9277
  *
9206
9278
  * @example
9207
9279
  * ```typescript
9208
- * const files = await ctx.noorm.run.discover('sql/')
9280
+ * const files = await ctx.noorm.run.discover('db/sql/')
9209
9281
  * ```
9210
9282
  */
9211
9283
  async discover(dirpath) {
9212
9284
  const absolutePath = this.#resolvePath(
9213
- dirpath ?? this.#state.settings.paths?.sql ?? "sql"
9285
+ dirpath ?? this.#state.settings.paths?.sql ?? DEFAULT_SQL_PATH
9214
9286
  );
9215
9287
  return discoverFiles(absolutePath);
9216
9288
  }
@@ -9222,7 +9294,7 @@ var RunNamespace = class {
9222
9294
  *
9223
9295
  * @example
9224
9296
  * ```typescript
9225
- * const results = await ctx.noorm.run.preview(['sql/001.sql', 'sql/002.sql'])
9297
+ * const results = await ctx.noorm.run.preview(['db/sql/001.sql', 'db/sql/002.sql'])
9226
9298
  * ```
9227
9299
  */
9228
9300
  async preview(filepaths, output) {
@@ -9299,7 +9371,7 @@ var RunNamespace = class {
9299
9371
  const context = await this.#createRunContext();
9300
9372
  const sqlPath = path.join(
9301
9373
  this.#state.projectRoot,
9302
- this.#state.settings.paths?.sql ?? "sql"
9374
+ this.#state.settings.paths?.sql ?? DEFAULT_SQL_PATH
9303
9375
  );
9304
9376
  const configForMatch = {
9305
9377
  name: this.#state.config.name,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noormdev/sdk",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "Database schema & changeset manager SDK",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",