appwrite-utils-cli 1.7.9 → 1.8.2

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.
Files changed (70) hide show
  1. package/CHANGELOG.md +14 -199
  2. package/README.md +87 -30
  3. package/dist/adapters/AdapterFactory.js +5 -25
  4. package/dist/adapters/DatabaseAdapter.d.ts +17 -2
  5. package/dist/adapters/LegacyAdapter.d.ts +2 -1
  6. package/dist/adapters/LegacyAdapter.js +212 -16
  7. package/dist/adapters/TablesDBAdapter.d.ts +2 -12
  8. package/dist/adapters/TablesDBAdapter.js +261 -57
  9. package/dist/cli/commands/databaseCommands.js +4 -3
  10. package/dist/cli/commands/functionCommands.js +17 -8
  11. package/dist/collections/attributes.js +447 -125
  12. package/dist/collections/methods.js +197 -186
  13. package/dist/collections/tableOperations.d.ts +86 -0
  14. package/dist/collections/tableOperations.js +434 -0
  15. package/dist/collections/transferOperations.d.ts +3 -2
  16. package/dist/collections/transferOperations.js +93 -12
  17. package/dist/config/yamlConfig.d.ts +221 -88
  18. package/dist/examples/yamlTerminologyExample.d.ts +1 -1
  19. package/dist/examples/yamlTerminologyExample.js +6 -3
  20. package/dist/functions/fnConfigDiscovery.d.ts +3 -0
  21. package/dist/functions/fnConfigDiscovery.js +108 -0
  22. package/dist/interactiveCLI.js +18 -15
  23. package/dist/main.js +211 -73
  24. package/dist/migrations/appwriteToX.d.ts +88 -23
  25. package/dist/migrations/comprehensiveTransfer.d.ts +2 -0
  26. package/dist/migrations/comprehensiveTransfer.js +83 -6
  27. package/dist/migrations/dataLoader.d.ts +227 -69
  28. package/dist/migrations/dataLoader.js +3 -3
  29. package/dist/migrations/importController.js +3 -3
  30. package/dist/migrations/relationships.d.ts +8 -2
  31. package/dist/migrations/services/ImportOrchestrator.js +3 -3
  32. package/dist/migrations/transfer.js +159 -37
  33. package/dist/shared/attributeMapper.d.ts +20 -0
  34. package/dist/shared/attributeMapper.js +203 -0
  35. package/dist/shared/selectionDialogs.js +8 -4
  36. package/dist/storage/schemas.d.ts +354 -92
  37. package/dist/utils/configDiscovery.js +4 -3
  38. package/dist/utils/versionDetection.d.ts +0 -4
  39. package/dist/utils/versionDetection.js +41 -173
  40. package/dist/utils/yamlConverter.js +89 -16
  41. package/dist/utils/yamlLoader.d.ts +1 -1
  42. package/dist/utils/yamlLoader.js +6 -2
  43. package/dist/utilsController.js +56 -19
  44. package/package.json +4 -4
  45. package/src/adapters/AdapterFactory.ts +119 -143
  46. package/src/adapters/DatabaseAdapter.ts +18 -3
  47. package/src/adapters/LegacyAdapter.ts +236 -105
  48. package/src/adapters/TablesDBAdapter.ts +773 -643
  49. package/src/cli/commands/databaseCommands.ts +13 -12
  50. package/src/cli/commands/functionCommands.ts +23 -14
  51. package/src/collections/attributes.ts +2054 -1611
  52. package/src/collections/methods.ts +208 -293
  53. package/src/collections/tableOperations.ts +506 -0
  54. package/src/collections/transferOperations.ts +218 -144
  55. package/src/examples/yamlTerminologyExample.ts +10 -5
  56. package/src/functions/fnConfigDiscovery.ts +103 -0
  57. package/src/interactiveCLI.ts +25 -20
  58. package/src/main.ts +549 -194
  59. package/src/migrations/comprehensiveTransfer.ts +126 -50
  60. package/src/migrations/dataLoader.ts +3 -3
  61. package/src/migrations/importController.ts +3 -3
  62. package/src/migrations/services/ImportOrchestrator.ts +3 -3
  63. package/src/migrations/transfer.ts +148 -131
  64. package/src/shared/attributeMapper.ts +229 -0
  65. package/src/shared/selectionDialogs.ts +29 -25
  66. package/src/utils/configDiscovery.ts +9 -3
  67. package/src/utils/versionDetection.ts +74 -228
  68. package/src/utils/yamlConverter.ts +94 -17
  69. package/src/utils/yamlLoader.ts +11 -4
  70. package/src/utilsController.ts +80 -30
@@ -2,14 +2,16 @@ import { converterFunctions, tryAwaitWithRetry } from "appwrite-utils";
2
2
  import { Client, Databases, IndexType, Query, Storage, Users, } from "node-appwrite";
3
3
  import { InputFile } from "node-appwrite/file";
4
4
  import { getAppwriteClient } from "../utils/helperFunctions.js";
5
- import { createOrUpdateAttribute, createUpdateCollectionAttributes, createUpdateCollectionAttributesWithStatusCheck, } from "../collections/attributes.js";
5
+ // Legacy attribute helpers retained only for local-to-local flows if needed
6
6
  import { parseAttribute } from "appwrite-utils";
7
7
  import chalk from "chalk";
8
8
  import { fetchAllCollections } from "../collections/methods.js";
9
9
  import { MessageFormatter } from "../shared/messageFormatter.js";
10
+ import { LegacyAdapter } from "../adapters/LegacyAdapter.js";
10
11
  import { ProgressManager } from "../shared/progressManager.js";
11
- import { createOrUpdateIndex, createOrUpdateIndexes, createOrUpdateIndexesWithStatusCheck, } from "../collections/indexes.js";
12
- import { getClient } from "../utils/getClientFromConfig.js";
12
+ import { getClient, getAdapter } from "../utils/getClientFromConfig.js";
13
+ import { diffTableColumns } from "../collections/tableOperations.js";
14
+ import { mapToCreateAttributeParams } from "../shared/attributeMapper.js";
13
15
  export const transferStorageLocalToLocal = async (storage, fromBucketId, toBucketId) => {
14
16
  MessageFormatter.info(`Transferring files from ${fromBucketId} to ${toBucketId}`, { prefix: "Transfer" });
15
17
  let lastFileId;
@@ -165,26 +167,83 @@ export const transferDatabaseLocalToLocal = async (localDb, fromDbId, targetDbId
165
167
  MessageFormatter.progress(`Creating collection ${collection.name} in target database...`, { prefix: "Transfer" });
166
168
  targetCollection = await tryAwaitWithRetry(async () => localDb.createCollection(targetDbId, collection.$id, collection.name, collection.$permissions, collection.documentSecurity, collection.enabled));
167
169
  }
168
- // Handle attributes with enhanced status checking
169
- MessageFormatter.info(`Creating attributes for collection ${collection.name} with enhanced monitoring...`, { prefix: "Transfer" });
170
- const allAttributes = collection.attributes.map((attr) => parseAttribute(attr));
171
- const attributeSuccess = await createUpdateCollectionAttributesWithStatusCheck(localDb, targetDbId, targetCollection, allAttributes);
172
- if (!attributeSuccess) {
173
- MessageFormatter.error(`Failed to create all attributes for collection ${collection.name}, skipping to next collection`, undefined, { prefix: "Transfer" });
174
- continue;
170
+ // Create attributes via local adapter (wrap the existing client)
171
+ const localAdapter = new LegacyAdapter(localDb.client);
172
+ MessageFormatter.info(`Creating attributes for ${collection.name} via adapter...`, { prefix: 'Transfer' });
173
+ const uniformAttrs = collection.attributes.map((attr) => parseAttribute(attr));
174
+ const nonRel = uniformAttrs.filter((a) => a.type !== 'relationship');
175
+ for (const attr of nonRel) {
176
+ const params = mapToCreateAttributeParams(attr, { databaseId: targetDbId, tableId: targetCollection.$id });
177
+ await localAdapter.createAttribute(params);
178
+ await new Promise((r) => setTimeout(r, 150));
175
179
  }
176
- MessageFormatter.success(`All attributes created successfully for collection ${collection.name}`, { prefix: "Transfer" });
177
- // Handle indexes
178
- const existingIndexes = await tryAwaitWithRetry(async () => await localDb.listIndexes(targetDbId, targetCollection.$id));
179
- for (const index of collection.indexes) {
180
- const existingIndex = existingIndexes.indexes.find((idx) => idx.key === index.key);
181
- if (!existingIndex) {
182
- await tryAwaitWithRetry(async () => createOrUpdateIndex(targetDbId, localDb, targetCollection.$id, index));
183
- MessageFormatter.success(`Index ${index.key} created`, { prefix: "Transfer" });
180
+ // Wait for attributes to become available
181
+ for (const attr of nonRel) {
182
+ const maxWait = 60000;
183
+ const start = Date.now();
184
+ let lastStatus = '';
185
+ while (Date.now() - start < maxWait) {
186
+ try {
187
+ const tableRes = await localAdapter.getTable({ databaseId: targetDbId, tableId: targetCollection.$id });
188
+ const attrs = tableRes.attributes || tableRes.columns || [];
189
+ const found = attrs.find((a) => a.key === attr.key);
190
+ if (found) {
191
+ if (found.status === 'available')
192
+ break;
193
+ if (found.status === 'failed' || found.status === 'stuck') {
194
+ throw new Error(found.error || `Attribute ${attr.key} failed`);
195
+ }
196
+ lastStatus = found.status;
197
+ }
198
+ await new Promise((r) => setTimeout(r, 2000));
199
+ }
200
+ catch {
201
+ await new Promise((r) => setTimeout(r, 2000));
202
+ }
184
203
  }
185
- else {
186
- MessageFormatter.info(`Index ${index.key} exists, checking for updates...`, { prefix: "Transfer" });
187
- await tryAwaitWithRetry(async () => createOrUpdateIndex(targetDbId, localDb, targetCollection.$id, index));
204
+ if (Date.now() - start >= maxWait) {
205
+ MessageFormatter.warning(`Attribute ${attr.key} did not become available within 60s (last: ${lastStatus})`, { prefix: 'Transfer' });
206
+ }
207
+ }
208
+ // Relationship attributes
209
+ const rels = uniformAttrs.filter((a) => a.type === 'relationship');
210
+ for (const attr of rels) {
211
+ const params = mapToCreateAttributeParams(attr, { databaseId: targetDbId, tableId: targetCollection.$id });
212
+ await localAdapter.createAttribute(params);
213
+ await new Promise((r) => setTimeout(r, 150));
214
+ }
215
+ // Handle indexes via adapter (create or update)
216
+ for (const idx of collection.indexes) {
217
+ try {
218
+ await localAdapter.createIndex({
219
+ databaseId: targetDbId,
220
+ tableId: targetCollection.$id,
221
+ key: idx.key,
222
+ type: idx.type,
223
+ attributes: idx.attributes,
224
+ orders: idx.orders || []
225
+ });
226
+ await new Promise((r) => setTimeout(r, 150));
227
+ MessageFormatter.success(`Index ${idx.key} created`, { prefix: 'Transfer' });
228
+ }
229
+ catch (e) {
230
+ // Try update path by deleting and recreating if necessary
231
+ try {
232
+ await localAdapter.deleteIndex({ databaseId: targetDbId, tableId: targetCollection.$id, key: idx.key });
233
+ await localAdapter.createIndex({
234
+ databaseId: targetDbId,
235
+ tableId: targetCollection.$id,
236
+ key: idx.key,
237
+ type: idx.type,
238
+ attributes: idx.attributes,
239
+ orders: idx.orders || []
240
+ });
241
+ await new Promise((r) => setTimeout(r, 150));
242
+ MessageFormatter.info(`Index ${idx.key} recreated`, { prefix: 'Transfer' });
243
+ }
244
+ catch (e2) {
245
+ MessageFormatter.error(`Failed to ensure index ${idx.key}`, e2 instanceof Error ? e2 : new Error(String(e2)), { prefix: 'Transfer' });
246
+ }
188
247
  }
189
248
  }
190
249
  // Transfer documents
@@ -225,26 +284,89 @@ export const transferDatabaseLocalToRemote = async (localDb, endpoint, projectId
225
284
  MessageFormatter.progress(`Creating collection ${collection.name} in remote database...`, { prefix: "Transfer" });
226
285
  targetCollection = await tryAwaitWithRetry(async () => remoteDb.createCollection(toDbId, collection.$id, collection.name, collection.$permissions, collection.documentSecurity, collection.enabled));
227
286
  }
228
- // Handle attributes with enhanced status checking
229
- MessageFormatter.info(`Creating attributes for collection ${collection.name} with enhanced monitoring...`, { prefix: "Transfer" });
230
- const attributesToCreate = collection.attributes.map((attr) => parseAttribute(attr));
231
- const attributesSuccess = await createUpdateCollectionAttributesWithStatusCheck(remoteDb, toDbId, targetCollection, attributesToCreate);
232
- if (!attributesSuccess) {
233
- MessageFormatter.warning(`Failed to create some attributes for collection ${collection.name}`, { prefix: "Transfer" });
234
- // Continue with the transfer even if some attributes failed
287
+ // Create/Update attributes via adapter (prefer adapter for remote)
288
+ const { adapter: remoteAdapter } = await getAdapter(endpoint, projectId, apiKey, 'auto');
289
+ MessageFormatter.info(`Creating attributes for ${collection.name} via adapter...`, { prefix: 'Transfer' });
290
+ const uniformAttrs = collection.attributes.map((attr) => parseAttribute(attr));
291
+ const nonRel = uniformAttrs.filter((a) => a.type !== 'relationship');
292
+ if (nonRel.length > 0) {
293
+ const tableInfo = await remoteAdapter.getTable({ databaseId: toDbId, tableId: collection.$id });
294
+ const existingCols = tableInfo.columns || tableInfo.attributes || [];
295
+ const { toCreate, toUpdate } = diffTableColumns(existingCols, nonRel);
296
+ for (const a of toUpdate) {
297
+ const p = mapToCreateAttributeParams(a, { databaseId: toDbId, tableId: collection.$id });
298
+ await remoteAdapter.updateAttribute(p);
299
+ await new Promise((r) => setTimeout(r, 150));
300
+ }
301
+ for (const a of toCreate) {
302
+ const p = mapToCreateAttributeParams(a, { databaseId: toDbId, tableId: collection.$id });
303
+ await remoteAdapter.createAttribute(p);
304
+ await new Promise((r) => setTimeout(r, 150));
305
+ }
235
306
  }
236
- else {
237
- MessageFormatter.success(`All attributes created successfully for collection ${collection.name}`, { prefix: "Transfer" });
307
+ // Wait for non-relationship attributes to become available
308
+ for (const attr of nonRel) {
309
+ const maxWait = 60000;
310
+ const start = Date.now();
311
+ let lastStatus = '';
312
+ while (Date.now() - start < maxWait) {
313
+ try {
314
+ const tableRes = await remoteAdapter.getTable({ databaseId: toDbId, tableId: collection.$id });
315
+ const attrs = tableRes.attributes || tableRes.columns || [];
316
+ const found = attrs.find((a) => a.key === attr.key);
317
+ if (found) {
318
+ if (found.status === 'available')
319
+ break;
320
+ if (found.status === 'failed' || found.status === 'stuck') {
321
+ throw new Error(found.error || `Attribute ${attr.key} failed`);
322
+ }
323
+ lastStatus = found.status;
324
+ }
325
+ await new Promise((r) => setTimeout(r, 2000));
326
+ }
327
+ catch {
328
+ await new Promise((r) => setTimeout(r, 2000));
329
+ }
330
+ }
331
+ if (Date.now() - start >= maxWait) {
332
+ MessageFormatter.warning(`Attribute ${attr.key} did not become available within 60s (last: ${lastStatus})`, { prefix: 'Transfer' });
333
+ }
334
+ }
335
+ // Relationship attributes
336
+ const rels = uniformAttrs.filter((a) => a.type === 'relationship');
337
+ if (rels.length > 0) {
338
+ const tableInfo2 = await remoteAdapter.getTable({ databaseId: toDbId, tableId: collection.$id });
339
+ const existingCols2 = tableInfo2.columns || tableInfo2.attributes || [];
340
+ const { toCreate: rCreate, toUpdate: rUpdate } = diffTableColumns(existingCols2, rels);
341
+ for (const a of rUpdate) {
342
+ const p = mapToCreateAttributeParams(a, { databaseId: toDbId, tableId: collection.$id });
343
+ await remoteAdapter.updateAttribute(p);
344
+ await new Promise((r) => setTimeout(r, 150));
345
+ }
346
+ for (const a of rCreate) {
347
+ const p = mapToCreateAttributeParams(a, { databaseId: toDbId, tableId: collection.$id });
348
+ await remoteAdapter.createAttribute(p);
349
+ await new Promise((r) => setTimeout(r, 150));
350
+ }
238
351
  }
239
352
  // Handle indexes with enhanced status checking
240
353
  MessageFormatter.info(`Creating indexes for collection ${collection.name} with enhanced monitoring...`, { prefix: "Transfer" });
241
- const indexesSuccess = await createOrUpdateIndexesWithStatusCheck(toDbId, remoteDb, targetCollection.$id, targetCollection, collection.indexes);
242
- if (!indexesSuccess) {
243
- MessageFormatter.warning(`Failed to create some indexes for collection ${collection.name}`, { prefix: "Transfer" });
244
- // Continue with the transfer even if some indexes failed
245
- }
246
- else {
247
- MessageFormatter.success(`All indexes created successfully for collection ${collection.name}`, { prefix: "Transfer" });
354
+ // Create indexes via adapter
355
+ for (const idx of collection.indexes || []) {
356
+ try {
357
+ await remoteAdapter.createIndex({
358
+ databaseId: toDbId,
359
+ tableId: collection.$id,
360
+ key: idx.key,
361
+ type: idx.type,
362
+ attributes: idx.attributes,
363
+ orders: idx.orders || []
364
+ });
365
+ await new Promise((r) => setTimeout(r, 150));
366
+ }
367
+ catch (e) {
368
+ MessageFormatter.error(`Failed to create index ${idx.key}`, e instanceof Error ? e : new Error(String(e)), { prefix: 'Transfer' });
369
+ }
248
370
  }
249
371
  // Transfer documents
250
372
  const { transferDocumentsBetweenDbsLocalToRemote } = await import("../collections/methods.js");
@@ -0,0 +1,20 @@
1
+ import type { CreateAttributeParams, UpdateAttributeParams } from "../adapters/DatabaseAdapter.js";
2
+ import type { Attribute } from "appwrite-utils";
3
+ /**
4
+ * Map a schema Attribute into DatabaseAdapter CreateAttributeParams
5
+ * Only includes fields valid for the specific type to satisfy TS unions.
6
+ * Also normalizes min/max ordering for numeric types to avoid server errors.
7
+ */
8
+ export declare function mapToCreateAttributeParams(attr: Attribute, base: {
9
+ databaseId: string;
10
+ tableId: string;
11
+ }): CreateAttributeParams;
12
+ /**
13
+ * Map a schema Attribute into DatabaseAdapter UpdateAttributeParams
14
+ * Omits fields that are not explicitly provided, and guards enum updates
15
+ * so we never send an empty elements array (preserve existing on server).
16
+ */
17
+ export declare function mapToUpdateAttributeParams(attr: Attribute, base: {
18
+ databaseId: string;
19
+ tableId: string;
20
+ }): UpdateAttributeParams;
@@ -0,0 +1,203 @@
1
+ function ensureNumber(n) {
2
+ if (n === null || n === undefined)
3
+ return undefined;
4
+ const num = Number(n);
5
+ return Number.isFinite(num) ? num : undefined;
6
+ }
7
+ /**
8
+ * Map a schema Attribute into DatabaseAdapter CreateAttributeParams
9
+ * Only includes fields valid for the specific type to satisfy TS unions.
10
+ * Also normalizes min/max ordering for numeric types to avoid server errors.
11
+ */
12
+ export function mapToCreateAttributeParams(attr, base) {
13
+ const type = String(attr.type || "").toLowerCase();
14
+ const required = !!attr.required;
15
+ const array = !!attr.array;
16
+ const xdefault = attr.xdefault;
17
+ const encrypt = attr.encrypted ?? attr.encrypt;
18
+ // Numeric helpers
19
+ const rawMin = ensureNumber(attr.min);
20
+ const rawMax = ensureNumber(attr.max);
21
+ let min = rawMin;
22
+ let max = rawMax;
23
+ if (min !== undefined && max !== undefined && min >= max) {
24
+ // Swap to satisfy server-side validation
25
+ const tmp = min;
26
+ min = Math.min(min, max);
27
+ max = Math.max(tmp, max);
28
+ if (min === max) {
29
+ // If still equal, unset max to avoid error
30
+ max = undefined;
31
+ }
32
+ }
33
+ switch (type) {
34
+ case "string":
35
+ return {
36
+ databaseId: base.databaseId,
37
+ tableId: base.tableId,
38
+ key: attr.key,
39
+ type,
40
+ size: attr.size ?? 255,
41
+ required,
42
+ default: xdefault,
43
+ array,
44
+ encrypt: !!encrypt,
45
+ };
46
+ case "integer":
47
+ return {
48
+ databaseId: base.databaseId,
49
+ tableId: base.tableId,
50
+ key: attr.key,
51
+ type,
52
+ required,
53
+ default: xdefault,
54
+ array,
55
+ min,
56
+ max,
57
+ };
58
+ case "double":
59
+ case "float":
60
+ return {
61
+ databaseId: base.databaseId,
62
+ tableId: base.tableId,
63
+ key: attr.key,
64
+ type,
65
+ required,
66
+ default: xdefault,
67
+ array,
68
+ min,
69
+ max,
70
+ };
71
+ case "boolean":
72
+ return {
73
+ databaseId: base.databaseId,
74
+ tableId: base.tableId,
75
+ key: attr.key,
76
+ type,
77
+ required,
78
+ default: xdefault,
79
+ array,
80
+ };
81
+ case "datetime":
82
+ case "email":
83
+ case "ip":
84
+ case "url":
85
+ return {
86
+ databaseId: base.databaseId,
87
+ tableId: base.tableId,
88
+ key: attr.key,
89
+ type,
90
+ required,
91
+ default: xdefault,
92
+ array,
93
+ };
94
+ case "enum":
95
+ {
96
+ const elements = attr.elements;
97
+ if (!Array.isArray(elements) || elements.length === 0) {
98
+ // Creating an enum without elements is invalid – fail fast with clear messaging
99
+ throw new Error(`Enum attribute '${attr.key}' requires a non-empty 'elements' array for creation`);
100
+ }
101
+ }
102
+ return {
103
+ databaseId: base.databaseId,
104
+ tableId: base.tableId,
105
+ key: attr.key,
106
+ type,
107
+ required,
108
+ default: xdefault,
109
+ array,
110
+ elements: attr.elements,
111
+ };
112
+ case "relationship": {
113
+ // Relationship attributes require related collection and metadata
114
+ return {
115
+ databaseId: base.databaseId,
116
+ tableId: base.tableId,
117
+ key: attr.key,
118
+ type,
119
+ relatedCollection: attr.relatedCollection,
120
+ relationType: attr.relationType,
121
+ twoWay: attr.twoWay,
122
+ twoWayKey: attr.twoWayKey,
123
+ onDelete: attr.onDelete,
124
+ side: attr.side,
125
+ };
126
+ }
127
+ default:
128
+ return {
129
+ databaseId: base.databaseId,
130
+ tableId: base.tableId,
131
+ key: attr.key,
132
+ type,
133
+ required,
134
+ };
135
+ }
136
+ }
137
+ /**
138
+ * Map a schema Attribute into DatabaseAdapter UpdateAttributeParams
139
+ * Omits fields that are not explicitly provided, and guards enum updates
140
+ * so we never send an empty elements array (preserve existing on server).
141
+ */
142
+ export function mapToUpdateAttributeParams(attr, base) {
143
+ const type = String(attr.type || "").toLowerCase();
144
+ const params = {
145
+ databaseId: base.databaseId,
146
+ tableId: base.tableId,
147
+ key: attr.key,
148
+ };
149
+ const setIfDefined = (key, value) => {
150
+ if (value !== undefined)
151
+ params[key] = value;
152
+ };
153
+ // Common fields
154
+ setIfDefined("type", type);
155
+ setIfDefined("required", attr.required);
156
+ // Only send default if explicitly provided and not on required
157
+ if (!attr.required && attr.xdefault !== undefined) {
158
+ setIfDefined("default", attr.xdefault);
159
+ }
160
+ setIfDefined("array", attr.array);
161
+ // encrypt only applies to string types
162
+ if (type === "string")
163
+ setIfDefined("encrypt", attr.encrypted ?? attr.encrypt);
164
+ // Numeric normalization
165
+ const toNum = (n) => (n === null || n === undefined ? undefined : (Number(n)));
166
+ let min = toNum(attr.min);
167
+ let max = toNum(attr.max);
168
+ if (min !== undefined && max !== undefined && min >= max) {
169
+ const tmp = min;
170
+ min = Math.min(min, max);
171
+ max = Math.max(tmp, max);
172
+ if (min === max)
173
+ max = undefined;
174
+ }
175
+ switch (type) {
176
+ case "string":
177
+ setIfDefined("size", attr.size);
178
+ break;
179
+ case "integer":
180
+ case "float":
181
+ case "double":
182
+ setIfDefined("min", min);
183
+ setIfDefined("max", max);
184
+ break;
185
+ case "enum": {
186
+ const elements = attr.elements;
187
+ if (Array.isArray(elements) && elements.length > 0) {
188
+ // Only include when non-empty; otherwise preserve existing on server
189
+ setIfDefined("elements", elements);
190
+ }
191
+ break;
192
+ }
193
+ case "relationship": {
194
+ setIfDefined("relatedCollection", attr.relatedCollection);
195
+ setIfDefined("relationType", attr.relationType);
196
+ setIfDefined("twoWay", attr.twoWay);
197
+ setIfDefined("twoWayKey", attr.twoWayKey);
198
+ setIfDefined("onDelete", attr.onDelete);
199
+ break;
200
+ }
201
+ }
202
+ return params;
203
+ }
@@ -145,7 +145,8 @@ export class SelectionDialogs {
145
145
  name,
146
146
  value: database.$id,
147
147
  short: database.name,
148
- checked: defaultSelected.includes(database.$id) || (!allowNewOnly && isConfigured)
148
+ // Do not preselect anything unless explicitly provided
149
+ checked: defaultSelected.includes(database.$id)
149
150
  });
150
151
  });
151
152
  if (choices.length === 0) {
@@ -205,7 +206,8 @@ export class SelectionDialogs {
205
206
  name,
206
207
  value: table.$id,
207
208
  short: table.name,
208
- checked: defaultSelected.includes(table.$id) || (!allowNewOnly && isConfigured)
209
+ // Do not preselect anything unless explicitly provided
210
+ checked: defaultSelected.includes(table.$id)
209
211
  });
210
212
  });
211
213
  if (choices.length === 0) {
@@ -288,7 +290,8 @@ export class SelectionDialogs {
288
290
  name: ` ${name}`,
289
291
  value: bucket.$id,
290
292
  short: bucket.name,
291
- checked: defaultSelected.includes(bucket.$id) || (!allowNewOnly && isConfigured)
293
+ // Do not preselect anything unless explicitly provided
294
+ checked: defaultSelected.includes(bucket.$id)
292
295
  });
293
296
  });
294
297
  }
@@ -327,7 +330,8 @@ export class SelectionDialogs {
327
330
  name,
328
331
  value: bucket.$id,
329
332
  short: bucket.name,
330
- checked: defaultSelected.includes(bucket.$id) || (!allowNewOnly && isConfigured)
333
+ // Do not preselect anything unless explicitly provided
334
+ checked: defaultSelected.includes(bucket.$id)
331
335
  });
332
336
  });
333
337
  }