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.
- package/CHANGELOG.md +14 -199
- package/README.md +87 -30
- package/dist/adapters/AdapterFactory.js +5 -25
- package/dist/adapters/DatabaseAdapter.d.ts +17 -2
- package/dist/adapters/LegacyAdapter.d.ts +2 -1
- package/dist/adapters/LegacyAdapter.js +212 -16
- package/dist/adapters/TablesDBAdapter.d.ts +2 -12
- package/dist/adapters/TablesDBAdapter.js +261 -57
- package/dist/cli/commands/databaseCommands.js +4 -3
- package/dist/cli/commands/functionCommands.js +17 -8
- package/dist/collections/attributes.js +447 -125
- package/dist/collections/methods.js +197 -186
- package/dist/collections/tableOperations.d.ts +86 -0
- package/dist/collections/tableOperations.js +434 -0
- package/dist/collections/transferOperations.d.ts +3 -2
- package/dist/collections/transferOperations.js +93 -12
- package/dist/config/yamlConfig.d.ts +221 -88
- package/dist/examples/yamlTerminologyExample.d.ts +1 -1
- package/dist/examples/yamlTerminologyExample.js +6 -3
- package/dist/functions/fnConfigDiscovery.d.ts +3 -0
- package/dist/functions/fnConfigDiscovery.js +108 -0
- package/dist/interactiveCLI.js +18 -15
- package/dist/main.js +211 -73
- package/dist/migrations/appwriteToX.d.ts +88 -23
- package/dist/migrations/comprehensiveTransfer.d.ts +2 -0
- package/dist/migrations/comprehensiveTransfer.js +83 -6
- package/dist/migrations/dataLoader.d.ts +227 -69
- package/dist/migrations/dataLoader.js +3 -3
- package/dist/migrations/importController.js +3 -3
- package/dist/migrations/relationships.d.ts +8 -2
- package/dist/migrations/services/ImportOrchestrator.js +3 -3
- package/dist/migrations/transfer.js +159 -37
- package/dist/shared/attributeMapper.d.ts +20 -0
- package/dist/shared/attributeMapper.js +203 -0
- package/dist/shared/selectionDialogs.js +8 -4
- package/dist/storage/schemas.d.ts +354 -92
- package/dist/utils/configDiscovery.js +4 -3
- package/dist/utils/versionDetection.d.ts +0 -4
- package/dist/utils/versionDetection.js +41 -173
- package/dist/utils/yamlConverter.js +89 -16
- package/dist/utils/yamlLoader.d.ts +1 -1
- package/dist/utils/yamlLoader.js +6 -2
- package/dist/utilsController.js +56 -19
- package/package.json +4 -4
- package/src/adapters/AdapterFactory.ts +119 -143
- package/src/adapters/DatabaseAdapter.ts +18 -3
- package/src/adapters/LegacyAdapter.ts +236 -105
- package/src/adapters/TablesDBAdapter.ts +773 -643
- package/src/cli/commands/databaseCommands.ts +13 -12
- package/src/cli/commands/functionCommands.ts +23 -14
- package/src/collections/attributes.ts +2054 -1611
- package/src/collections/methods.ts +208 -293
- package/src/collections/tableOperations.ts +506 -0
- package/src/collections/transferOperations.ts +218 -144
- package/src/examples/yamlTerminologyExample.ts +10 -5
- package/src/functions/fnConfigDiscovery.ts +103 -0
- package/src/interactiveCLI.ts +25 -20
- package/src/main.ts +549 -194
- package/src/migrations/comprehensiveTransfer.ts +126 -50
- package/src/migrations/dataLoader.ts +3 -3
- package/src/migrations/importController.ts +3 -3
- package/src/migrations/services/ImportOrchestrator.ts +3 -3
- package/src/migrations/transfer.ts +148 -131
- package/src/shared/attributeMapper.ts +229 -0
- package/src/shared/selectionDialogs.ts +29 -25
- package/src/utils/configDiscovery.ts +9 -3
- package/src/utils/versionDetection.ts +74 -228
- package/src/utils/yamlConverter.ts +94 -17
- package/src/utils/yamlLoader.ts +11 -4
- 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
|
-
|
|
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 {
|
|
12
|
-
import {
|
|
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
|
-
//
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
186
|
-
MessageFormatter.
|
|
187
|
-
|
|
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
|
-
//
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
237
|
-
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
333
|
+
// Do not preselect anything unless explicitly provided
|
|
334
|
+
checked: defaultSelected.includes(bucket.$id)
|
|
331
335
|
});
|
|
332
336
|
});
|
|
333
337
|
}
|