@lobb-js/core 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lobb-js/core",
3
3
  "license": "UNLICENSED",
4
- "version": "0.19.0",
4
+ "version": "0.20.0",
5
5
  "type": "module",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -58,7 +58,7 @@ export class ConfigManager {
58
58
  if (!this.config.collections[fieldValue.references.collection]) {
59
59
  throw new LobbError({
60
60
  code: "INTERNAL_SERVER_ERROR",
61
- message: `Field (${collectionName}.${fieldName}) references collection (${fieldValue.references.collection}) which does not exist`,
61
+ message: `"${collectionName}.${fieldName}" references "${fieldValue.references.collection}" which is not defined in your collections.`,
62
62
  });
63
63
  }
64
64
  if (!this.config.relations) {
@@ -36,21 +36,6 @@ export class DatabaseSyncManager {
36
36
  ) {
37
37
  if (!dbSchemaDiff.length) return;
38
38
 
39
- if (!forceSync) {
40
- console.error(
41
- "These are the differences between the config schema and the database schema",
42
- );
43
- console.error(
44
- "You should add migrations to match the collection schema with the database schema",
45
- );
46
- console.error(dbSchemaDiff);
47
- throw new LobbError({
48
- code: "INTERNAL_SERVER_ERROR",
49
- message:
50
- "The schema of the configuration collection does not align with the actual schema of the connected database.",
51
- });
52
- }
53
-
54
39
  // just-diff may produce deep paths (length > 3) for sub-field changes within
55
40
  // an existing index (e.g. renaming a column the index covers). Convert those
56
41
  // into explicit drop+recreate (replace) ops, deduplicating by index name.
@@ -84,6 +69,8 @@ export class DatabaseSyncManager {
84
69
  };
85
70
  normalizedDiff.sort((a, b) => opOrder(a) - opOrder(b));
86
71
 
72
+ await this.assertNoDataLoss(normalizedDiff, forceSync);
73
+
87
74
  for (let index = 0; index < normalizedDiff.length; index++) {
88
75
  const change = normalizedDiff[index];
89
76
  if (change.path.length === 1 && change.op === "add") {
@@ -152,6 +139,59 @@ export class DatabaseSyncManager {
152
139
  }
153
140
  }
154
141
 
142
+ private async assertNoDataLoss(
143
+ diff: Array<{ op: string; path: (string | number)[]; value: unknown }>,
144
+ forceSync: boolean,
145
+ ) {
146
+ if (forceSync) return;
147
+ const destructiveOps = diff.filter(
148
+ (change) =>
149
+ (change.path.length === 1 && change.op === "remove") ||
150
+ (change.path[1] === "fields" && change.op === "remove"),
151
+ );
152
+
153
+ if (destructiveOps.length === 0) return;
154
+
155
+ const blocked: string[] = [];
156
+
157
+ for (const change of destructiveOps) {
158
+ const collectionName = change.path[0] as string;
159
+
160
+ if (change.path.length === 1 && change.op === "remove") {
161
+ const { meta } = await this.dbDriver.findAll(collectionName, { limit: 1, offset: 0, fields: "id" });
162
+ if (meta.totalCount > 0) {
163
+ blocked.push(
164
+ `Table "${collectionName}" has ${meta.totalCount} row(s) and cannot be dropped automatically. Write a migration to handle the data first.`,
165
+ );
166
+ }
167
+ } else if (change.path[1] === "fields" && change.op === "remove") {
168
+ const fieldName = change.path[2] as string;
169
+ const { meta } = await this.dbDriver.findAll(collectionName, {
170
+ limit: 1,
171
+ offset: 0,
172
+ fields: "id",
173
+ filter: { [fieldName]: { $ne: null } },
174
+ });
175
+ if (meta.totalCount > 0) {
176
+ blocked.push(
177
+ `Column "${collectionName}.${fieldName}" has ${meta.totalCount} non-null row(s) and cannot be dropped automatically. Write a migration to handle the data first.`,
178
+ );
179
+ }
180
+ }
181
+ }
182
+
183
+ if (blocked.length > 0) {
184
+ console.error("Blocked destructive schema changes detected:");
185
+ for (const msg of blocked) console.error(` • ${msg}`);
186
+ throw new LobbError({
187
+ code: "INTERNAL_SERVER_ERROR",
188
+ message:
189
+ "Refusing to apply destructive schema changes on tables/columns that contain data. " +
190
+ "Write explicit migrations to handle the data, then re-run.",
191
+ });
192
+ }
193
+ }
194
+
155
195
  private async getDbDifferences(specificColleciton?: string) {
156
196
  let dbSchema = await this.dbDriver.getDbSchema();
157
197
  let configDbSchema = Lobb.instance.configManager.getDbSchema();