nextly 0.0.1 → 0.0.2-alpha.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.
- package/LICENSE +22 -0
- package/README.md +122 -0
- package/dist/_dts-chunks/collections-handler.d-DjgO74Wt.d.ts +20540 -0
- package/dist/_dts-chunks/config.d-DNwsDnjs.d.ts +2589 -0
- package/dist/_dts-chunks/define-component.d-BUgTHmt3.d.ts +1149 -0
- package/dist/_dts-chunks/image-processor.d-OO1PmMrv.d.ts +335 -0
- package/dist/_dts-chunks/index.d-axCAzZ7m.d.ts +17842 -0
- package/dist/_dts-chunks/media.d-DjDOZo4B.d.ts +117 -0
- package/dist/_dts-chunks/on-error.d-CHIKWNxd.d.ts +38 -0
- package/dist/_dts-chunks/storage.d-BUhQ2we_.d.ts +404 -0
- package/dist/actions/index.d.ts +239 -0
- package/dist/actions/index.mjs +281 -0
- package/dist/api/auth-state.d.ts +5 -0
- package/dist/api/auth-state.mjs +131 -0
- package/dist/api/collections-schema-detail.d.ts +56 -0
- package/dist/api/collections-schema-detail.mjs +244 -0
- package/dist/api/collections-schema-export.d.ts +56 -0
- package/dist/api/collections-schema-export.mjs +129 -0
- package/dist/api/collections-schema.d.ts +59 -0
- package/dist/api/collections-schema.mjs +207 -0
- package/dist/api/components-detail.d.ts +50 -0
- package/dist/api/components-detail.mjs +132 -0
- package/dist/api/components.d.ts +69 -0
- package/dist/api/components.mjs +144 -0
- package/dist/api/email-providers-default.d.ts +40 -0
- package/dist/api/email-providers-default.mjs +75 -0
- package/dist/api/email-providers-detail.d.ts +81 -0
- package/dist/api/email-providers-detail.mjs +109 -0
- package/dist/api/email-providers-test.d.ts +43 -0
- package/dist/api/email-providers-test.mjs +114 -0
- package/dist/api/email-providers.d.ts +69 -0
- package/dist/api/email-providers.mjs +110 -0
- package/dist/api/email-send-template.d.ts +41 -0
- package/dist/api/email-send-template.mjs +58 -0
- package/dist/api/email-send.d.ts +42 -0
- package/dist/api/email-send.mjs +58 -0
- package/dist/api/email-templates-detail.d.ts +74 -0
- package/dist/api/email-templates-detail.mjs +112 -0
- package/dist/api/email-templates-layout.d.ts +55 -0
- package/dist/api/email-templates-layout.mjs +92 -0
- package/dist/api/email-templates-preview.d.ts +48 -0
- package/dist/api/email-templates-preview.mjs +93 -0
- package/dist/api/email-templates.d.ts +61 -0
- package/dist/api/email-templates.mjs +118 -0
- package/dist/api/health.d.ts +68 -0
- package/dist/api/health.mjs +67 -0
- package/dist/api/index.d.ts +54 -0
- package/dist/api/index.mjs +16 -0
- package/dist/api/media-bulk.d.ts +74 -0
- package/dist/api/media-bulk.mjs +196 -0
- package/dist/api/media-folders.d.ts +112 -0
- package/dist/api/media-folders.mjs +187 -0
- package/dist/api/media-handlers.d.ts +102 -0
- package/dist/api/media-handlers.mjs +437 -0
- package/dist/api/media.d.ts +117 -0
- package/dist/api/media.mjs +242 -0
- package/dist/api/singles-detail.d.ts +87 -0
- package/dist/api/singles-detail.mjs +170 -0
- package/dist/api/singles-schema-detail.d.ts +54 -0
- package/dist/api/singles-schema-detail.mjs +182 -0
- package/dist/api/singles.d.ts +34 -0
- package/dist/api/singles.mjs +94 -0
- package/dist/api/storage-upload-url.d.ts +48 -0
- package/dist/api/storage-upload-url.mjs +202 -0
- package/dist/api/uploads.d.ts +109 -0
- package/dist/api/uploads.mjs +359 -0
- package/dist/auth/index.d.ts +425 -0
- package/dist/auth/index.mjs +199 -0
- package/dist/boot-apply-PQSYLDIN.mjs +7 -0
- package/dist/chunk-2OALJTK6.mjs +489 -0
- package/dist/chunk-2Q2SX2CS.mjs +365 -0
- package/dist/chunk-2TFX4ND3.mjs +13 -0
- package/dist/chunk-2TWPDSYD.mjs +87 -0
- package/dist/chunk-2W3DVD7S.mjs +647 -0
- package/dist/chunk-2ZFKXPQM.mjs +88 -0
- package/dist/chunk-3FA7FKAV.mjs +832 -0
- package/dist/chunk-3NZ2KMBL.mjs +58 -0
- package/dist/chunk-4MJLT6PZ.mjs +0 -0
- package/dist/chunk-56WO4WX7.mjs +0 -0
- package/dist/chunk-5APFUGAD.mjs +89 -0
- package/dist/chunk-5HMZ644B.mjs +108 -0
- package/dist/chunk-67GXH6PR.mjs +32 -0
- package/dist/chunk-6JNEPWRW.mjs +14368 -0
- package/dist/chunk-6NFHQIJD.mjs +45 -0
- package/dist/chunk-7P6ASYW6.mjs +9 -0
- package/dist/chunk-A3WPLSDT.mjs +1364 -0
- package/dist/chunk-AGJ6F2T3.mjs +144 -0
- package/dist/chunk-AK6Z23OX.mjs +1464 -0
- package/dist/chunk-APKKRD2G.mjs +102 -0
- package/dist/chunk-B2GV2BWH.mjs +73 -0
- package/dist/chunk-D5HQBNUB.mjs +74 -0
- package/dist/chunk-DNNG377Z.mjs +204 -0
- package/dist/chunk-DP3G27G5.mjs +135 -0
- package/dist/chunk-DV6WVX2Q.mjs +0 -0
- package/dist/chunk-DXGGXIUZ.mjs +57 -0
- package/dist/chunk-EGXBZCGC.mjs +943 -0
- package/dist/chunk-ERCNLX3V.mjs +176 -0
- package/dist/chunk-FQULBZ53.mjs +850 -0
- package/dist/chunk-G2AA4QLC.mjs +262 -0
- package/dist/chunk-GDBJ5JCU.mjs +488 -0
- package/dist/chunk-GJNSJU4S.mjs +19 -0
- package/dist/chunk-GZ6DCQKC.mjs +69 -0
- package/dist/chunk-H26B4FYG.mjs +167 -0
- package/dist/chunk-I4JMR3UR.mjs +21 -0
- package/dist/chunk-INV7QKLG.mjs +508 -0
- package/dist/chunk-IUDOC7N7.mjs +46 -0
- package/dist/chunk-IZWPRDC3.mjs +206 -0
- package/dist/chunk-KIMNCZGV.mjs +15 -0
- package/dist/chunk-L6HW2DA7.mjs +15 -0
- package/dist/chunk-LAZXX4HR.mjs +100 -0
- package/dist/chunk-LDKCUMHK.mjs +95 -0
- package/dist/chunk-LRXMECUA.mjs +0 -0
- package/dist/chunk-M52VMPGA.mjs +119 -0
- package/dist/chunk-MGUWEEI6.mjs +160 -0
- package/dist/chunk-NRUWQ5Z7.mjs +419 -0
- package/dist/chunk-NSEFNNU4.mjs +25360 -0
- package/dist/chunk-NTHVDFGO.mjs +138 -0
- package/dist/chunk-O3QHXMOX.mjs +3166 -0
- package/dist/chunk-P7NH2OSC.mjs +2605 -0
- package/dist/chunk-PKMABBB5.mjs +184 -0
- package/dist/chunk-PWS6XGJK.mjs +76 -0
- package/dist/chunk-R6JJQHFC.mjs +20 -0
- package/dist/chunk-RJLLGGPG.mjs +0 -0
- package/dist/chunk-SBACDPNX.mjs +689 -0
- package/dist/chunk-TO5AFLVQ.mjs +124 -0
- package/dist/chunk-TS7GHTG2.mjs +5436 -0
- package/dist/chunk-UJ2IMJ4W.mjs +133 -0
- package/dist/chunk-UOP63Q54.mjs +102 -0
- package/dist/chunk-UUOFWCM6.mjs +78 -0
- package/dist/chunk-V4EQTOA4.mjs +893 -0
- package/dist/chunk-VJ66NCL4.mjs +193 -0
- package/dist/chunk-VQJQHVEV.mjs +29 -0
- package/dist/chunk-VTJADRO3.mjs +141 -0
- package/dist/chunk-VWF3JO32.mjs +0 -0
- package/dist/chunk-W4MGXIRR.mjs +27 -0
- package/dist/chunk-W5KKPZT5.mjs +1204 -0
- package/dist/chunk-WD34YQ6T.mjs +381 -0
- package/dist/chunk-WZBYMYVW.mjs +14 -0
- package/dist/chunk-X23WKS3Z.mjs +50 -0
- package/dist/chunk-X7TXCYYN.mjs +6496 -0
- package/dist/chunk-XGI4EMS3.mjs +140 -0
- package/dist/chunk-XZKLBMN6.mjs +1153 -0
- package/dist/chunk-YB7INWPY.mjs +0 -0
- package/dist/chunk-YV4Y7SDL.mjs +83 -0
- package/dist/chunk-YZNBLFIW.mjs +1688 -0
- package/dist/chunk-YZZCTONM.mjs +263 -0
- package/dist/chunk-ZE6A3FYH.mjs +289 -0
- package/dist/cli/nextly.mjs +68 -0
- package/dist/cli/utils/index.d.ts +449 -0
- package/dist/cli/utils/index.mjs +49 -0
- package/dist/component-schema-service-5577KVW6.mjs +11 -0
- package/dist/config-loader-23YEMC3Z.mjs +23 -0
- package/dist/config.d.ts +44 -0
- package/dist/config.mjs +109 -0
- package/dist/container-ORGFGYSZ.mjs +9 -0
- package/dist/database/index.d.ts +12 -0
- package/dist/database/index.mjs +40 -0
- package/dist/database/seeders/index.d.ts +93 -0
- package/dist/database/seeders/index.mjs +47 -0
- package/dist/db-sync-demote-LJGKLB3S.mjs +117 -0
- package/dist/db-sync-promote-B26VSYQF.mjs +113 -0
- package/dist/dev-reload-broadcaster-B73IQ53V.mjs +25 -0
- package/dist/dist-M2NOU37V.mjs +19 -0
- package/dist/drizzle-kit-lazy-D2M2PXR2.mjs +13 -0
- package/dist/dynamic-collection-schema-service-IEXTPIZ7.mjs +8 -0
- package/dist/errors/index.d.ts +159 -0
- package/dist/errors/index.mjs +10 -0
- package/dist/factory-IWMBKUJM.mjs +15 -0
- package/dist/first-run-QIVKWJIF.mjs +63 -0
- package/dist/fresh-push-NR67DC3R.mjs +8 -0
- package/dist/index.d.ts +4175 -0
- package/dist/index.mjs +1336 -0
- package/dist/local-plugin-PTET4NAT.mjs +7 -0
- package/dist/logger-NU46DXNY.mjs +15 -0
- package/dist/logger-YE4TC7ZN.mjs +9 -0
- package/dist/migration-journal-EP532Y4L.mjs +139 -0
- package/dist/migrations/mysql/0000_eager_sentry.sql +174 -0
- package/dist/migrations/mysql/0001_soft_giant_girl.sql +27 -0
- package/dist/migrations/mysql/0002_media_table.sql +24 -0
- package/dist/migrations/mysql/0003_dynamic_singles.sql +37 -0
- package/dist/migrations/mysql/0004_dynamic_components.sql +35 -0
- package/dist/migrations/mysql/0005_user_management_tables.sql +92 -0
- package/dist/migrations/mysql/0006_api_keys.sql +36 -0
- package/dist/migrations/mysql/0007_general_settings.sql +20 -0
- package/dist/migrations/mysql/0008_site_settings_logo_url.sql +9 -0
- package/dist/migrations/mysql/0009_activity_log.sql +30 -0
- package/dist/migrations/mysql/0010_site_settings_sidebar.sql +13 -0
- package/dist/migrations/mysql/0011_missing_tables_and_columns.sql +54 -0
- package/dist/migrations/mysql/0012_image_sizes_and_focal_point.sql +30 -0
- package/dist/migrations/mysql/0012_media_folders.sql +43 -0
- package/dist/migrations/mysql/0013_user_brute_force_protection.sql +31 -0
- package/dist/migrations/mysql/0014_email_template_attachments.sql +12 -0
- package/dist/migrations/mysql/0015_media_uploaded_by_nullable.sql +15 -0
- package/dist/migrations/mysql/20260429_000000_000_initial_journal.sql +22 -0
- package/dist/migrations/mysql/20260501_000000_journal_batch.sql +17 -0
- package/dist/migrations/mysql/20260501_000001_audit_log.sql +24 -0
- package/dist/migrations/mysql/20260504_000000_nextly_meta.sql +21 -0
- package/dist/migrations/mysql/meta/0000_snapshot.json +1005 -0
- package/dist/migrations/mysql/meta/0001_snapshot.json +1099 -0
- package/dist/migrations/mysql/meta/_journal.json +41 -0
- package/dist/migrations/postgresql/0000_misty_king_bedlam.sql +169 -0
- package/dist/migrations/postgresql/0001_perpetual_captain_marvel.sql +8 -0
- package/dist/migrations/postgresql/0002_sad_spectrum.sql +16 -0
- package/dist/migrations/postgresql/0003_hesitant_ultron.sql +17 -0
- package/dist/migrations/postgresql/0004_media_table.sql +24 -0
- package/dist/migrations/postgresql/0005_media_folders.sql +36 -0
- package/dist/migrations/postgresql/0006_dynamic_collections_update.sql +50 -0
- package/dist/migrations/postgresql/0007_dynamic_singles.sql +38 -0
- package/dist/migrations/postgresql/0008_dynamic_components.sql +37 -0
- package/dist/migrations/postgresql/0009_user_management_tables.sql +95 -0
- package/dist/migrations/postgresql/0010_api_keys.sql +34 -0
- package/dist/migrations/postgresql/0011_general_settings.sql +20 -0
- package/dist/migrations/postgresql/0012_site_settings_logo_url.sql +9 -0
- package/dist/migrations/postgresql/0013_activity_log.sql +29 -0
- package/dist/migrations/postgresql/0014_image_sizes_and_focal_point.sql +33 -0
- package/dist/migrations/postgresql/0014_site_settings_sidebar.sql +13 -0
- package/dist/migrations/postgresql/0015_user_brute_force_protection.sql +29 -0
- package/dist/migrations/postgresql/0016_email_template_attachments.sql +12 -0
- package/dist/migrations/postgresql/0017_media_uploaded_by_nullable.sql +15 -0
- package/dist/migrations/postgresql/20260429_000000_000_initial_journal.sql +24 -0
- package/dist/migrations/postgresql/20260501_000000_journal_batch.sql +17 -0
- package/dist/migrations/postgresql/20260501_000001_audit_log.sql +24 -0
- package/dist/migrations/postgresql/20260504_000000_nextly_meta.sql +22 -0
- package/dist/migrations/postgresql/meta/0000_snapshot.json +1286 -0
- package/dist/migrations/postgresql/meta/0001_snapshot.json +1407 -0
- package/dist/migrations/postgresql/meta/0002_snapshot.json +1552 -0
- package/dist/migrations/postgresql/meta/0003_snapshot.json +1695 -0
- package/dist/migrations/postgresql/meta/0010_snapshot.json +2345 -0
- package/dist/migrations/postgresql/meta/_journal.json +90 -0
- package/dist/migrations/sqlite/0000_api_keys.sql +34 -0
- package/dist/migrations/sqlite/0001_general_settings.sql +20 -0
- package/dist/migrations/sqlite/0002_site_settings_logo_url.sql +9 -0
- package/dist/migrations/sqlite/0003_activity_log.sql +29 -0
- package/dist/migrations/sqlite/0004_image_sizes_and_focal_point.sql +29 -0
- package/dist/migrations/sqlite/0004_site_settings_sidebar.sql +11 -0
- package/dist/migrations/sqlite/0005_user_brute_force_protection.sql +29 -0
- package/dist/migrations/sqlite/0006_email_template_attachments.sql +12 -0
- package/dist/migrations/sqlite/0007_media_uploaded_by_nullable.sql +111 -0
- package/dist/migrations/sqlite/20260429_000000_000_initial_journal.sql +24 -0
- package/dist/migrations/sqlite/20260501_000000_journal_batch.sql +19 -0
- package/dist/migrations/sqlite/20260501_000001_audit_log.sql +24 -0
- package/dist/migrations/sqlite/20260504_000000_nextly_meta.sql +21 -0
- package/dist/migrations/sqlite/20260505_000000_user_management_tables.sql +77 -0
- package/dist/next.d.ts +57 -0
- package/dist/next.mjs +55 -0
- package/dist/observability/index.d.ts +87 -0
- package/dist/observability/index.mjs +57 -0
- package/dist/permissions-3DZZQZMI.mjs +39 -0
- package/dist/pipeline-YOML7SWF.mjs +29 -0
- package/dist/preview-ZZTR3QGS.mjs +9 -0
- package/dist/program-PW6UB2ZC.mjs +5934 -0
- package/dist/reconcile-single-tables-7ENVXJGB.mjs +7 -0
- package/dist/register-SF6E6FVU.mjs +49 -0
- package/dist/reload-config-HWQ4G5MM.mjs +23 -0
- package/dist/resolve-single-table-name-JSOMUB3R.mjs +7 -0
- package/dist/routeHandler-UNMMJIBM.mjs +77 -0
- package/dist/runtime-schema-generator-NRA6A6Z6.mjs +8 -0
- package/dist/runtime.d.ts +120 -0
- package/dist/runtime.mjs +73 -0
- package/dist/schema-hash-FMMG6VPJ.mjs +13 -0
- package/dist/schema-registry-EQ36FZDP.mjs +7 -0
- package/dist/scripts/load-env.mjs +42 -0
- package/dist/storage/index.d.ts +566 -0
- package/dist/storage/index.mjs +45 -0
- package/dist/super-admin-G5ZK5F4T.mjs +39 -0
- package/dist/system-table-service-WGSRVEGT.mjs +17 -0
- package/dist/users-7KELGRYJ.mjs +38 -0
- package/package.json +308 -9
|
@@ -0,0 +1,1464 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildDesiredTableFromComponentFields,
|
|
3
|
+
buildDesiredTableFromFields,
|
|
4
|
+
countNulls,
|
|
5
|
+
countRows,
|
|
6
|
+
diffSnapshots,
|
|
7
|
+
introspectLiveSnapshot
|
|
8
|
+
} from "./chunk-SBACDPNX.mjs";
|
|
9
|
+
import {
|
|
10
|
+
ComponentSchemaService
|
|
11
|
+
} from "./chunk-V4EQTOA4.mjs";
|
|
12
|
+
import {
|
|
13
|
+
generateRuntimeSchema
|
|
14
|
+
} from "./chunk-IZWPRDC3.mjs";
|
|
15
|
+
import {
|
|
16
|
+
getDialectTables
|
|
17
|
+
} from "./chunk-TS7GHTG2.mjs";
|
|
18
|
+
|
|
19
|
+
// src/domains/schema/pipeline/errors.ts
|
|
20
|
+
import { UnsupportedDialectVersionError } from "@nextlyhq/adapter-drizzle/version-check";
|
|
21
|
+
function classifyError(err) {
|
|
22
|
+
if (err instanceof UnsupportedDialectVersionError) {
|
|
23
|
+
return {
|
|
24
|
+
code: "UNSUPPORTED_DIALECT_VERSION",
|
|
25
|
+
message: err.message,
|
|
26
|
+
details: err
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
if (err instanceof Error) {
|
|
30
|
+
const stack = err.stack ?? "";
|
|
31
|
+
if (stack.includes("drizzle-kit")) {
|
|
32
|
+
return {
|
|
33
|
+
code: "PUSHSCHEMA_FAILED",
|
|
34
|
+
message: err.message,
|
|
35
|
+
details: err
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
code: "INTERNAL_ERROR",
|
|
40
|
+
message: err.message,
|
|
41
|
+
details: err
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const message = typeof err === "string" ? err : JSON.stringify(err);
|
|
45
|
+
return {
|
|
46
|
+
code: "INTERNAL_ERROR",
|
|
47
|
+
message,
|
|
48
|
+
details: err
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/domains/schema/pipeline/apply.ts
|
|
53
|
+
function createApplyDesiredSchema(deps) {
|
|
54
|
+
return async function applyDesiredSchema(desired, source, ctx) {
|
|
55
|
+
const start = Date.now();
|
|
56
|
+
const resolvedChannel = ctx.promptChannel === "auto" ? "terminal" : ctx.promptChannel;
|
|
57
|
+
if (source === "ui" && ctx.schemaVersions) {
|
|
58
|
+
for (const slug of Object.keys(desired.collections)) {
|
|
59
|
+
const expected = ctx.schemaVersions[slug];
|
|
60
|
+
if (expected === void 0) continue;
|
|
61
|
+
const actual = await deps.readSchemaVersionForSlug(slug);
|
|
62
|
+
if (actual !== null && actual !== expected) {
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
error: {
|
|
66
|
+
code: "SCHEMA_VERSION_CONFLICT",
|
|
67
|
+
message: `Schema version conflict on '${slug}': expected ${expected}, found ${actual}. Reload and try again.`
|
|
68
|
+
},
|
|
69
|
+
durationMs: Date.now() - start
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
let pipelineResult;
|
|
75
|
+
try {
|
|
76
|
+
pipelineResult = await deps.applyPipeline(
|
|
77
|
+
desired,
|
|
78
|
+
source,
|
|
79
|
+
resolvedChannel
|
|
80
|
+
);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
const classified = classifyError(err);
|
|
83
|
+
return {
|
|
84
|
+
success: false,
|
|
85
|
+
error: classified,
|
|
86
|
+
durationMs: Date.now() - start
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (!pipelineResult.success) {
|
|
90
|
+
return {
|
|
91
|
+
success: false,
|
|
92
|
+
error: {
|
|
93
|
+
code: pipelineResult.error?.code ?? "INTERNAL_ERROR",
|
|
94
|
+
message: pipelineResult.error?.message ?? "Pipeline returned failure with no error details",
|
|
95
|
+
details: pipelineResult.error?.details
|
|
96
|
+
},
|
|
97
|
+
partiallyApplied: pipelineResult.partiallyApplied,
|
|
98
|
+
durationMs: Date.now() - start
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const allSlugs = Object.keys(desired.collections);
|
|
102
|
+
const newSchemaVersions = await deps.readNewSchemaVersionsForSlugs(allSlugs);
|
|
103
|
+
return {
|
|
104
|
+
success: true,
|
|
105
|
+
newSchemaVersions,
|
|
106
|
+
statementsExecuted: pipelineResult.statementsExecuted,
|
|
107
|
+
renamesApplied: pipelineResult.renamesApplied,
|
|
108
|
+
durationMs: Date.now() - start,
|
|
109
|
+
// F10 PR 6: forward the diff-derived counts so the dispatcher
|
|
110
|
+
// can build a contextual toast. Undefined when the underlying
|
|
111
|
+
// pipeline doesn't compute one.
|
|
112
|
+
summary: pipelineResult.summary
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/domains/schema/pipeline/pre-cleanup/executor.ts
|
|
118
|
+
import { sql } from "drizzle-orm";
|
|
119
|
+
|
|
120
|
+
// src/domains/schema/pipeline/_internal/run-statement.ts
|
|
121
|
+
async function runStatement(txOrDb, dialect, stmt) {
|
|
122
|
+
if (dialect === "sqlite") {
|
|
123
|
+
txOrDb.run(stmt);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
await txOrDb.execute(stmt);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/domains/schema/pipeline/prompt-dispatcher/errors.ts
|
|
130
|
+
var TTYRequiredError = class extends Error {
|
|
131
|
+
constructor(detail) {
|
|
132
|
+
super(
|
|
133
|
+
`TTY required for schema confirmation. ${detail} Run from an interactive terminal, or use code-first migration files via \`nextly migrate:create\`.`
|
|
134
|
+
);
|
|
135
|
+
this.name = "TTYRequiredError";
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
var PromptCancelledError = class extends Error {
|
|
139
|
+
constructor() {
|
|
140
|
+
super("Schema apply cancelled by user");
|
|
141
|
+
this.name = "PromptCancelledError";
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// src/domains/schema/pipeline/pre-cleanup/snapshot-patch.ts
|
|
146
|
+
function applyMakeOptionalToSnapshot(snapshot, resolutions, events) {
|
|
147
|
+
const makeOptionalEventIds = new Set(
|
|
148
|
+
resolutions.filter((r) => r.kind === "make_optional").map((r) => r.eventId)
|
|
149
|
+
);
|
|
150
|
+
if (makeOptionalEventIds.size === 0) return snapshot;
|
|
151
|
+
const targets = /* @__PURE__ */ new Map();
|
|
152
|
+
for (const event of events) {
|
|
153
|
+
if (makeOptionalEventIds.has(event.id) && (event.kind === "add_not_null_with_nulls" || event.kind === "add_required_field_no_default")) {
|
|
154
|
+
targets.set(event.id, {
|
|
155
|
+
table: event.tableName,
|
|
156
|
+
column: event.columnName
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (targets.size === 0) return snapshot;
|
|
161
|
+
return {
|
|
162
|
+
tables: snapshot.tables.map((table) => {
|
|
163
|
+
const matchingTargets = [...targets.values()].filter(
|
|
164
|
+
(t) => t.table === table.name
|
|
165
|
+
);
|
|
166
|
+
if (matchingTargets.length === 0) return table;
|
|
167
|
+
return {
|
|
168
|
+
...table,
|
|
169
|
+
columns: table.columns.map((col) => {
|
|
170
|
+
const matched = matchingTargets.some((t) => t.column === col.name);
|
|
171
|
+
return matched ? { ...col, nullable: true } : col;
|
|
172
|
+
})
|
|
173
|
+
};
|
|
174
|
+
})
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/domains/schema/pipeline/pre-cleanup/validate-default.ts
|
|
179
|
+
import { z } from "zod";
|
|
180
|
+
var TEXT_TYPES = /* @__PURE__ */ new Set([
|
|
181
|
+
"text",
|
|
182
|
+
"email",
|
|
183
|
+
"textarea",
|
|
184
|
+
"richText",
|
|
185
|
+
"code",
|
|
186
|
+
"url",
|
|
187
|
+
"slug"
|
|
188
|
+
]);
|
|
189
|
+
var NUMBER_TYPES = /* @__PURE__ */ new Set(["number", "int", "integer"]);
|
|
190
|
+
var BOOL_TYPES = /* @__PURE__ */ new Set(["checkbox", "boolean"]);
|
|
191
|
+
var DATE_TYPES = /* @__PURE__ */ new Set(["date", "datetime", "timestamp"]);
|
|
192
|
+
function schemaForType(type) {
|
|
193
|
+
if (TEXT_TYPES.has(type)) return z.string();
|
|
194
|
+
if (NUMBER_TYPES.has(type)) return z.number();
|
|
195
|
+
if (BOOL_TYPES.has(type)) return z.boolean();
|
|
196
|
+
if (DATE_TYPES.has(type)) {
|
|
197
|
+
return z.union([z.string().datetime(), z.date()]);
|
|
198
|
+
}
|
|
199
|
+
return z.unknown();
|
|
200
|
+
}
|
|
201
|
+
function validateDefaultValue(field, value) {
|
|
202
|
+
const schema = schemaForType(field.type);
|
|
203
|
+
const parsed = schema.safeParse(value);
|
|
204
|
+
if (!parsed.success) {
|
|
205
|
+
const issue = parsed.error.issues[0];
|
|
206
|
+
return { success: false, error: issue?.message ?? "validation failed" };
|
|
207
|
+
}
|
|
208
|
+
return { success: true };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/domains/schema/pipeline/pre-cleanup/executor.ts
|
|
212
|
+
var SAFE_IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
213
|
+
function readDeleteThreshold() {
|
|
214
|
+
const env = process.env.NEXTLY_DELETE_THRESHOLD;
|
|
215
|
+
if (!env) return 1e4;
|
|
216
|
+
const n = parseInt(env, 10);
|
|
217
|
+
return Number.isFinite(n) && n > 0 ? n : 1e4;
|
|
218
|
+
}
|
|
219
|
+
function assertSafeIdent(name) {
|
|
220
|
+
if (!SAFE_IDENT.test(name)) {
|
|
221
|
+
throw new Error(
|
|
222
|
+
`unsafe identifier: ${name} (only [A-Za-z_][A-Za-z0-9_]* allowed)`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
var RealPreCleanupExecutor = class {
|
|
227
|
+
async execute(args) {
|
|
228
|
+
for (const r of args.resolutions) {
|
|
229
|
+
if (r.kind === "abort") {
|
|
230
|
+
throw new PromptCancelledError();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const seenEventIds = /* @__PURE__ */ new Set();
|
|
234
|
+
for (const r of args.resolutions) {
|
|
235
|
+
if (seenEventIds.has(r.eventId)) {
|
|
236
|
+
throw new Error(
|
|
237
|
+
`DUPLICATE_RESOLUTION_FOR_EVENT: ${r.eventId} has multiple resolutions attached`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
seenEventIds.add(r.eventId);
|
|
241
|
+
}
|
|
242
|
+
const eventById = new Map(
|
|
243
|
+
args.events.map((e) => [e.id, e])
|
|
244
|
+
);
|
|
245
|
+
const threshold = readDeleteThreshold();
|
|
246
|
+
for (const r of args.resolutions) {
|
|
247
|
+
const event = eventById.get(r.eventId);
|
|
248
|
+
if (!event) continue;
|
|
249
|
+
if (event.kind !== "add_not_null_with_nulls" && event.kind !== "add_required_field_no_default") {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (r.kind === "provide_default") {
|
|
253
|
+
const field = args.fields.find((f) => f.name === event.columnName);
|
|
254
|
+
const fieldType = field?.type ?? "text";
|
|
255
|
+
const validation = validateDefaultValue(
|
|
256
|
+
{ name: event.columnName, type: fieldType },
|
|
257
|
+
r.value
|
|
258
|
+
);
|
|
259
|
+
if (!validation.success) {
|
|
260
|
+
throw new Error(
|
|
261
|
+
`INVALID_DEFAULT_FOR_TYPE: ${event.columnName} (${fieldType}) - ${validation.error}`
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
assertSafeIdent(event.tableName);
|
|
265
|
+
assertSafeIdent(event.columnName);
|
|
266
|
+
const stmt = sql`UPDATE ${sql.identifier(event.tableName)} SET ${sql.identifier(event.columnName)} = ${r.value} WHERE ${sql.identifier(event.columnName)} IS NULL`;
|
|
267
|
+
await runStatement(args.tx, args.dialect, stmt);
|
|
268
|
+
} else if (r.kind === "delete_nonconforming") {
|
|
269
|
+
if (event.kind === "add_not_null_with_nulls" && event.nullCount >= threshold) {
|
|
270
|
+
throw new Error(
|
|
271
|
+
`DELETE_THRESHOLD_EXCEEDED: ${event.nullCount} rows >= ${threshold}; explicit confirmation required`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
assertSafeIdent(event.tableName);
|
|
275
|
+
assertSafeIdent(event.columnName);
|
|
276
|
+
const stmt = sql`DELETE FROM ${sql.identifier(event.tableName)} WHERE ${sql.identifier(event.columnName)} IS NULL`;
|
|
277
|
+
await runStatement(args.tx, args.dialect, stmt);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return applyMakeOptionalToSnapshot(
|
|
281
|
+
args.desiredSnapshot,
|
|
282
|
+
args.resolutions,
|
|
283
|
+
args.events
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
// src/domains/schema/pipeline/pushschema-pipeline.ts
|
|
289
|
+
import { dequal } from "dequal";
|
|
290
|
+
|
|
291
|
+
// src/init/schema-snapshot-cache.ts
|
|
292
|
+
var g = globalThis;
|
|
293
|
+
function getCachedSnapshot() {
|
|
294
|
+
return g.__nextly_prevSchemaSnapshot;
|
|
295
|
+
}
|
|
296
|
+
function setCachedSnapshot(snapshot) {
|
|
297
|
+
g.__nextly_prevSchemaSnapshot = snapshot;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/runtime/notifications/build-event.ts
|
|
301
|
+
function buildNotificationEvent(args) {
|
|
302
|
+
const ts = (args.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
303
|
+
if (args.success) {
|
|
304
|
+
return {
|
|
305
|
+
ts,
|
|
306
|
+
source: args.source,
|
|
307
|
+
status: "success",
|
|
308
|
+
scope: args.scope,
|
|
309
|
+
summary: args.summary,
|
|
310
|
+
durationMs: args.durationMs,
|
|
311
|
+
journalId: args.journalId
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
return {
|
|
315
|
+
ts,
|
|
316
|
+
source: args.source,
|
|
317
|
+
status: "failed",
|
|
318
|
+
scope: args.scope,
|
|
319
|
+
summary: args.summary,
|
|
320
|
+
durationMs: args.durationMs,
|
|
321
|
+
journalId: args.journalId,
|
|
322
|
+
error: args.error
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// src/domains/schema/pipeline/managed-tables.ts
|
|
327
|
+
var MANAGED_TABLE_PREFIXES_REGEX = /^(dc_|single_|comp_)/;
|
|
328
|
+
function isManagedTable(name) {
|
|
329
|
+
return MANAGED_TABLE_PREFIXES_REGEX.test(name);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// src/domains/schema/pipeline/pre-resolution/apply-resolutions.ts
|
|
333
|
+
function applyResolutionsToOperations(ops, resolutions) {
|
|
334
|
+
const renamesToApply = resolutions.filter((r) => r.choice === "rename");
|
|
335
|
+
if (renamesToApply.length === 0) return ops;
|
|
336
|
+
const dropIndexByKey = /* @__PURE__ */ new Map();
|
|
337
|
+
const addIndexByKey = /* @__PURE__ */ new Map();
|
|
338
|
+
ops.forEach((op, i) => {
|
|
339
|
+
if (op.type === "drop_column") {
|
|
340
|
+
dropIndexByKey.set(keyDrop(op), i);
|
|
341
|
+
} else if (op.type === "add_column") {
|
|
342
|
+
addIndexByKey.set(keyAdd(op), i);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
346
|
+
const renameOps = [];
|
|
347
|
+
for (const r of renamesToApply) {
|
|
348
|
+
const dropKey = `${r.tableName}::${r.fromColumn}`;
|
|
349
|
+
const addKey = `${r.tableName}::${r.toColumn}`;
|
|
350
|
+
const dropIdx = dropIndexByKey.get(dropKey);
|
|
351
|
+
const addIdx = addIndexByKey.get(addKey);
|
|
352
|
+
if (dropIdx === void 0 || addIdx === void 0) continue;
|
|
353
|
+
if (consumed.has(dropIdx) || consumed.has(addIdx)) continue;
|
|
354
|
+
const dropOp = ops[dropIdx];
|
|
355
|
+
const addOp = ops[addIdx];
|
|
356
|
+
consumed.add(dropIdx);
|
|
357
|
+
consumed.add(addIdx);
|
|
358
|
+
renameOps.push({
|
|
359
|
+
type: "rename_column",
|
|
360
|
+
tableName: r.tableName,
|
|
361
|
+
fromColumn: r.fromColumn,
|
|
362
|
+
toColumn: r.toColumn,
|
|
363
|
+
fromType: dropOp.columnType,
|
|
364
|
+
toType: addOp.column.type
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
const out = [];
|
|
368
|
+
for (let i = 0; i < ops.length; i++) {
|
|
369
|
+
if (!consumed.has(i)) out.push(ops[i]);
|
|
370
|
+
}
|
|
371
|
+
out.push(...renameOps);
|
|
372
|
+
return out;
|
|
373
|
+
}
|
|
374
|
+
function keyDrop(op) {
|
|
375
|
+
return `${op.tableName}::${op.columnName}`;
|
|
376
|
+
}
|
|
377
|
+
function keyAdd(op) {
|
|
378
|
+
return `${op.tableName}::${op.column.name}`;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// src/domains/schema/pipeline/pre-resolution/executor.ts
|
|
382
|
+
import { sql as sql2 } from "drizzle-orm";
|
|
383
|
+
|
|
384
|
+
// src/domains/schema/pipeline/diff/types.ts
|
|
385
|
+
var PRE_RESOLUTION_OP_TYPES = [
|
|
386
|
+
"rename_column",
|
|
387
|
+
"rename_table",
|
|
388
|
+
"drop_column",
|
|
389
|
+
"drop_table"
|
|
390
|
+
];
|
|
391
|
+
function isPreResolutionOp(op) {
|
|
392
|
+
return PRE_RESOLUTION_OP_TYPES.includes(op.type);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// src/domains/schema/pipeline/sql-templates/identifier-quoting.ts
|
|
396
|
+
function quoteIdent(name, dialect) {
|
|
397
|
+
const q4 = dialect === "mysql" ? "`" : '"';
|
|
398
|
+
if (name.includes(q4)) {
|
|
399
|
+
throw new Error(
|
|
400
|
+
`Invalid identifier ${JSON.stringify(name)}: contains the dialect quote character (${q4}). Managed tables and FieldConfig column names must not contain quote characters.`
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
return `${q4}${name}${q4}`;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/domains/schema/pipeline/sql-templates/mysql.ts
|
|
407
|
+
var q = (n) => quoteIdent(n, "mysql");
|
|
408
|
+
function columnDef(c) {
|
|
409
|
+
const nullable = c.nullable ? "" : " NOT NULL";
|
|
410
|
+
const def = c.default !== void 0 ? ` DEFAULT ${c.default}` : "";
|
|
411
|
+
return `${q(c.name)} ${c.type}${nullable}${def}`;
|
|
412
|
+
}
|
|
413
|
+
function generateMysqlSQL(op) {
|
|
414
|
+
switch (op.type) {
|
|
415
|
+
case "add_table":
|
|
416
|
+
return generateAddTable(op);
|
|
417
|
+
case "drop_table":
|
|
418
|
+
return generateDropTable(op);
|
|
419
|
+
case "rename_table":
|
|
420
|
+
return generateRenameTable(op);
|
|
421
|
+
case "add_column":
|
|
422
|
+
return generateAddColumn(op);
|
|
423
|
+
case "drop_column":
|
|
424
|
+
return generateDropColumn(op);
|
|
425
|
+
case "rename_column":
|
|
426
|
+
return generateRenameColumn(op);
|
|
427
|
+
case "change_column_type":
|
|
428
|
+
return generateChangeColumnType(op);
|
|
429
|
+
case "change_column_nullable":
|
|
430
|
+
return generateChangeColumnNullable(op);
|
|
431
|
+
case "change_column_default":
|
|
432
|
+
return generateChangeColumnDefault(op);
|
|
433
|
+
default: {
|
|
434
|
+
const exhaustive = op;
|
|
435
|
+
void exhaustive;
|
|
436
|
+
throw new Error(
|
|
437
|
+
`generateMysqlSQL: unsupported op ${op.type}`
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function generateAddTable(op) {
|
|
443
|
+
const cols = op.table.columns.map((c) => ` ${columnDef(c)}`).join(",\n");
|
|
444
|
+
return `CREATE TABLE ${q(op.table.name)} (
|
|
445
|
+
${cols}
|
|
446
|
+
)`;
|
|
447
|
+
}
|
|
448
|
+
function generateDropTable(op) {
|
|
449
|
+
return `DROP TABLE ${q(op.tableName)}`;
|
|
450
|
+
}
|
|
451
|
+
function generateRenameTable(op) {
|
|
452
|
+
return `ALTER TABLE ${q(op.fromName)} RENAME TO ${q(op.toName)}`;
|
|
453
|
+
}
|
|
454
|
+
function generateAddColumn(op) {
|
|
455
|
+
return `ALTER TABLE ${q(op.tableName)} ADD COLUMN ${columnDef(op.column)}`;
|
|
456
|
+
}
|
|
457
|
+
function generateDropColumn(op) {
|
|
458
|
+
return `ALTER TABLE ${q(op.tableName)} DROP COLUMN ${q(op.columnName)}`;
|
|
459
|
+
}
|
|
460
|
+
function generateRenameColumn(op) {
|
|
461
|
+
return `ALTER TABLE ${q(op.tableName)} RENAME COLUMN ${q(op.fromColumn)} TO ${q(op.toColumn)}`;
|
|
462
|
+
}
|
|
463
|
+
function generateChangeColumnType(op) {
|
|
464
|
+
return `ALTER TABLE ${q(op.tableName)} MODIFY COLUMN ${q(op.columnName)} ${op.toType}`;
|
|
465
|
+
}
|
|
466
|
+
function generateChangeColumnNullable(op) {
|
|
467
|
+
throw new MysqlUnsupportedOperationError(
|
|
468
|
+
"change_column_nullable",
|
|
469
|
+
`Cannot generate valid MODIFY COLUMN SQL for ${op.tableName}.${op.columnName} because the column type is not tracked on this operation. F12 will extend ChangeColumnNullableOp with the type. For now, use 'nextly migrate:create --blank' and hand-write 'ALTER TABLE \`${op.tableName}\` MODIFY COLUMN \`${op.columnName}\` <TYPE> ${op.toNullable ? "NULL" : "NOT NULL"};'.`
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
function generateChangeColumnDefault(op) {
|
|
473
|
+
return op.toDefault === void 0 ? `ALTER TABLE ${q(op.tableName)} ALTER COLUMN ${q(op.columnName)} DROP DEFAULT` : `ALTER TABLE ${q(op.tableName)} ALTER COLUMN ${q(op.columnName)} SET DEFAULT ${op.toDefault}`;
|
|
474
|
+
}
|
|
475
|
+
var MysqlUnsupportedOperationError = class extends Error {
|
|
476
|
+
constructor(opType, hint) {
|
|
477
|
+
super(`MySQL F11 PR 3 limitation: ${opType}. ${hint}`);
|
|
478
|
+
this.name = "MysqlUnsupportedOperationError";
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
// src/domains/schema/pipeline/sql-templates/postgres.ts
|
|
483
|
+
var q2 = (n) => quoteIdent(n, "postgresql");
|
|
484
|
+
function columnDef2(c) {
|
|
485
|
+
const nullable = c.nullable ? "" : " NOT NULL";
|
|
486
|
+
const def = c.default !== void 0 ? ` DEFAULT ${c.default}` : "";
|
|
487
|
+
return `${q2(c.name)} ${c.type}${nullable}${def}`;
|
|
488
|
+
}
|
|
489
|
+
function generatePgSQL(op) {
|
|
490
|
+
switch (op.type) {
|
|
491
|
+
case "add_table":
|
|
492
|
+
return generateAddTable2(op);
|
|
493
|
+
case "drop_table":
|
|
494
|
+
return generateDropTable2(op);
|
|
495
|
+
case "rename_table":
|
|
496
|
+
return generateRenameTable2(op);
|
|
497
|
+
case "add_column":
|
|
498
|
+
return generateAddColumn2(op);
|
|
499
|
+
case "drop_column":
|
|
500
|
+
return generateDropColumn2(op);
|
|
501
|
+
case "rename_column":
|
|
502
|
+
return generateRenameColumn2(op);
|
|
503
|
+
case "change_column_type":
|
|
504
|
+
return generateChangeColumnType2(op);
|
|
505
|
+
case "change_column_nullable":
|
|
506
|
+
return generateChangeColumnNullable2(op);
|
|
507
|
+
case "change_column_default":
|
|
508
|
+
return generateChangeColumnDefault2(op);
|
|
509
|
+
default: {
|
|
510
|
+
const exhaustive = op;
|
|
511
|
+
void exhaustive;
|
|
512
|
+
throw new Error(
|
|
513
|
+
`generatePgSQL: unsupported op ${op.type}`
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
function generateAddTable2(op) {
|
|
519
|
+
const cols = op.table.columns.map((c) => ` ${columnDef2(c)}`).join(",\n");
|
|
520
|
+
return `CREATE TABLE ${q2(op.table.name)} (
|
|
521
|
+
${cols}
|
|
522
|
+
)`;
|
|
523
|
+
}
|
|
524
|
+
function generateDropTable2(op) {
|
|
525
|
+
return `DROP TABLE ${q2(op.tableName)} CASCADE`;
|
|
526
|
+
}
|
|
527
|
+
function generateRenameTable2(op) {
|
|
528
|
+
return `ALTER TABLE ${q2(op.fromName)} RENAME TO ${q2(op.toName)}`;
|
|
529
|
+
}
|
|
530
|
+
function generateAddColumn2(op) {
|
|
531
|
+
return `ALTER TABLE ${q2(op.tableName)} ADD COLUMN ${columnDef2(op.column)}`;
|
|
532
|
+
}
|
|
533
|
+
function generateDropColumn2(op) {
|
|
534
|
+
return `ALTER TABLE ${q2(op.tableName)} DROP COLUMN ${q2(op.columnName)}`;
|
|
535
|
+
}
|
|
536
|
+
function generateRenameColumn2(op) {
|
|
537
|
+
return `ALTER TABLE ${q2(op.tableName)} RENAME COLUMN ${q2(op.fromColumn)} TO ${q2(op.toColumn)}`;
|
|
538
|
+
}
|
|
539
|
+
function generateChangeColumnType2(op) {
|
|
540
|
+
return `ALTER TABLE ${q2(op.tableName)} ALTER COLUMN ${q2(op.columnName)} TYPE ${op.toType}`;
|
|
541
|
+
}
|
|
542
|
+
function generateChangeColumnNullable2(op) {
|
|
543
|
+
return op.toNullable ? `ALTER TABLE ${q2(op.tableName)} ALTER COLUMN ${q2(op.columnName)} DROP NOT NULL` : `ALTER TABLE ${q2(op.tableName)} ALTER COLUMN ${q2(op.columnName)} SET NOT NULL`;
|
|
544
|
+
}
|
|
545
|
+
function generateChangeColumnDefault2(op) {
|
|
546
|
+
return op.toDefault === void 0 ? `ALTER TABLE ${q2(op.tableName)} ALTER COLUMN ${q2(op.columnName)} DROP DEFAULT` : `ALTER TABLE ${q2(op.tableName)} ALTER COLUMN ${q2(op.columnName)} SET DEFAULT ${op.toDefault}`;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// src/domains/schema/pipeline/sql-templates/sqlite.ts
|
|
550
|
+
var q3 = (n) => quoteIdent(n, "sqlite");
|
|
551
|
+
function columnDef3(c) {
|
|
552
|
+
const nullable = c.nullable ? "" : " NOT NULL";
|
|
553
|
+
const def = c.default !== void 0 ? ` DEFAULT ${c.default}` : "";
|
|
554
|
+
return `${q3(c.name)} ${c.type}${nullable}${def}`;
|
|
555
|
+
}
|
|
556
|
+
var SqliteUnsupportedOperationError = class extends Error {
|
|
557
|
+
constructor(opType, hint) {
|
|
558
|
+
super(
|
|
559
|
+
`SQLite does not support ${opType} in place. ${hint} For migrate:create, you may need to write a manual recreate-table migration via --blank.`
|
|
560
|
+
);
|
|
561
|
+
this.name = "SqliteUnsupportedOperationError";
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
function generateSqliteSQL(op) {
|
|
565
|
+
switch (op.type) {
|
|
566
|
+
case "add_table":
|
|
567
|
+
return generateAddTable3(op);
|
|
568
|
+
case "drop_table":
|
|
569
|
+
return generateDropTable3(op);
|
|
570
|
+
case "rename_table":
|
|
571
|
+
return generateRenameTable3(op);
|
|
572
|
+
case "add_column":
|
|
573
|
+
return generateAddColumn3(op);
|
|
574
|
+
case "drop_column":
|
|
575
|
+
return generateDropColumn3(op);
|
|
576
|
+
case "rename_column":
|
|
577
|
+
return generateRenameColumn3(op);
|
|
578
|
+
case "change_column_type":
|
|
579
|
+
throw new SqliteUnsupportedOperationError(
|
|
580
|
+
"change_column_type",
|
|
581
|
+
"Use ALTER TABLE ... RENAME TO ... + CREATE TABLE ... + INSERT INTO ... SELECT + DROP TABLE ..."
|
|
582
|
+
);
|
|
583
|
+
case "change_column_nullable":
|
|
584
|
+
throw new SqliteUnsupportedOperationError(
|
|
585
|
+
"change_column_nullable",
|
|
586
|
+
"Same recreate-table workaround as change_column_type."
|
|
587
|
+
);
|
|
588
|
+
case "change_column_default":
|
|
589
|
+
throw new SqliteUnsupportedOperationError(
|
|
590
|
+
"change_column_default",
|
|
591
|
+
"Same recreate-table workaround as change_column_type."
|
|
592
|
+
);
|
|
593
|
+
default: {
|
|
594
|
+
const exhaustive = op;
|
|
595
|
+
void exhaustive;
|
|
596
|
+
throw new Error(
|
|
597
|
+
`generateSqliteSQL: unsupported op ${op.type}`
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
function generateAddTable3(op) {
|
|
603
|
+
const cols = op.table.columns.map((c) => ` ${columnDef3(c)}`).join(",\n");
|
|
604
|
+
return `CREATE TABLE ${q3(op.table.name)} (
|
|
605
|
+
${cols}
|
|
606
|
+
)`;
|
|
607
|
+
}
|
|
608
|
+
function generateDropTable3(op) {
|
|
609
|
+
return `DROP TABLE ${q3(op.tableName)}`;
|
|
610
|
+
}
|
|
611
|
+
function generateRenameTable3(op) {
|
|
612
|
+
return `ALTER TABLE ${q3(op.fromName)} RENAME TO ${q3(op.toName)}`;
|
|
613
|
+
}
|
|
614
|
+
function generateAddColumn3(op) {
|
|
615
|
+
return `ALTER TABLE ${q3(op.tableName)} ADD COLUMN ${columnDef3(op.column)}`;
|
|
616
|
+
}
|
|
617
|
+
function generateDropColumn3(op) {
|
|
618
|
+
return `ALTER TABLE ${q3(op.tableName)} DROP COLUMN ${q3(op.columnName)}`;
|
|
619
|
+
}
|
|
620
|
+
function generateRenameColumn3(op) {
|
|
621
|
+
return `ALTER TABLE ${q3(op.tableName)} RENAME COLUMN ${q3(op.fromColumn)} TO ${q3(op.toColumn)}`;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// src/domains/schema/pipeline/sql-templates/index.ts
|
|
625
|
+
function generateSQL(op, dialect) {
|
|
626
|
+
switch (dialect) {
|
|
627
|
+
case "postgresql":
|
|
628
|
+
return generatePgSQL(op);
|
|
629
|
+
case "mysql":
|
|
630
|
+
return generateMysqlSQL(op);
|
|
631
|
+
case "sqlite":
|
|
632
|
+
return generateSqliteSQL(op);
|
|
633
|
+
default: {
|
|
634
|
+
const exhaustive = dialect;
|
|
635
|
+
void exhaustive;
|
|
636
|
+
throw new Error(`generateSQL: unsupported dialect ${dialect}`);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// src/domains/schema/pipeline/pre-resolution/executor.ts
|
|
642
|
+
async function executePreResolutionOps(txOrDb, ops, dialect) {
|
|
643
|
+
const preOps = ops.filter(isPreResolutionOp);
|
|
644
|
+
if (preOps.length === 0) return 0;
|
|
645
|
+
const ordered = orderForExecution(preOps);
|
|
646
|
+
for (const op of ordered) {
|
|
647
|
+
const sqlString = sqlForOp(op, dialect);
|
|
648
|
+
await runRaw(txOrDb, sqlString, dialect);
|
|
649
|
+
}
|
|
650
|
+
return ordered.length;
|
|
651
|
+
}
|
|
652
|
+
function orderForExecution(ops) {
|
|
653
|
+
const renameTables = [];
|
|
654
|
+
const renameColumns = [];
|
|
655
|
+
const dropColumns = [];
|
|
656
|
+
const dropTables = [];
|
|
657
|
+
for (const op of ops) {
|
|
658
|
+
if (op.type === "rename_table") renameTables.push(op);
|
|
659
|
+
else if (op.type === "rename_column") renameColumns.push(op);
|
|
660
|
+
else if (op.type === "drop_column") dropColumns.push(op);
|
|
661
|
+
else if (op.type === "drop_table") dropTables.push(op);
|
|
662
|
+
}
|
|
663
|
+
return [...renameTables, ...renameColumns, ...dropColumns, ...dropTables];
|
|
664
|
+
}
|
|
665
|
+
function sqlForOp(op, dialect) {
|
|
666
|
+
return generateSQL(op, dialect);
|
|
667
|
+
}
|
|
668
|
+
async function runRaw(txOrDb, sqlString, dialect) {
|
|
669
|
+
if (dialect === "sqlite") {
|
|
670
|
+
const handle2 = txOrDb;
|
|
671
|
+
handle2.run(sql2.raw(sqlString));
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
const handle = txOrDb;
|
|
675
|
+
await handle.execute(sql2.raw(sqlString));
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// src/domains/schema/pipeline/stdout-capture.ts
|
|
679
|
+
async function withCapturedStdout(work, logger) {
|
|
680
|
+
if (process.stdin.isTTY) {
|
|
681
|
+
return work();
|
|
682
|
+
}
|
|
683
|
+
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
684
|
+
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
685
|
+
let stdoutBuffer = "";
|
|
686
|
+
let stderrBuffer = "";
|
|
687
|
+
process.stdout.write = ((chunk) => {
|
|
688
|
+
stdoutBuffer += String(chunk);
|
|
689
|
+
return true;
|
|
690
|
+
});
|
|
691
|
+
process.stderr.write = ((chunk) => {
|
|
692
|
+
stderrBuffer += String(chunk);
|
|
693
|
+
return true;
|
|
694
|
+
});
|
|
695
|
+
try {
|
|
696
|
+
const result = await work();
|
|
697
|
+
flushBufferedToDebug("[drizzle-kit stdout]", stdoutBuffer, logger);
|
|
698
|
+
flushBufferedToDebug("[drizzle-kit stderr]", stderrBuffer, logger);
|
|
699
|
+
return result;
|
|
700
|
+
} finally {
|
|
701
|
+
process.stdout.write = originalStdoutWrite;
|
|
702
|
+
process.stderr.write = originalStderrWrite;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
function flushBufferedToDebug(prefix, buffer, logger) {
|
|
706
|
+
if (!buffer) return;
|
|
707
|
+
const trimmed = buffer.replace(/\n+$/, "");
|
|
708
|
+
if (!trimmed) return;
|
|
709
|
+
if (logger?.debug) {
|
|
710
|
+
logger.debug(`${prefix} ${trimmed}`);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// src/domains/schema/pipeline/pushschema-pipeline.ts
|
|
715
|
+
function applyMakeOptionalToDesired(desired, resolutions, events) {
|
|
716
|
+
const makeOptionalEventIds = new Set(
|
|
717
|
+
resolutions.filter((r) => r.kind === "make_optional").map((r) => r.eventId)
|
|
718
|
+
);
|
|
719
|
+
if (makeOptionalEventIds.size === 0) return desired;
|
|
720
|
+
const targets = /* @__PURE__ */ new Map();
|
|
721
|
+
for (const event of events) {
|
|
722
|
+
if (makeOptionalEventIds.has(event.id) && (event.kind === "add_not_null_with_nulls" || event.kind === "add_required_field_no_default")) {
|
|
723
|
+
targets.set(event.id, {
|
|
724
|
+
table: event.tableName,
|
|
725
|
+
column: event.columnName
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
if (targets.size === 0) return desired;
|
|
730
|
+
const patchCollection = (coll) => {
|
|
731
|
+
const matchingTargets = [...targets.values()].filter(
|
|
732
|
+
(t) => t.table === coll.tableName
|
|
733
|
+
);
|
|
734
|
+
if (matchingTargets.length === 0) return coll;
|
|
735
|
+
return {
|
|
736
|
+
...coll,
|
|
737
|
+
fields: coll.fields.map((field) => {
|
|
738
|
+
const matched = matchingTargets.some((t) => t.column === field.name);
|
|
739
|
+
if (!matched) return field;
|
|
740
|
+
return { ...field, required: false };
|
|
741
|
+
})
|
|
742
|
+
};
|
|
743
|
+
};
|
|
744
|
+
return {
|
|
745
|
+
...desired,
|
|
746
|
+
collections: Object.fromEntries(
|
|
747
|
+
Object.entries(desired.collections).map(([slug, c]) => [
|
|
748
|
+
slug,
|
|
749
|
+
patchCollection(c)
|
|
750
|
+
])
|
|
751
|
+
),
|
|
752
|
+
singles: Object.fromEntries(
|
|
753
|
+
Object.entries(desired.singles).map(([slug, s]) => [
|
|
754
|
+
slug,
|
|
755
|
+
patchCollection(s)
|
|
756
|
+
])
|
|
757
|
+
),
|
|
758
|
+
components: Object.fromEntries(
|
|
759
|
+
Object.entries(desired.components).map(([slug, c]) => [
|
|
760
|
+
slug,
|
|
761
|
+
patchCollection(c)
|
|
762
|
+
])
|
|
763
|
+
)
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
var PushSchemaError = class extends Error {
|
|
767
|
+
constructor(message, cause) {
|
|
768
|
+
super(message);
|
|
769
|
+
this.cause = cause;
|
|
770
|
+
this.name = "PushSchemaError";
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
var DdlExecutionError = class extends Error {
|
|
774
|
+
constructor(message, cause) {
|
|
775
|
+
super(message);
|
|
776
|
+
this.cause = cause;
|
|
777
|
+
this.name = "DdlExecutionError";
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
function computeJournalSummaryFromOperations(operations) {
|
|
781
|
+
let added = 0;
|
|
782
|
+
let removed = 0;
|
|
783
|
+
let renamed = 0;
|
|
784
|
+
let changed = 0;
|
|
785
|
+
for (const op of operations) {
|
|
786
|
+
switch (op.type) {
|
|
787
|
+
case "add_table":
|
|
788
|
+
case "add_column":
|
|
789
|
+
added++;
|
|
790
|
+
break;
|
|
791
|
+
case "drop_table":
|
|
792
|
+
case "drop_column":
|
|
793
|
+
removed++;
|
|
794
|
+
break;
|
|
795
|
+
case "rename_table":
|
|
796
|
+
case "rename_column":
|
|
797
|
+
renamed++;
|
|
798
|
+
break;
|
|
799
|
+
case "change_column_type":
|
|
800
|
+
case "change_column_nullable":
|
|
801
|
+
case "change_column_default":
|
|
802
|
+
changed++;
|
|
803
|
+
break;
|
|
804
|
+
default: {
|
|
805
|
+
const exhaustive = op;
|
|
806
|
+
void exhaustive;
|
|
807
|
+
break;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
return { added, removed, renamed, changed };
|
|
812
|
+
}
|
|
813
|
+
function computeJournalScope(source, uiTargetSlug) {
|
|
814
|
+
if (source === "ui" && uiTargetSlug) {
|
|
815
|
+
return { kind: "collection", slug: uiTargetSlug };
|
|
816
|
+
}
|
|
817
|
+
return { kind: "global" };
|
|
818
|
+
}
|
|
819
|
+
function toNotificationScope(scope) {
|
|
820
|
+
if (scope.kind === "fresh-push") return { kind: "fresh-push" };
|
|
821
|
+
if (scope.kind === "global") {
|
|
822
|
+
return scope.slug ? { kind: "global", slug: scope.slug } : { kind: "global" };
|
|
823
|
+
}
|
|
824
|
+
return scope.slug ? { kind: scope.kind, slug: scope.slug } : { kind: "global" };
|
|
825
|
+
}
|
|
826
|
+
var PushSchemaPipeline = class {
|
|
827
|
+
constructor(deps, testHooks = {}) {
|
|
828
|
+
this.deps = deps;
|
|
829
|
+
this.testHooks = testHooks;
|
|
830
|
+
}
|
|
831
|
+
async apply(args) {
|
|
832
|
+
const { desired, db, dialect, source, promptChannel, databaseName } = args;
|
|
833
|
+
const scope = computeJournalScope(source, args.uiTargetSlug);
|
|
834
|
+
const startMs = Date.now();
|
|
835
|
+
const cachedSnapshot = getCachedSnapshot();
|
|
836
|
+
if (cachedSnapshot !== void 0 && dequal(desired, cachedSnapshot)) {
|
|
837
|
+
console.log(
|
|
838
|
+
"[Nextly schema] No changes detected since last apply; skipping push (dequal cache hit)."
|
|
839
|
+
);
|
|
840
|
+
return {
|
|
841
|
+
success: true,
|
|
842
|
+
statementsExecuted: 0,
|
|
843
|
+
renamesApplied: 0,
|
|
844
|
+
// Zero-count summary — same shape as the full-success branch,
|
|
845
|
+
// so the dispatcher's notification rendering doesn't have to
|
|
846
|
+
// distinguish "no-op" from "real-success-with-no-ops".
|
|
847
|
+
summary: {
|
|
848
|
+
added: 0,
|
|
849
|
+
removed: 0,
|
|
850
|
+
renamed: 0,
|
|
851
|
+
changed: 0
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
const journalId = await this.deps.migrationJournal.recordStart({
|
|
856
|
+
source,
|
|
857
|
+
statementsPlanned: 0,
|
|
858
|
+
scope,
|
|
859
|
+
batch: source === "code" ? -1 : void 0
|
|
860
|
+
});
|
|
861
|
+
try {
|
|
862
|
+
const managedTableNames = [
|
|
863
|
+
...Object.values(desired.collections).map((c) => c.tableName),
|
|
864
|
+
...Object.values(desired.singles).map((s) => s.tableName),
|
|
865
|
+
...Object.values(desired.components).map((c) => c.tableName)
|
|
866
|
+
];
|
|
867
|
+
const liveSnapshot = this.testHooks._introspectSnapshotOverride ? await this.testHooks._introspectSnapshotOverride(
|
|
868
|
+
db,
|
|
869
|
+
dialect,
|
|
870
|
+
managedTableNames
|
|
871
|
+
) : await introspectLiveSnapshot(db, dialect, managedTableNames);
|
|
872
|
+
const desiredSnapshot = {
|
|
873
|
+
tables: [
|
|
874
|
+
...Object.values(desired.collections).map(
|
|
875
|
+
(c) => buildDesiredTableFromFields(
|
|
876
|
+
c.tableName,
|
|
877
|
+
// FieldConfig has the shape buildDesiredTableFromFields expects;
|
|
878
|
+
// cast through unknown for the structural-vs-nominal type gap.
|
|
879
|
+
c.fields,
|
|
880
|
+
dialect,
|
|
881
|
+
// Thread the status flag so the diff includes the status system
|
|
882
|
+
// column when Draft/Published is enabled.
|
|
883
|
+
{ hasStatus: c.status === true }
|
|
884
|
+
)
|
|
885
|
+
),
|
|
886
|
+
...Object.values(desired.singles).map(
|
|
887
|
+
(s) => buildDesiredTableFromFields(
|
|
888
|
+
s.tableName,
|
|
889
|
+
s.fields,
|
|
890
|
+
dialect,
|
|
891
|
+
{ hasStatus: s.status === true }
|
|
892
|
+
)
|
|
893
|
+
),
|
|
894
|
+
...Object.values(desired.components).map(
|
|
895
|
+
(c) => buildDesiredTableFromComponentFields(
|
|
896
|
+
c.tableName,
|
|
897
|
+
c.fields,
|
|
898
|
+
dialect
|
|
899
|
+
)
|
|
900
|
+
)
|
|
901
|
+
]
|
|
902
|
+
};
|
|
903
|
+
const operations = diffSnapshots(liveSnapshot, desiredSnapshot);
|
|
904
|
+
const candidates = this.deps.renameDetector.detect(operations, dialect);
|
|
905
|
+
const classificationResult = await this.deps.classifier.classify({
|
|
906
|
+
operations,
|
|
907
|
+
drizzleWarnings: [],
|
|
908
|
+
hasDataLoss: false,
|
|
909
|
+
countNulls: (table, column) => countNulls(db, dialect, table, column),
|
|
910
|
+
countRows: (table) => countRows(db, dialect, table),
|
|
911
|
+
dialect
|
|
912
|
+
});
|
|
913
|
+
const dispatchResult = candidates.length > 0 || classificationResult.level !== "safe" ? await this.deps.promptDispatcher.dispatch({
|
|
914
|
+
candidates,
|
|
915
|
+
events: classificationResult.events,
|
|
916
|
+
classification: classificationResult.level,
|
|
917
|
+
channel: promptChannel
|
|
918
|
+
}) : {
|
|
919
|
+
confirmedRenames: [],
|
|
920
|
+
resolutions: [],
|
|
921
|
+
proceed: true
|
|
922
|
+
};
|
|
923
|
+
if (!dispatchResult.proceed) {
|
|
924
|
+
throw new PromptCancelledError();
|
|
925
|
+
}
|
|
926
|
+
const patchedDesired = applyMakeOptionalToDesired(
|
|
927
|
+
desired,
|
|
928
|
+
dispatchResult.resolutions,
|
|
929
|
+
classificationResult.events
|
|
930
|
+
);
|
|
931
|
+
const resolvedOps = applyResolutionsToOperations(
|
|
932
|
+
operations,
|
|
933
|
+
toRenameResolutions(dispatchResult.confirmedRenames, candidates)
|
|
934
|
+
);
|
|
935
|
+
const drizzleSchema = this.testHooks._buildDrizzleSchemaOverride ? this.testHooks._buildDrizzleSchemaOverride(patchedDesired, dialect) : this.buildDrizzleSchema(patchedDesired, dialect);
|
|
936
|
+
const kit = this.testHooks._kitOverride ? this.testHooks._kitOverride : await this.importDrizzleKit(dialect, databaseName);
|
|
937
|
+
const isSqlite = dialect === "sqlite";
|
|
938
|
+
const runApply = async (tx) => {
|
|
939
|
+
const preResExecutor = this.testHooks._executePreResolutionOverride ?? executePreResolutionOps;
|
|
940
|
+
try {
|
|
941
|
+
await preResExecutor(tx, resolvedOps, dialect);
|
|
942
|
+
} catch (err) {
|
|
943
|
+
throw new DdlExecutionError(
|
|
944
|
+
err instanceof Error ? err.message : String(err),
|
|
945
|
+
err
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
const aggregatedFields = [
|
|
949
|
+
...Object.values(desired.collections),
|
|
950
|
+
...Object.values(desired.singles),
|
|
951
|
+
...Object.values(desired.components)
|
|
952
|
+
].flatMap(
|
|
953
|
+
(c) => c.fields.filter(
|
|
954
|
+
(f) => typeof f.name === "string"
|
|
955
|
+
).map((f) => ({ name: f.name, type: f.type }))
|
|
956
|
+
);
|
|
957
|
+
try {
|
|
958
|
+
await this.deps.preCleanupExecutor.execute({
|
|
959
|
+
tx,
|
|
960
|
+
desiredSnapshot,
|
|
961
|
+
resolutions: dispatchResult.resolutions,
|
|
962
|
+
events: classificationResult.events,
|
|
963
|
+
fields: aggregatedFields,
|
|
964
|
+
dialect
|
|
965
|
+
});
|
|
966
|
+
} catch (err) {
|
|
967
|
+
if (err instanceof PromptCancelledError) throw err;
|
|
968
|
+
throw new DdlExecutionError(
|
|
969
|
+
err instanceof Error ? err.message : String(err),
|
|
970
|
+
err
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
const desiredTableNames = Object.keys(drizzleSchema);
|
|
974
|
+
let pushResult;
|
|
975
|
+
try {
|
|
976
|
+
pushResult = await withCapturedStdout(
|
|
977
|
+
() => kit.pushSchema(drizzleSchema, tx, desiredTableNames),
|
|
978
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
979
|
+
process.env.DEBUG_SCHEMA === "1" ? { debug: (msg) => console.debug(msg) } : void 0
|
|
980
|
+
);
|
|
981
|
+
} catch (err) {
|
|
982
|
+
throw new PushSchemaError(
|
|
983
|
+
err instanceof Error ? err.message : String(err),
|
|
984
|
+
err
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
const safe = this.filterUnsafeStatements(
|
|
988
|
+
pushResult.statementsToExecute,
|
|
989
|
+
desiredTableNames
|
|
990
|
+
);
|
|
991
|
+
try {
|
|
992
|
+
await this.deps.executor.executeStatements(tx, safe);
|
|
993
|
+
} catch (err) {
|
|
994
|
+
throw new DdlExecutionError(
|
|
995
|
+
err instanceof Error ? err.message : String(err),
|
|
996
|
+
err
|
|
997
|
+
);
|
|
998
|
+
}
|
|
999
|
+
return safe.length;
|
|
1000
|
+
};
|
|
1001
|
+
let statementsExecuted;
|
|
1002
|
+
if (isSqlite) {
|
|
1003
|
+
await this.runSqlitePragma(db, "PRAGMA foreign_keys = OFF");
|
|
1004
|
+
try {
|
|
1005
|
+
statementsExecuted = await runApply(db);
|
|
1006
|
+
} finally {
|
|
1007
|
+
await this.runSqlitePragma(db, "PRAGMA foreign_keys = ON");
|
|
1008
|
+
}
|
|
1009
|
+
} else {
|
|
1010
|
+
const txFn = this.testHooks._txOverride ? this.testHooks._txOverride : this.makeTransactionRunner(db);
|
|
1011
|
+
statementsExecuted = await txFn(runApply);
|
|
1012
|
+
}
|
|
1013
|
+
const summary = computeJournalSummaryFromOperations(resolvedOps);
|
|
1014
|
+
setCachedSnapshot(desired);
|
|
1015
|
+
await this.deps.migrationJournal.recordEnd(journalId, {
|
|
1016
|
+
success: true,
|
|
1017
|
+
statementsExecuted,
|
|
1018
|
+
summary
|
|
1019
|
+
});
|
|
1020
|
+
await this.deps.notifier.notify(
|
|
1021
|
+
buildNotificationEvent({
|
|
1022
|
+
success: true,
|
|
1023
|
+
source,
|
|
1024
|
+
scope: toNotificationScope(scope),
|
|
1025
|
+
summary,
|
|
1026
|
+
durationMs: Date.now() - startMs,
|
|
1027
|
+
journalId
|
|
1028
|
+
})
|
|
1029
|
+
);
|
|
1030
|
+
return {
|
|
1031
|
+
success: true,
|
|
1032
|
+
statementsExecuted,
|
|
1033
|
+
renamesApplied: dispatchResult.confirmedRenames.length,
|
|
1034
|
+
// F10 PR 6: surface the diff counts so the dispatcher can
|
|
1035
|
+
// render an admin toast like "1 field added, 1 renamed".
|
|
1036
|
+
summary
|
|
1037
|
+
};
|
|
1038
|
+
} catch (err) {
|
|
1039
|
+
const code = this.classifyErrorCode(err);
|
|
1040
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1041
|
+
await this.deps.migrationJournal.recordEnd(journalId, {
|
|
1042
|
+
success: false,
|
|
1043
|
+
statementsExecuted: 0,
|
|
1044
|
+
error: err
|
|
1045
|
+
});
|
|
1046
|
+
await this.deps.notifier.notify(
|
|
1047
|
+
buildNotificationEvent({
|
|
1048
|
+
success: false,
|
|
1049
|
+
source,
|
|
1050
|
+
scope: toNotificationScope(scope),
|
|
1051
|
+
durationMs: Date.now() - startMs,
|
|
1052
|
+
journalId,
|
|
1053
|
+
error: { code, message }
|
|
1054
|
+
})
|
|
1055
|
+
);
|
|
1056
|
+
return {
|
|
1057
|
+
success: false,
|
|
1058
|
+
statementsExecuted: 0,
|
|
1059
|
+
renamesApplied: 0,
|
|
1060
|
+
error: {
|
|
1061
|
+
code,
|
|
1062
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1063
|
+
details: err
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
filterUnsafeStatements(statements, desiredTableNames) {
|
|
1069
|
+
const desiredSet = new Set(
|
|
1070
|
+
desiredTableNames.map((t) => t.toLowerCase())
|
|
1071
|
+
);
|
|
1072
|
+
return statements.filter((stmt) => {
|
|
1073
|
+
const dropMatch = stmt.match(
|
|
1074
|
+
/^DROP\s+TABLE\s+(?:IF\s+EXISTS\s+)?(?:["`]?\w+["`]?\.)?["`]?(\w+)["`]?/i
|
|
1075
|
+
);
|
|
1076
|
+
if (!dropMatch) return true;
|
|
1077
|
+
const tableName = dropMatch[1] ?? "<unknown>";
|
|
1078
|
+
const isInDesired = desiredSet.has(tableName.toLowerCase());
|
|
1079
|
+
if (isInDesired) {
|
|
1080
|
+
return true;
|
|
1081
|
+
}
|
|
1082
|
+
console.warn(
|
|
1083
|
+
`[Nextly schema] Blocked DROP TABLE "${tableName}" emitted by drizzle-kit pushSchema (table not in current desired schema). If this drop was intentional, route it through the pre-resolution executor with explicit user confirmation. (managed=${isManagedTable(tableName)})`
|
|
1084
|
+
);
|
|
1085
|
+
return false;
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
async runSqlitePragma(db, pragma) {
|
|
1089
|
+
const { sql: sqlTag } = await import("drizzle-orm");
|
|
1090
|
+
const dbTyped = db;
|
|
1091
|
+
dbTyped.run(sqlTag.raw(pragma));
|
|
1092
|
+
}
|
|
1093
|
+
buildDrizzleSchema(desired, dialect) {
|
|
1094
|
+
const out = {};
|
|
1095
|
+
for (const [exportKey, value] of Object.entries(
|
|
1096
|
+
getDialectTables(dialect)
|
|
1097
|
+
)) {
|
|
1098
|
+
if (this.isDrizzleTable(value)) {
|
|
1099
|
+
const sqlName = this.getDrizzleTableName(value, exportKey);
|
|
1100
|
+
out[sqlName] = value;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
for (const c of Object.values(desired.collections)) {
|
|
1104
|
+
const { table } = generateRuntimeSchema(
|
|
1105
|
+
c.tableName,
|
|
1106
|
+
c.fields,
|
|
1107
|
+
dialect,
|
|
1108
|
+
{ status: c.status === true }
|
|
1109
|
+
);
|
|
1110
|
+
out[c.tableName] = table;
|
|
1111
|
+
}
|
|
1112
|
+
for (const s of Object.values(desired.singles)) {
|
|
1113
|
+
const { table } = generateRuntimeSchema(
|
|
1114
|
+
s.tableName,
|
|
1115
|
+
s.fields,
|
|
1116
|
+
dialect,
|
|
1117
|
+
{ status: s.status === true }
|
|
1118
|
+
);
|
|
1119
|
+
out[s.tableName] = table;
|
|
1120
|
+
}
|
|
1121
|
+
const componentSchemaService = new ComponentSchemaService(dialect);
|
|
1122
|
+
for (const c of Object.values(desired.components)) {
|
|
1123
|
+
const componentTable = componentSchemaService.generateRuntimeSchema(
|
|
1124
|
+
c.tableName,
|
|
1125
|
+
c.fields
|
|
1126
|
+
);
|
|
1127
|
+
out[c.tableName] = componentTable;
|
|
1128
|
+
}
|
|
1129
|
+
return out;
|
|
1130
|
+
}
|
|
1131
|
+
// Phase 6 follow-up: cheap structural check for Drizzle tables.
|
|
1132
|
+
// Mirrors SchemaRegistry.isDrizzleTable but inlined to avoid
|
|
1133
|
+
// pulling SchemaRegistry's DI graph into the pipeline module.
|
|
1134
|
+
isDrizzleTable(value) {
|
|
1135
|
+
if (!value || typeof value !== "object") return false;
|
|
1136
|
+
return Symbol.for("drizzle:Name") in value;
|
|
1137
|
+
}
|
|
1138
|
+
// Phase 6 follow-up: extract a Drizzle table's SQL name.
|
|
1139
|
+
getDrizzleTableName(value, fallback) {
|
|
1140
|
+
const named = value[Symbol.for("drizzle:Name")];
|
|
1141
|
+
return typeof named === "string" ? named : fallback;
|
|
1142
|
+
}
|
|
1143
|
+
async importDrizzleKit(dialect, databaseName) {
|
|
1144
|
+
const { getPgDrizzleKit, getMySQLDrizzleKit, getSQLiteDrizzleKit } = await import("./drizzle-kit-lazy-D2M2PXR2.mjs");
|
|
1145
|
+
switch (dialect) {
|
|
1146
|
+
case "postgresql": {
|
|
1147
|
+
const kit = await getPgDrizzleKit();
|
|
1148
|
+
return {
|
|
1149
|
+
// PG drizzle-kit accepts tablesFilter (4th arg) — pass through
|
|
1150
|
+
// so introspection is scoped to just the current pipeline's
|
|
1151
|
+
// desired tables. Eliminates spurious DROP TABLE / RENAME
|
|
1152
|
+
// emissions for managed tables outside the pipeline's scope.
|
|
1153
|
+
pushSchema: (schema, db, tablesFilter) => kit.pushSchema(schema, db, ["public"], tablesFilter)
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
case "mysql": {
|
|
1157
|
+
if (!databaseName) {
|
|
1158
|
+
throw new Error(
|
|
1159
|
+
"PushSchemaPipeline: MySQL requires databaseName in apply() args. Caller (e.g. dev-server.ts, dispatcher) must extract the database name from the connection URL and pass it through."
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
const kit = await getMySQLDrizzleKit();
|
|
1163
|
+
return {
|
|
1164
|
+
// MySQL drizzle-kit upstream takes (schema, db, databaseName) —
|
|
1165
|
+
// no tablesFilter slot. Discard the arg here; the post-emission
|
|
1166
|
+
// filterUnsafeStatements is the data-loss safeguard.
|
|
1167
|
+
pushSchema: (schema, db) => kit.pushSchema(schema, db, databaseName)
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
case "sqlite": {
|
|
1171
|
+
const kit = await getSQLiteDrizzleKit();
|
|
1172
|
+
return {
|
|
1173
|
+
// SQLite drizzle-kit upstream takes only (schema, db) — no
|
|
1174
|
+
// tablesFilter. Same caveat as MySQL.
|
|
1175
|
+
pushSchema: (schema, db) => kit.pushSchema(schema, db)
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
default: {
|
|
1179
|
+
const exhaustive = dialect;
|
|
1180
|
+
throw new Error(`Unsupported dialect: ${String(exhaustive)}`);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
makeTransactionRunner(db) {
|
|
1185
|
+
const dbTyped = db;
|
|
1186
|
+
return (fn) => dbTyped.transaction(fn);
|
|
1187
|
+
}
|
|
1188
|
+
classifyErrorCode(err) {
|
|
1189
|
+
if (err instanceof PushSchemaError) return "PUSHSCHEMA_FAILED";
|
|
1190
|
+
if (err instanceof DdlExecutionError) return "DDL_EXECUTION_FAILED";
|
|
1191
|
+
if (err instanceof TTYRequiredError) return "CONFIRMATION_REQUIRED_NO_TTY";
|
|
1192
|
+
if (err instanceof PromptCancelledError) return "CONFIRMATION_DECLINED";
|
|
1193
|
+
return "INTERNAL_ERROR";
|
|
1194
|
+
}
|
|
1195
|
+
};
|
|
1196
|
+
function toRenameResolutions(confirmedRenames, allCandidates) {
|
|
1197
|
+
const confirmedSet = new Set(
|
|
1198
|
+
confirmedRenames.map((c) => `${c.tableName}::${c.fromColumn}::${c.toColumn}`)
|
|
1199
|
+
);
|
|
1200
|
+
return allCandidates.map((c) => ({
|
|
1201
|
+
tableName: c.tableName,
|
|
1202
|
+
fromColumn: c.fromColumn,
|
|
1203
|
+
toColumn: c.toColumn,
|
|
1204
|
+
choice: confirmedSet.has(`${c.tableName}::${c.fromColumn}::${c.toColumn}`) ? "rename" : "drop_and_add"
|
|
1205
|
+
}));
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// src/domains/schema/pipeline/pushschema-pipeline-stubs.ts
|
|
1209
|
+
var noopPreRenameExecutor = {
|
|
1210
|
+
execute: () => Promise.resolve()
|
|
1211
|
+
};
|
|
1212
|
+
var noopMigrationJournal = {
|
|
1213
|
+
recordStart: () => Promise.resolve("noop-journal-id"),
|
|
1214
|
+
recordEnd: () => Promise.resolve()
|
|
1215
|
+
};
|
|
1216
|
+
|
|
1217
|
+
// src/domains/schema/services/drizzle-statement-executor.ts
|
|
1218
|
+
var DrizzleStatementExecutor = class {
|
|
1219
|
+
constructor(dialect, db) {
|
|
1220
|
+
this.dialect = dialect;
|
|
1221
|
+
this.db = db;
|
|
1222
|
+
}
|
|
1223
|
+
// tx is load-bearing for PG and MySQL (we execute via tx.execute so
|
|
1224
|
+
// the statements run inside the pipeline's transaction). For SQLite,
|
|
1225
|
+
// tx is intentionally ignored — better-sqlite3's driver is sync and
|
|
1226
|
+
// automatically associates this.db.run() calls with the active
|
|
1227
|
+
// transaction context (started by drizzle's db.transaction()).
|
|
1228
|
+
async executeStatements(tx, statements) {
|
|
1229
|
+
if (statements.length === 0) return;
|
|
1230
|
+
switch (this.dialect) {
|
|
1231
|
+
case "postgresql":
|
|
1232
|
+
return this.executePg(tx, statements);
|
|
1233
|
+
case "mysql":
|
|
1234
|
+
return this.executeMysql(tx, statements);
|
|
1235
|
+
case "sqlite":
|
|
1236
|
+
return this.executeSqlite(statements);
|
|
1237
|
+
default: {
|
|
1238
|
+
const exhaustive = this.dialect;
|
|
1239
|
+
throw new Error(`Unsupported dialect: ${String(exhaustive)}`);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
async executePg(tx, statements) {
|
|
1244
|
+
const { sql: sqlTag } = await import("drizzle-orm");
|
|
1245
|
+
const txTyped = tx;
|
|
1246
|
+
for (const stmt of statements) {
|
|
1247
|
+
await txTyped.execute(sqlTag.raw(stmt));
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
async executeMysql(tx, statements) {
|
|
1251
|
+
const { sql: sqlTag } = await import("drizzle-orm");
|
|
1252
|
+
const txTyped = tx;
|
|
1253
|
+
for (const stmt of statements) {
|
|
1254
|
+
await txTyped.execute(sqlTag.raw(stmt));
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
async executeSqlite(statements) {
|
|
1258
|
+
const { sql: sqlTag } = await import("drizzle-orm");
|
|
1259
|
+
const dbTyped = this.db;
|
|
1260
|
+
for (const rawStmt of statements) {
|
|
1261
|
+
const pieces = rawStmt.split("\n").map((line) => line.replace(/--> statement-breakpoint/g, "")).join("\n").split(";").map((s) => s.trim()).filter(
|
|
1262
|
+
(s) => s.length > 0 && !s.startsWith("--") && /\b(CREATE|ALTER|DROP|INSERT|UPDATE|DELETE)\b/i.test(s)
|
|
1263
|
+
);
|
|
1264
|
+
for (const raw of pieces) {
|
|
1265
|
+
const stmt = await this.rewriteRecreateInsertForMissingCols(raw);
|
|
1266
|
+
try {
|
|
1267
|
+
dbTyped.run(sqlTag.raw(stmt));
|
|
1268
|
+
} catch (err) {
|
|
1269
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1270
|
+
const causeMsg = err instanceof Error && err.cause instanceof Error ? err.cause.message : "";
|
|
1271
|
+
if (msg.includes("already exists") || msg.includes("duplicate column name") || causeMsg.includes("already exists") || causeMsg.includes("duplicate column name")) {
|
|
1272
|
+
continue;
|
|
1273
|
+
}
|
|
1274
|
+
throw err;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
const violations = dbTyped.all(
|
|
1279
|
+
sqlTag.raw("PRAGMA foreign_key_check")
|
|
1280
|
+
);
|
|
1281
|
+
if (violations.length > 0) {
|
|
1282
|
+
const byTable = /* @__PURE__ */ new Map();
|
|
1283
|
+
for (const v of violations) {
|
|
1284
|
+
byTable.set(v.table, (byTable.get(v.table) ?? 0) + 1);
|
|
1285
|
+
}
|
|
1286
|
+
const summary = [...byTable.entries()].map(([t, n]) => `${t}: ${n} orphan(s)`).join(", ");
|
|
1287
|
+
const sample = JSON.stringify(violations.slice(0, 5));
|
|
1288
|
+
throw new Error(
|
|
1289
|
+
`SQLite foreign_key_check found ${violations.length} FK violation(s) after schema apply (${summary}). First few rows: ${sample}`
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
// Preserved verbatim (with structural typing) from the old
|
|
1294
|
+
// DrizzlePushService — see that class for the original rationale.
|
|
1295
|
+
// Drizzle-kit 0.31.10's SQLite recreate-table strategy emits
|
|
1296
|
+
// INSERT INTO `__new_<t>`(cols) SELECT cols FROM `<t>`
|
|
1297
|
+
// where `cols` includes columns that do not yet exist in `<t>`.
|
|
1298
|
+
// That fails with "no such column". This rewrites the SELECT list
|
|
1299
|
+
// so missing columns are substituted with NULL.
|
|
1300
|
+
async rewriteRecreateInsertForMissingCols(stmt) {
|
|
1301
|
+
const m = stmt.match(
|
|
1302
|
+
/^INSERT\s+INTO\s+`(__new_[^`]+)`\s*\(([^)]+)\)\s+SELECT\s+([^]+?)\s+FROM\s+`([^`]+)`\s*$/i
|
|
1303
|
+
);
|
|
1304
|
+
if (!m) return stmt;
|
|
1305
|
+
const [, , insertColsRaw, , sourceTable] = m;
|
|
1306
|
+
if (/["\0]/.test(sourceTable)) return stmt;
|
|
1307
|
+
const insertCols = insertColsRaw.split(",").map((c) => c.trim().replace(/^`|`$/g, "").replace(/^"|"$/g, ""));
|
|
1308
|
+
let sourceColNames;
|
|
1309
|
+
try {
|
|
1310
|
+
const { sql: sqlTag } = await import("drizzle-orm");
|
|
1311
|
+
const dbTyped = this.db;
|
|
1312
|
+
const rows = dbTyped.all(
|
|
1313
|
+
sqlTag.raw(`PRAGMA table_info("${sourceTable}")`)
|
|
1314
|
+
);
|
|
1315
|
+
sourceColNames = rows.map((r) => r.name);
|
|
1316
|
+
} catch {
|
|
1317
|
+
return stmt;
|
|
1318
|
+
}
|
|
1319
|
+
const selectList = insertCols.map((col) => sourceColNames.includes(col) ? `"${col}"` : "NULL").join(", ");
|
|
1320
|
+
const colList = insertCols.map((col) => `"${col}"`).join(", ");
|
|
1321
|
+
return `INSERT INTO \`__new_${sourceTable}\`(${colList}) SELECT ${selectList} FROM \`${sourceTable}\``;
|
|
1322
|
+
}
|
|
1323
|
+
};
|
|
1324
|
+
|
|
1325
|
+
// src/runtime/notifications/production.ts
|
|
1326
|
+
import { join } from "path";
|
|
1327
|
+
|
|
1328
|
+
// src/runtime/notifications/channels/ndjson.ts
|
|
1329
|
+
import { mkdir, appendFile } from "fs/promises";
|
|
1330
|
+
import { dirname } from "path";
|
|
1331
|
+
var NDJSONChannel = class {
|
|
1332
|
+
name = "ndjson";
|
|
1333
|
+
filePath;
|
|
1334
|
+
logger;
|
|
1335
|
+
dirEnsured = false;
|
|
1336
|
+
disabled = false;
|
|
1337
|
+
constructor(opts) {
|
|
1338
|
+
this.filePath = opts.filePath;
|
|
1339
|
+
this.logger = opts.logger ?? {};
|
|
1340
|
+
}
|
|
1341
|
+
async write(event) {
|
|
1342
|
+
if (this.disabled) return;
|
|
1343
|
+
try {
|
|
1344
|
+
if (!this.dirEnsured) {
|
|
1345
|
+
await mkdir(dirname(this.filePath), { recursive: true });
|
|
1346
|
+
this.dirEnsured = true;
|
|
1347
|
+
}
|
|
1348
|
+
const line = `${JSON.stringify(event)}
|
|
1349
|
+
`;
|
|
1350
|
+
await appendFile(this.filePath, line, { encoding: "utf8" });
|
|
1351
|
+
} catch (err) {
|
|
1352
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1353
|
+
this.logger.warn?.(
|
|
1354
|
+
`[notifications] ndjson channel disabled (${msg}). Restart the dev server after fixing the underlying cause.`
|
|
1355
|
+
);
|
|
1356
|
+
this.disabled = true;
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
};
|
|
1360
|
+
|
|
1361
|
+
// src/runtime/notifications/channels/terminal.ts
|
|
1362
|
+
var TerminalChannel = class {
|
|
1363
|
+
name = "terminal";
|
|
1364
|
+
writer;
|
|
1365
|
+
constructor(opts = {}) {
|
|
1366
|
+
this.writer = opts.writer ?? ((chunk) => process.stdout.write(chunk));
|
|
1367
|
+
}
|
|
1368
|
+
write(event) {
|
|
1369
|
+
if (event.status === "failed" && event.error.code === "CONFIRMATION_REQUIRED_NO_TTY") {
|
|
1370
|
+
return Promise.resolve();
|
|
1371
|
+
}
|
|
1372
|
+
const title = event.status === "success" ? `Schema applied \u2014 ${describeScope(event.scope)}` : `Schema apply FAILED \u2014 ${describeScope(event.scope)}`;
|
|
1373
|
+
const detail = event.status === "success" ? describeSummary(event.summary) : event.error.message;
|
|
1374
|
+
const meta = `${event.source === "ui" ? "ui" : "hmr"} \xB7 ${event.durationMs}ms`;
|
|
1375
|
+
const box = boxify([title, detail, meta]);
|
|
1376
|
+
this.writer(box + "\n");
|
|
1377
|
+
return Promise.resolve();
|
|
1378
|
+
}
|
|
1379
|
+
};
|
|
1380
|
+
function describeScope(scope) {
|
|
1381
|
+
if (scope.kind === "fresh-push") return "fresh-push";
|
|
1382
|
+
if (scope.kind === "global") {
|
|
1383
|
+
return scope.slug ? `global:${scope.slug}` : "global";
|
|
1384
|
+
}
|
|
1385
|
+
return `${scope.kind}:${scope.slug}`;
|
|
1386
|
+
}
|
|
1387
|
+
function describeSummary(s) {
|
|
1388
|
+
const parts = [];
|
|
1389
|
+
if (s.added) parts.push(`${s.added} added`);
|
|
1390
|
+
if (s.removed) parts.push(`${s.removed} removed`);
|
|
1391
|
+
if (s.renamed) parts.push(`${s.renamed} renamed`);
|
|
1392
|
+
if (s.changed) parts.push(`${s.changed} changed`);
|
|
1393
|
+
return parts.length > 0 ? parts.join(", ") : "no changes";
|
|
1394
|
+
}
|
|
1395
|
+
function boxify(lines) {
|
|
1396
|
+
const minWidth = 20;
|
|
1397
|
+
const width = Math.max(...lines.map((l) => l.length), minWidth);
|
|
1398
|
+
const border = `+${"-".repeat(width + 2)}+`;
|
|
1399
|
+
const padded = lines.map((l) => `| ${l.padEnd(width)} |`);
|
|
1400
|
+
return [border, ...padded, border].join("\n");
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
// src/runtime/notifications/dispatcher.ts
|
|
1404
|
+
function describeError(err) {
|
|
1405
|
+
if (err instanceof Error) return err.message;
|
|
1406
|
+
if (typeof err === "string") return err;
|
|
1407
|
+
if (err === void 0) return "undefined";
|
|
1408
|
+
if (err === null) return "null";
|
|
1409
|
+
if (typeof err === "number" || typeof err === "boolean") {
|
|
1410
|
+
return String(err);
|
|
1411
|
+
}
|
|
1412
|
+
try {
|
|
1413
|
+
return JSON.stringify(err);
|
|
1414
|
+
} catch {
|
|
1415
|
+
return "[unstringifiable error]";
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
function createNotifier(opts) {
|
|
1419
|
+
const { channels, logger } = opts;
|
|
1420
|
+
return {
|
|
1421
|
+
async notify(event) {
|
|
1422
|
+
for (const channel of channels) {
|
|
1423
|
+
try {
|
|
1424
|
+
await channel.write(event);
|
|
1425
|
+
} catch (err) {
|
|
1426
|
+
logger?.warn?.(
|
|
1427
|
+
`[notifications] ${channel.name} channel failed: ${describeError(err)}`
|
|
1428
|
+
);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
// src/runtime/notifications/production.ts
|
|
1436
|
+
var cached = null;
|
|
1437
|
+
function getProductionNotifier() {
|
|
1438
|
+
if (cached) return cached;
|
|
1439
|
+
cached = createNotifier({
|
|
1440
|
+
channels: [
|
|
1441
|
+
new TerminalChannel(),
|
|
1442
|
+
new NDJSONChannel({
|
|
1443
|
+
filePath: join(process.cwd(), ".nextly", "logs", "migrations.log")
|
|
1444
|
+
})
|
|
1445
|
+
],
|
|
1446
|
+
logger: {
|
|
1447
|
+
warn: (msg) => console.warn(msg)
|
|
1448
|
+
}
|
|
1449
|
+
});
|
|
1450
|
+
return cached;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
export {
|
|
1454
|
+
createApplyDesiredSchema,
|
|
1455
|
+
TTYRequiredError,
|
|
1456
|
+
PromptCancelledError,
|
|
1457
|
+
RealPreCleanupExecutor,
|
|
1458
|
+
generateSQL,
|
|
1459
|
+
PushSchemaPipeline,
|
|
1460
|
+
noopPreRenameExecutor,
|
|
1461
|
+
noopMigrationJournal,
|
|
1462
|
+
DrizzleStatementExecutor,
|
|
1463
|
+
getProductionNotifier
|
|
1464
|
+
};
|