@lark-apaas/db-schema-sync 0.1.21 → 0.1.22-alpha.20261015232825

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/bin.d.ts CHANGED
@@ -1 +1,4 @@
1
1
  #!/usr/bin/env node
2
+ declare function main(argv?: string[]): Promise<void>;
3
+
4
+ export { main };
package/dist/bin.js CHANGED
@@ -4,10 +4,11 @@ import {
4
4
  __commonJS,
5
5
  __require,
6
6
  __toESM,
7
+ checkSchemaConsumerCompatibility,
7
8
  generateSchema,
8
9
  resolveEnvOptions,
9
10
  validateGeneratedSchema
10
- } from "./chunk-T3RIJCQD.js";
11
+ } from "./chunk-EVOKG4DJ.js";
11
12
 
12
13
  // node_modules/dotenv/package.json
13
14
  var require_package = __commonJS({
@@ -406,8 +407,9 @@ var require_main = __commonJS({
406
407
 
407
408
  // src/bin.ts
408
409
  var import_dotenv = __toESM(require_main(), 1);
409
- import { resolve, dirname } from "path";
410
- import { writeFileSync, mkdirSync, existsSync } from "fs";
410
+ import { existsSync as existsSync2, realpathSync } from "fs";
411
+ import { resolve as resolve2 } from "path";
412
+ import { fileURLToPath } from "url";
411
413
 
412
414
  // src/cli-args.ts
413
415
  function parseArgs(argv) {
@@ -428,6 +430,215 @@ function parseArgs(argv) {
428
430
  };
429
431
  }
430
432
 
433
+ // src/safe-writer.ts
434
+ import { createHash, randomUUID } from "crypto";
435
+ import {
436
+ existsSync,
437
+ mkdirSync,
438
+ readFileSync,
439
+ renameSync,
440
+ rmSync,
441
+ writeFileSync
442
+ } from "fs";
443
+ import { basename, dirname, join, resolve } from "path";
444
+ var SCHEMA_SYNC_CONTRACT = "db-schema-sync.safe-write.v1";
445
+ var MAX_RESULT_MESSAGE_LENGTH = 500;
446
+ function boundedMessage(message) {
447
+ const normalized = message.replace(/\s+/g, " ").trim();
448
+ return normalized.length > MAX_RESULT_MESSAGE_LENGTH ? `${normalized.slice(0, MAX_RESULT_MESSAGE_LENGTH)}...` : normalized;
449
+ }
450
+ function messageFrom(error) {
451
+ return boundedMessage(error instanceof Error ? error.message : String(error));
452
+ }
453
+ function sha256(bytes) {
454
+ return createHash("sha256").update(bytes).digest("hex");
455
+ }
456
+ function readTargetSnapshot(outputPath) {
457
+ if (!existsSync(outputPath)) {
458
+ return { bytes: null, sha: null };
459
+ }
460
+ const bytes = readFileSync(outputPath);
461
+ return { bytes, sha: sha256(bytes) };
462
+ }
463
+ function makeResult(status, initial, params = {}) {
464
+ return {
465
+ contract: SCHEMA_SYNC_CONTRACT,
466
+ ok: params.ok ?? false,
467
+ status,
468
+ changed: params.changed ?? false,
469
+ targetShaBefore: initial?.sha ?? null,
470
+ targetShaAfter: params.targetShaAfter === void 0 ? initial?.sha ?? null : params.targetShaAfter,
471
+ removedExportCount: params.removedExportCount ?? 0,
472
+ newDiagnosticCount: params.newDiagnosticCount ?? 0,
473
+ diagnosticSummary: params.diagnosticSummary ?? [],
474
+ ...params.message ? { message: boundedMessage(params.message) } : {}
475
+ };
476
+ }
477
+ function tempPathFor(outputPath) {
478
+ return join(
479
+ dirname(outputPath),
480
+ `.${basename(outputPath)}.${process.pid}.${randomUUID()}.tmp.ts`
481
+ );
482
+ }
483
+ function hasGeneratedDatabaseObjects(source) {
484
+ return /\bexport\s+const\s+[A-Za-z_$][\w$]*\s*=\s*(?:pgTable|pgView|pgMaterializedView)\s*\(/.test(
485
+ source
486
+ );
487
+ }
488
+ function validationFailureMessage(validation) {
489
+ return `Generated schema validation failed (${validation.errorType}): ${validation.message ?? validation.status}`;
490
+ }
491
+ function compatibilityFailureResult(initial, compatibility) {
492
+ const common = {
493
+ removedExportCount: compatibility.removedExportCount,
494
+ newDiagnosticCount: compatibility.newDiagnosticCount,
495
+ diagnosticSummary: compatibility.diagnosticSummary,
496
+ message: compatibility.message ?? "Candidate schema consumer compatibility check failed"
497
+ };
498
+ return compatibility.status === "incompatible" ? makeResult("schema_consumer_incompatible", initial, common) : makeResult("schema_compatibility_unavailable", initial, common);
499
+ }
500
+ function writeSchemaFileSafely(options) {
501
+ const cwd = resolve(options.cwd ?? process.cwd());
502
+ const outputPath = resolve(cwd, options.outputPath);
503
+ let initial;
504
+ try {
505
+ initial = readTargetSnapshot(outputPath);
506
+ } catch (error) {
507
+ return makeResult("schema_target_read_failed", void 0, {
508
+ message: messageFrom(error)
509
+ });
510
+ }
511
+ try {
512
+ mkdirSync(dirname(outputPath), { recursive: true });
513
+ } catch (error) {
514
+ return makeResult("schema_candidate_write_failed", initial, {
515
+ message: messageFrom(error)
516
+ });
517
+ }
518
+ const tempPath = options.controls?.createTempPath?.(outputPath) ?? tempPathFor(outputPath);
519
+ const validate = options.validate ?? validateGeneratedSchema;
520
+ const checkCompatibility = options.controls?.checkCompatibility ?? checkSchemaConsumerCompatibility;
521
+ const rename = options.controls?.rename ?? renameSync;
522
+ let outcome;
523
+ const evaluateCandidate = () => {
524
+ try {
525
+ writeFileSync(tempPath, options.schema, "utf8");
526
+ } catch (error) {
527
+ return makeResult("schema_candidate_write_failed", initial, {
528
+ message: messageFrom(error)
529
+ });
530
+ }
531
+ let validation;
532
+ try {
533
+ validation = validate({
534
+ input: tempPath,
535
+ project: options.project,
536
+ cwd
537
+ });
538
+ } catch (error) {
539
+ return makeResult("schema_candidate_validation_unavailable", initial, {
540
+ message: messageFrom(error)
541
+ });
542
+ }
543
+ if (!validation.ok) {
544
+ const status = validation.status === "invalid_artifact" ? "schema_candidate_invalid" : "schema_candidate_validation_unavailable";
545
+ return makeResult(status, initial, {
546
+ message: validationFailureMessage(validation)
547
+ });
548
+ }
549
+ const oldSource = initial.bytes?.toString("utf8");
550
+ if (oldSource !== void 0 && hasGeneratedDatabaseObjects(oldSource) && !hasGeneratedDatabaseObjects(options.schema)) {
551
+ return makeResult("schema_zero_objects", initial, {
552
+ message: `Refusing to overwrite existing schema with a zero-object candidate: ${options.displayPath}`
553
+ });
554
+ }
555
+ if (initial.bytes?.equals(Buffer.from(options.schema, "utf8"))) {
556
+ try {
557
+ options.controls?.beforeFinalTargetRead?.();
558
+ const current2 = readTargetSnapshot(outputPath);
559
+ if (current2.sha !== initial.sha) {
560
+ return makeResult("schema_target_changed", initial, {
561
+ targetShaAfter: current2.sha,
562
+ message: "Schema target changed after validation; the candidate was not installed"
563
+ });
564
+ }
565
+ return makeResult("schema_unchanged", initial, {
566
+ ok: true,
567
+ targetShaAfter: current2.sha
568
+ });
569
+ } catch (error) {
570
+ return makeResult("schema_target_read_failed", initial, {
571
+ message: messageFrom(error)
572
+ });
573
+ }
574
+ }
575
+ let compatibility;
576
+ if (oldSource !== void 0) {
577
+ try {
578
+ compatibility = checkCompatibility({
579
+ cwd,
580
+ targetPath: outputPath,
581
+ oldSource,
582
+ candidateSource: options.schema,
583
+ project: options.project
584
+ });
585
+ } catch (error) {
586
+ return makeResult("schema_compatibility_unavailable", initial, {
587
+ message: messageFrom(error)
588
+ });
589
+ }
590
+ if (!compatibility.ok) {
591
+ return compatibilityFailureResult(initial, compatibility);
592
+ }
593
+ }
594
+ let current;
595
+ try {
596
+ options.controls?.beforeFinalTargetRead?.();
597
+ current = readTargetSnapshot(outputPath);
598
+ } catch (error) {
599
+ return makeResult("schema_target_read_failed", initial, {
600
+ removedExportCount: compatibility?.removedExportCount,
601
+ message: messageFrom(error)
602
+ });
603
+ }
604
+ if (current.sha !== initial.sha) {
605
+ return makeResult("schema_target_changed", initial, {
606
+ targetShaAfter: current.sha,
607
+ removedExportCount: compatibility?.removedExportCount,
608
+ message: "Schema target changed after validation; the candidate was not installed"
609
+ });
610
+ }
611
+ try {
612
+ rename(tempPath, outputPath);
613
+ } catch (error) {
614
+ return makeResult("schema_replace_failed", initial, {
615
+ removedExportCount: compatibility?.removedExportCount,
616
+ message: messageFrom(error)
617
+ });
618
+ }
619
+ return makeResult("schema_written", initial, {
620
+ ok: true,
621
+ changed: true,
622
+ targetShaAfter: sha256(Buffer.from(options.schema, "utf8")),
623
+ removedExportCount: compatibility?.removedExportCount
624
+ });
625
+ };
626
+ outcome = evaluateCandidate();
627
+ try {
628
+ if (existsSync(tempPath)) {
629
+ rmSync(tempPath, { force: true });
630
+ }
631
+ } catch (error) {
632
+ outcome = makeResult("schema_cleanup_failed", initial, {
633
+ removedExportCount: outcome.removedExportCount,
634
+ newDiagnosticCount: outcome.newDiagnosticCount,
635
+ diagnosticSummary: outcome.diagnosticSummary,
636
+ message: `Temporary candidate cleanup failed: ${messageFrom(error)}`
637
+ });
638
+ }
639
+ return outcome;
640
+ }
641
+
431
642
  // src/bin.ts
432
643
  function getValidateExitCode(result) {
433
644
  if (result.ok) return 0;
@@ -435,8 +646,38 @@ function getValidateExitCode(result) {
435
646
  if (result.status === "inconclusive") return 3;
436
647
  return 1;
437
648
  }
438
- async function main() {
439
- const cliArgs = parseArgs(process.argv);
649
+ function boundedMessage2(error) {
650
+ const message = (error instanceof Error ? error.message : String(error)).replace(/\s+/g, " ").trim();
651
+ return message.length > 500 ? `${message.slice(0, 500)}...` : message;
652
+ }
653
+ function generationFailure(error) {
654
+ return {
655
+ contract: SCHEMA_SYNC_CONTRACT,
656
+ ok: false,
657
+ status: "schema_generation_failed",
658
+ changed: false,
659
+ targetShaBefore: null,
660
+ targetShaAfter: null,
661
+ removedExportCount: 0,
662
+ newDiagnosticCount: 0,
663
+ diagnosticSummary: [],
664
+ message: boundedMessage2(error)
665
+ };
666
+ }
667
+ function emitSchemaSyncResult(result) {
668
+ console.log(JSON.stringify(result));
669
+ }
670
+ function isDirectExecution() {
671
+ const entryPath = process.argv[1];
672
+ if (!entryPath) return false;
673
+ try {
674
+ return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(resolve2(entryPath));
675
+ } catch {
676
+ return false;
677
+ }
678
+ }
679
+ async function main(argv = process.argv) {
680
+ const cliArgs = parseArgs(argv);
440
681
  if (cliArgs.command === "validate") {
441
682
  if (!cliArgs.input) {
442
683
  const output3 = {
@@ -447,18 +688,20 @@ async function main() {
447
688
  message: "--input is required for validate command"
448
689
  };
449
690
  console.log(JSON.stringify(output3));
450
- process.exit(1);
691
+ process.exitCode = 1;
692
+ return;
451
693
  }
452
694
  const output2 = validateGeneratedSchema({
453
695
  input: cliArgs.input,
454
696
  project: cliArgs.project
455
697
  });
456
698
  console.log(JSON.stringify(output2));
457
- process.exit(getValidateExitCode(output2));
699
+ process.exitCode = getValidateExitCode(output2);
700
+ return;
458
701
  }
459
702
  for (const name of [".env.local", ".env"]) {
460
- const envPath = resolve(process.cwd(), name);
461
- if (existsSync(envPath)) {
703
+ const envPath = resolve2(process.cwd(), name);
704
+ if (existsSync2(envPath)) {
462
705
  (0, import_dotenv.config)({ path: envPath });
463
706
  console.log(`[db-schema-sync] \u2713 Loaded ${name}`);
464
707
  }
@@ -474,21 +717,45 @@ async function main() {
474
717
  ...options,
475
718
  exportCustomTypes: cliArgs.exportCustomTypes
476
719
  });
477
- if (!schema.includes("pgTable") && !schema.includes("pgView")) {
720
+ if (!hasGeneratedDatabaseObjects(schema)) {
478
721
  console.warn(
479
722
  "[db-schema-sync] \u26A0 Warning: No tables or views found in database. The generated schema will be empty."
480
723
  );
481
724
  }
482
- const outputPath = resolve(process.cwd(), output);
483
- mkdirSync(dirname(outputPath), { recursive: true });
484
- writeFileSync(outputPath, schema, "utf8");
485
- console.log(`[db-schema-sync] \u2713 Schema written to ${output}`);
725
+ const result = writeSchemaFileSafely({
726
+ outputPath: resolve2(process.cwd(), output),
727
+ displayPath: output,
728
+ schema,
729
+ project: cliArgs.project,
730
+ cwd: process.cwd()
731
+ });
732
+ if (!result.ok) {
733
+ console.error(
734
+ `[db-schema-sync] \u2717 Failed (${result.status}): ${result.message ?? "schema was not replaced"}`
735
+ );
736
+ emitSchemaSyncResult(result);
737
+ process.exitCode = 1;
738
+ return;
739
+ }
740
+ if (result.changed) {
741
+ console.log(`[db-schema-sync] \u2713 Schema written to ${output}`);
742
+ } else {
743
+ console.log(`[db-schema-sync] \u2713 Schema unchanged at ${output}`);
744
+ }
745
+ emitSchemaSyncResult(result);
746
+ process.exitCode = 0;
486
747
  } catch (err) {
748
+ const result = generationFailure(err);
487
749
  console.error(
488
- `[db-schema-sync] \u2717 Failed:`,
489
- err instanceof Error ? err.message : String(err)
750
+ `[db-schema-sync] \u2717 Failed (${result.status}): ${result.message}`
490
751
  );
491
- process.exit(1);
752
+ emitSchemaSyncResult(result);
753
+ process.exitCode = 1;
492
754
  }
493
755
  }
494
- main();
756
+ if (isDirectExecution()) {
757
+ void main();
758
+ }
759
+ export {
760
+ main
761
+ };
@@ -2673,10 +2673,15 @@ import {
2673
2673
  rmSync,
2674
2674
  writeFileSync
2675
2675
  } from "fs";
2676
+ import { createRequire as createNodeRequire } from "module";
2676
2677
  import { tmpdir } from "os";
2677
- import { dirname, delimiter, join, resolve } from "path";
2678
+ import { dirname, delimiter, join, relative, resolve } from "path";
2679
+ var MAX_MESSAGE_LENGTH = 500;
2680
+ var MAX_DIAGNOSTIC_MESSAGE_LENGTH = 300;
2681
+ var MAX_DIAGNOSTIC_FILE_LENGTH = 160;
2682
+ var MAX_DIAGNOSTIC_SUMMARY_COUNT = 5;
2678
2683
  function truncateMessage(message) {
2679
- return message.length > 500 ? `${message.slice(0, 500)}...` : message;
2684
+ return message.length > MAX_MESSAGE_LENGTH ? `${message.slice(0, MAX_MESSAGE_LENGTH)}...` : message;
2680
2685
  }
2681
2686
  function result(params, startTime) {
2682
2687
  return {
@@ -2917,6 +2922,320 @@ function validateGeneratedSchema(options) {
2917
2922
  );
2918
2923
  }
2919
2924
  }
2925
+ function compatibilityUnavailable(message, removedExportCount = 0) {
2926
+ return {
2927
+ ok: false,
2928
+ status: "unavailable",
2929
+ removedExportCount,
2930
+ oldDiagnosticCount: 0,
2931
+ candidateDiagnosticCount: 0,
2932
+ newDiagnosticCount: 0,
2933
+ diagnosticSummary: [],
2934
+ message: truncateMessage(message)
2935
+ };
2936
+ }
2937
+ function loadProjectTypeScript(cwd) {
2938
+ const projectRequire = createNodeRequire(join(cwd, "package.json"));
2939
+ return projectRequire("typescript");
2940
+ }
2941
+ function resolveConsumerProjectPath(cwd, project) {
2942
+ if (project) return resolve(cwd, project);
2943
+ const projectPath = resolve(cwd, "tsconfig.node.json");
2944
+ return existsSync(projectPath) ? projectPath : void 0;
2945
+ }
2946
+ function canonicalPath(path) {
2947
+ const absolute = resolve(path).replace(/\\/g, "/");
2948
+ return process.platform === "win32" ? absolute.toLowerCase() : absolute;
2949
+ }
2950
+ function hasExportModifier(ts, node) {
2951
+ const modifiers = node.modifiers;
2952
+ return Boolean(
2953
+ modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)
2954
+ );
2955
+ }
2956
+ function collectBindingNames(ts, name, names) {
2957
+ if (ts.isIdentifier(name)) {
2958
+ names.add(name.text);
2959
+ return;
2960
+ }
2961
+ for (const element of name.elements) {
2962
+ if (!ts.isOmittedExpression(element)) {
2963
+ collectBindingNames(ts, element.name, names);
2964
+ }
2965
+ }
2966
+ }
2967
+ function collectPublicExportNames(ts, source) {
2968
+ const sourceFile = ts.createSourceFile(
2969
+ "schema.ts",
2970
+ source,
2971
+ ts.ScriptTarget.Latest,
2972
+ false,
2973
+ ts.ScriptKind.TS
2974
+ );
2975
+ const names = /* @__PURE__ */ new Set();
2976
+ let complete = true;
2977
+ for (const statement of sourceFile.statements) {
2978
+ if (ts.isExportDeclaration(statement)) {
2979
+ if (!statement.exportClause) {
2980
+ complete = false;
2981
+ } else if (ts.isNamedExports(statement.exportClause)) {
2982
+ for (const element of statement.exportClause.elements) {
2983
+ names.add(element.name.text);
2984
+ }
2985
+ } else {
2986
+ names.add(statement.exportClause.name.text);
2987
+ }
2988
+ continue;
2989
+ }
2990
+ if (ts.isExportAssignment(statement)) {
2991
+ names.add("default");
2992
+ continue;
2993
+ }
2994
+ if (!hasExportModifier(ts, statement)) continue;
2995
+ if (ts.isVariableStatement(statement)) {
2996
+ for (const declaration of statement.declarationList.declarations) {
2997
+ collectBindingNames(ts, declaration.name, names);
2998
+ }
2999
+ continue;
3000
+ }
3001
+ if (ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement) || ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement) || ts.isEnumDeclaration(statement) || ts.isModuleDeclaration(statement)) {
3002
+ if (statement.name) {
3003
+ names.add(statement.name.text);
3004
+ } else {
3005
+ names.add("default");
3006
+ }
3007
+ }
3008
+ }
3009
+ return { names, complete };
3010
+ }
3011
+ function fallbackRemovedExportCount(oldSource, candidateSource) {
3012
+ const declarationPattern = /^\s*export\s+(?:declare\s+)?(?:const|let|var|function|class|interface|type|enum|namespace|module)\s+([A-Za-z_$][\w$]*)/gm;
3013
+ const collect = (source) => {
3014
+ const names = /* @__PURE__ */ new Set();
3015
+ for (const match of source.matchAll(declarationPattern)) {
3016
+ names.add(match[1]);
3017
+ }
3018
+ return names;
3019
+ };
3020
+ const oldNames = collect(oldSource);
3021
+ const candidateNames = collect(candidateSource);
3022
+ return [...oldNames].filter((name) => !candidateNames.has(name)).length;
3023
+ }
3024
+ function createOverlayProgram(params) {
3025
+ const target = canonicalPath(params.targetPath);
3026
+ const host = params.ts.createCompilerHost(params.parsed.options, true);
3027
+ const defaultFileExists = host.fileExists.bind(host);
3028
+ const defaultReadFile = host.readFile.bind(host);
3029
+ const defaultGetSourceFile = host.getSourceFile.bind(host);
3030
+ const isTarget = (fileName) => canonicalPath(fileName) === target;
3031
+ host.fileExists = (fileName) => isTarget(fileName) ? true : defaultFileExists(fileName);
3032
+ host.readFile = (fileName) => isTarget(fileName) ? params.source : defaultReadFile(fileName);
3033
+ host.getSourceFile = (fileName, languageVersion, onError, shouldCreate) => {
3034
+ if (isTarget(fileName)) {
3035
+ return params.ts.createSourceFile(
3036
+ fileName,
3037
+ params.source,
3038
+ languageVersion,
3039
+ true,
3040
+ params.ts.ScriptKind.TS
3041
+ );
3042
+ }
3043
+ return defaultGetSourceFile(
3044
+ fileName,
3045
+ languageVersion,
3046
+ onError,
3047
+ shouldCreate
3048
+ );
3049
+ };
3050
+ return params.ts.createProgram({
3051
+ rootNames: params.parsed.fileNames,
3052
+ options: params.parsed.options,
3053
+ projectReferences: params.parsed.projectReferences,
3054
+ host
3055
+ });
3056
+ }
3057
+ function isServerDiagnostic(diagnostic, cwd) {
3058
+ if (!diagnostic.file) return true;
3059
+ const path = relative(cwd, resolve(diagnostic.file.fileName)).replace(
3060
+ /\\/g,
3061
+ "/"
3062
+ );
3063
+ return path === "server" || path.startsWith("server/");
3064
+ }
3065
+ function normalizeDiagnostic(ts, diagnostic, cwd) {
3066
+ const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, " ");
3067
+ const file = diagnostic.file ? relative(cwd, resolve(diagnostic.file.fileName)).replace(/\\/g, "/") : void 0;
3068
+ const position = diagnostic.file && diagnostic.start !== void 0 ? diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start) : void 0;
3069
+ const line = position ? position.line + 1 : void 0;
3070
+ const character = position ? position.character + 1 : void 0;
3071
+ const key = [
3072
+ diagnostic.code,
3073
+ diagnostic.category,
3074
+ file ?? "",
3075
+ line ?? "",
3076
+ character ?? "",
3077
+ message
3078
+ ].join("|");
3079
+ return {
3080
+ key,
3081
+ summary: {
3082
+ code: diagnostic.code,
3083
+ file: file && file.length > MAX_DIAGNOSTIC_FILE_LENGTH ? `...${file.slice(-MAX_DIAGNOSTIC_FILE_LENGTH)}` : file,
3084
+ line,
3085
+ character,
3086
+ message: message.length > MAX_DIAGNOSTIC_MESSAGE_LENGTH ? `${message.slice(0, MAX_DIAGNOSTIC_MESSAGE_LENGTH)}...` : message
3087
+ }
3088
+ };
3089
+ }
3090
+ function getServerDiagnostics(ts, program, cwd) {
3091
+ return ts.getPreEmitDiagnostics(program).filter((diagnostic) => isServerDiagnostic(diagnostic, cwd));
3092
+ }
3093
+ function getNewDiagnostics(params) {
3094
+ const oldCounts = /* @__PURE__ */ new Map();
3095
+ for (const diagnostic of params.oldDiagnostics) {
3096
+ const { key } = normalizeDiagnostic(params.ts, diagnostic, params.cwd);
3097
+ oldCounts.set(key, (oldCounts.get(key) ?? 0) + 1);
3098
+ }
3099
+ const additions = [];
3100
+ for (const diagnostic of params.candidateDiagnostics) {
3101
+ const normalized = normalizeDiagnostic(params.ts, diagnostic, params.cwd);
3102
+ const oldCount = oldCounts.get(normalized.key) ?? 0;
3103
+ if (oldCount > 0) {
3104
+ oldCounts.set(normalized.key, oldCount - 1);
3105
+ } else {
3106
+ additions.push(normalized);
3107
+ }
3108
+ }
3109
+ additions.sort((left, right) => left.key.localeCompare(right.key));
3110
+ return additions.map((addition) => addition.summary);
3111
+ }
3112
+ function checkSchemaConsumerCompatibility(options) {
3113
+ const cwd = resolve(options.cwd);
3114
+ const targetPath = resolve(cwd, options.targetPath);
3115
+ let ts;
3116
+ try {
3117
+ ts = options.typescript ?? loadProjectTypeScript(cwd);
3118
+ } catch (error) {
3119
+ return compatibilityUnavailable(
3120
+ `Project TypeScript compiler is unavailable: ${error instanceof Error ? error.message : String(error)}`,
3121
+ fallbackRemovedExportCount(options.oldSource, options.candidateSource)
3122
+ );
3123
+ }
3124
+ let removedExportCount = 0;
3125
+ try {
3126
+ const oldExports = collectPublicExportNames(ts, options.oldSource);
3127
+ const candidateExports = collectPublicExportNames(
3128
+ ts,
3129
+ options.candidateSource
3130
+ );
3131
+ removedExportCount = [...oldExports.names].filter(
3132
+ (name) => !candidateExports.names.has(name)
3133
+ ).length;
3134
+ if (!oldExports.complete || !candidateExports.complete) {
3135
+ return compatibilityUnavailable(
3136
+ "Schema public exports could not be completely enumerated",
3137
+ removedExportCount
3138
+ );
3139
+ }
3140
+ } catch (error) {
3141
+ return compatibilityUnavailable(
3142
+ `Schema public exports could not be compared: ${error instanceof Error ? error.message : String(error)}`,
3143
+ fallbackRemovedExportCount(options.oldSource, options.candidateSource)
3144
+ );
3145
+ }
3146
+ const projectPath = resolveConsumerProjectPath(cwd, options.project);
3147
+ if (!projectPath || !existsSync(projectPath)) {
3148
+ return compatibilityUnavailable(
3149
+ "tsconfig.node.json is required for schema consumer compatibility checking",
3150
+ removedExportCount
3151
+ );
3152
+ }
3153
+ try {
3154
+ const config = ts.readConfigFile(projectPath, ts.sys.readFile);
3155
+ if (config.error) {
3156
+ const message = ts.flattenDiagnosticMessageText(
3157
+ config.error.messageText,
3158
+ " "
3159
+ );
3160
+ return compatibilityUnavailable(
3161
+ `Unable to read TypeScript project configuration: ${message}`,
3162
+ removedExportCount
3163
+ );
3164
+ }
3165
+ const parsed = ts.parseJsonConfigFileContent(
3166
+ config.config,
3167
+ ts.sys,
3168
+ dirname(projectPath),
3169
+ {
3170
+ noEmit: true,
3171
+ incremental: false,
3172
+ composite: false
3173
+ },
3174
+ projectPath
3175
+ );
3176
+ if (parsed.errors.length > 0) {
3177
+ const message = parsed.errors.slice(0, MAX_DIAGNOSTIC_SUMMARY_COUNT).map((error) => ts.flattenDiagnosticMessageText(error.messageText, " ")).join("; ");
3178
+ return compatibilityUnavailable(
3179
+ `Unable to parse TypeScript project configuration: ${message}`,
3180
+ removedExportCount
3181
+ );
3182
+ }
3183
+ const oldProgram = createOverlayProgram({
3184
+ ts,
3185
+ parsed,
3186
+ targetPath,
3187
+ source: options.oldSource
3188
+ });
3189
+ const candidateProgram = createOverlayProgram({
3190
+ ts,
3191
+ parsed,
3192
+ targetPath,
3193
+ source: options.candidateSource
3194
+ });
3195
+ const oldDiagnostics = getServerDiagnostics(ts, oldProgram, cwd);
3196
+ const candidateDiagnostics = getServerDiagnostics(
3197
+ ts,
3198
+ candidateProgram,
3199
+ cwd
3200
+ );
3201
+ const newDiagnostics = getNewDiagnostics({
3202
+ ts,
3203
+ cwd,
3204
+ oldDiagnostics,
3205
+ candidateDiagnostics
3206
+ });
3207
+ const diagnosticSummary = newDiagnostics.slice(
3208
+ 0,
3209
+ MAX_DIAGNOSTIC_SUMMARY_COUNT
3210
+ );
3211
+ if (newDiagnostics.length > 0) {
3212
+ return {
3213
+ ok: false,
3214
+ status: "incompatible",
3215
+ removedExportCount,
3216
+ oldDiagnosticCount: oldDiagnostics.length,
3217
+ candidateDiagnosticCount: candidateDiagnostics.length,
3218
+ newDiagnosticCount: newDiagnostics.length,
3219
+ diagnosticSummary,
3220
+ message: "Candidate schema introduces new server TypeScript diagnostics"
3221
+ };
3222
+ }
3223
+ return {
3224
+ ok: true,
3225
+ status: "compatible",
3226
+ removedExportCount,
3227
+ oldDiagnosticCount: oldDiagnostics.length,
3228
+ candidateDiagnosticCount: candidateDiagnostics.length,
3229
+ newDiagnosticCount: 0,
3230
+ diagnosticSummary: []
3231
+ };
3232
+ } catch (error) {
3233
+ return compatibilityUnavailable(
3234
+ `Schema consumer compatibility check failed: ${error instanceof Error ? error.message : String(error)}`,
3235
+ removedExportCount
3236
+ );
3237
+ }
3238
+ }
2920
3239
 
2921
3240
  // src/index.ts
2922
3241
  async function generateSchema(options) {
@@ -2942,6 +3261,7 @@ export {
2942
3261
  normalizeApiResponse,
2943
3262
  generateSchemaCode,
2944
3263
  validateGeneratedSchema,
3264
+ checkSchemaConsumerCompatibility,
2945
3265
  generateSchema,
2946
3266
  generateSchemaFromData
2947
3267
  };
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  normalizeApiResponse,
7
7
  resolveEnvOptions,
8
8
  validateGeneratedSchema
9
- } from "./chunk-T3RIJCQD.js";
9
+ } from "./chunk-EVOKG4DJ.js";
10
10
  export {
11
11
  generateSchema,
12
12
  generateSchemaCode,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/db-schema-sync",
3
- "version": "0.1.21",
3
+ "version": "0.1.22-alpha.20261015232825",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",