appwrite-utils-cli 1.7.8 → 1.8.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.
Files changed (111) 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 +10 -10
  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/services/ConfigLoaderService.d.ts +7 -0
  18. package/dist/config/services/ConfigLoaderService.js +47 -1
  19. package/dist/config/yamlConfig.d.ts +221 -88
  20. package/dist/examples/yamlTerminologyExample.d.ts +1 -1
  21. package/dist/examples/yamlTerminologyExample.js +6 -3
  22. package/dist/functions/deployments.js +5 -23
  23. package/dist/functions/fnConfigDiscovery.d.ts +3 -0
  24. package/dist/functions/fnConfigDiscovery.js +108 -0
  25. package/dist/functions/methods.js +4 -2
  26. package/dist/functions/pathResolution.d.ts +37 -0
  27. package/dist/functions/pathResolution.js +185 -0
  28. package/dist/functions/templates/count-docs-in-collection/README.md +54 -0
  29. package/dist/functions/templates/count-docs-in-collection/package.json +25 -0
  30. package/dist/functions/templates/count-docs-in-collection/src/main.ts +159 -0
  31. package/dist/functions/templates/count-docs-in-collection/src/request.ts +9 -0
  32. package/dist/functions/templates/count-docs-in-collection/tsconfig.json +28 -0
  33. package/dist/functions/templates/hono-typescript/README.md +286 -0
  34. package/dist/functions/templates/hono-typescript/package.json +26 -0
  35. package/dist/functions/templates/hono-typescript/src/adapters/request.ts +74 -0
  36. package/dist/functions/templates/hono-typescript/src/adapters/response.ts +106 -0
  37. package/dist/functions/templates/hono-typescript/src/app.ts +180 -0
  38. package/dist/functions/templates/hono-typescript/src/context.ts +103 -0
  39. package/dist/functions/templates/hono-typescript/src/index.ts +54 -0
  40. package/dist/functions/templates/hono-typescript/src/middleware/appwrite.ts +119 -0
  41. package/dist/functions/templates/hono-typescript/tsconfig.json +20 -0
  42. package/dist/functions/templates/typescript-node/README.md +32 -0
  43. package/dist/functions/templates/typescript-node/package.json +25 -0
  44. package/dist/functions/templates/typescript-node/src/context.ts +103 -0
  45. package/dist/functions/templates/typescript-node/src/index.ts +29 -0
  46. package/dist/functions/templates/typescript-node/tsconfig.json +28 -0
  47. package/dist/functions/templates/uv/README.md +31 -0
  48. package/dist/functions/templates/uv/pyproject.toml +30 -0
  49. package/dist/functions/templates/uv/src/__init__.py +0 -0
  50. package/dist/functions/templates/uv/src/context.py +125 -0
  51. package/dist/functions/templates/uv/src/index.py +46 -0
  52. package/dist/interactiveCLI.js +18 -15
  53. package/dist/main.js +219 -81
  54. package/dist/migrations/appwriteToX.d.ts +88 -23
  55. package/dist/migrations/comprehensiveTransfer.d.ts +2 -0
  56. package/dist/migrations/comprehensiveTransfer.js +83 -6
  57. package/dist/migrations/dataLoader.d.ts +227 -69
  58. package/dist/migrations/dataLoader.js +3 -3
  59. package/dist/migrations/importController.js +3 -3
  60. package/dist/migrations/relationships.d.ts +8 -2
  61. package/dist/migrations/services/ImportOrchestrator.js +3 -3
  62. package/dist/migrations/transfer.js +159 -37
  63. package/dist/shared/attributeMapper.d.ts +20 -0
  64. package/dist/shared/attributeMapper.js +203 -0
  65. package/dist/shared/selectionDialogs.d.ts +1 -1
  66. package/dist/shared/selectionDialogs.js +39 -11
  67. package/dist/storage/schemas.d.ts +354 -92
  68. package/dist/utils/configDiscovery.js +4 -3
  69. package/dist/utils/versionDetection.d.ts +0 -4
  70. package/dist/utils/versionDetection.js +41 -173
  71. package/dist/utils/yamlConverter.js +89 -16
  72. package/dist/utils/yamlLoader.d.ts +1 -1
  73. package/dist/utils/yamlLoader.js +6 -2
  74. package/dist/utilsController.d.ts +2 -1
  75. package/dist/utilsController.js +151 -22
  76. package/package.json +7 -5
  77. package/scripts/copy-templates.ts +23 -0
  78. package/src/adapters/AdapterFactory.ts +119 -143
  79. package/src/adapters/DatabaseAdapter.ts +18 -3
  80. package/src/adapters/LegacyAdapter.ts +236 -105
  81. package/src/adapters/TablesDBAdapter.ts +773 -643
  82. package/src/cli/commands/databaseCommands.ts +19 -19
  83. package/src/cli/commands/functionCommands.ts +23 -14
  84. package/src/collections/attributes.ts +2054 -1611
  85. package/src/collections/methods.ts +208 -293
  86. package/src/collections/tableOperations.ts +506 -0
  87. package/src/collections/transferOperations.ts +218 -144
  88. package/src/config/services/ConfigLoaderService.ts +62 -1
  89. package/src/examples/yamlTerminologyExample.ts +10 -5
  90. package/src/functions/deployments.ts +10 -35
  91. package/src/functions/fnConfigDiscovery.ts +103 -0
  92. package/src/functions/methods.ts +4 -2
  93. package/src/functions/pathResolution.ts +227 -0
  94. package/src/interactiveCLI.ts +25 -20
  95. package/src/main.ts +557 -202
  96. package/src/migrations/comprehensiveTransfer.ts +126 -50
  97. package/src/migrations/dataLoader.ts +3 -3
  98. package/src/migrations/importController.ts +3 -3
  99. package/src/migrations/services/ImportOrchestrator.ts +3 -3
  100. package/src/migrations/transfer.ts +148 -131
  101. package/src/shared/attributeMapper.ts +229 -0
  102. package/src/shared/selectionDialogs.ts +65 -32
  103. package/src/utils/configDiscovery.ts +9 -3
  104. package/src/utils/versionDetection.ts +74 -228
  105. package/src/utils/yamlConverter.ts +94 -17
  106. package/src/utils/yamlLoader.ts +11 -4
  107. package/src/utilsController.ts +202 -36
  108. package/dist/utils/schemaStrings.d.ts +0 -14
  109. package/dist/utils/schemaStrings.js +0 -428
  110. package/dist/utils/sessionPreservationExample.d.ts +0 -1666
  111. package/dist/utils/sessionPreservationExample.js +0 -101
@@ -0,0 +1,229 @@
1
+ import type { CreateAttributeParams, UpdateAttributeParams } from "../adapters/DatabaseAdapter.js";
2
+ import type { Attribute } from "appwrite-utils";
3
+
4
+ function ensureNumber(n: any): number | undefined {
5
+ if (n === null || n === undefined) return undefined;
6
+ const num = Number(n);
7
+ return Number.isFinite(num) ? num : undefined;
8
+ }
9
+
10
+ /**
11
+ * Map a schema Attribute into DatabaseAdapter CreateAttributeParams
12
+ * Only includes fields valid for the specific type to satisfy TS unions.
13
+ * Also normalizes min/max ordering for numeric types to avoid server errors.
14
+ */
15
+ export function mapToCreateAttributeParams(
16
+ attr: Attribute,
17
+ base: { databaseId: string; tableId: string }
18
+ ): CreateAttributeParams {
19
+ const type = String((attr as any).type || "").toLowerCase();
20
+ const required = !!(attr as any).required;
21
+ const array = !!(attr as any).array;
22
+ const xdefault = (attr as any).xdefault;
23
+ const encrypt = (attr as any).encrypted ?? (attr as any).encrypt;
24
+
25
+ // Numeric helpers
26
+ const rawMin = ensureNumber((attr as any).min);
27
+ const rawMax = ensureNumber((attr as any).max);
28
+ let min = rawMin;
29
+ let max = rawMax;
30
+ if (min !== undefined && max !== undefined && min >= max) {
31
+ // Swap to satisfy server-side validation
32
+ const tmp = min;
33
+ min = Math.min(min, max);
34
+ max = Math.max(tmp, max);
35
+ if (min === max) {
36
+ // If still equal, unset max to avoid error
37
+ max = undefined;
38
+ }
39
+ }
40
+
41
+ switch (type) {
42
+ case "string":
43
+ return {
44
+ databaseId: base.databaseId,
45
+ tableId: base.tableId,
46
+ key: attr.key,
47
+ type,
48
+ size: (attr as any).size ?? 255,
49
+ required,
50
+ default: xdefault,
51
+ array,
52
+ encrypt: !!encrypt,
53
+ };
54
+
55
+ case "integer":
56
+ return {
57
+ databaseId: base.databaseId,
58
+ tableId: base.tableId,
59
+ key: attr.key,
60
+ type,
61
+ required,
62
+ default: xdefault,
63
+ array,
64
+ min,
65
+ max,
66
+ };
67
+
68
+ case "double":
69
+ case "float":
70
+ return {
71
+ databaseId: base.databaseId,
72
+ tableId: base.tableId,
73
+ key: attr.key,
74
+ type,
75
+ required,
76
+ default: xdefault,
77
+ array,
78
+ min,
79
+ max,
80
+ };
81
+
82
+ case "boolean":
83
+ return {
84
+ databaseId: base.databaseId,
85
+ tableId: base.tableId,
86
+ key: attr.key,
87
+ type,
88
+ required,
89
+ default: xdefault,
90
+ array,
91
+ };
92
+
93
+ case "datetime":
94
+ case "email":
95
+ case "ip":
96
+ case "url":
97
+ return {
98
+ databaseId: base.databaseId,
99
+ tableId: base.tableId,
100
+ key: attr.key,
101
+ type,
102
+ required,
103
+ default: xdefault,
104
+ array,
105
+ };
106
+
107
+ case "enum":
108
+ {
109
+ const elements = (attr as any).elements;
110
+ if (!Array.isArray(elements) || elements.length === 0) {
111
+ // Creating an enum without elements is invalid – fail fast with clear messaging
112
+ throw new Error(
113
+ `Enum attribute '${(attr as any).key}' requires a non-empty 'elements' array for creation`
114
+ );
115
+ }
116
+ }
117
+ return {
118
+ databaseId: base.databaseId,
119
+ tableId: base.tableId,
120
+ key: attr.key,
121
+ type,
122
+ required,
123
+ default: xdefault,
124
+ array,
125
+ elements: (attr as any).elements,
126
+ };
127
+
128
+ case "relationship": {
129
+ // Relationship attributes require related collection and metadata
130
+ return {
131
+ databaseId: base.databaseId,
132
+ tableId: base.tableId,
133
+ key: attr.key,
134
+ type,
135
+ relatedCollection: (attr as any).relatedCollection,
136
+ relationType: (attr as any).relationType,
137
+ twoWay: (attr as any).twoWay,
138
+ twoWayKey: (attr as any).twoWayKey,
139
+ onDelete: (attr as any).onDelete,
140
+ side: (attr as any).side,
141
+ } as unknown as CreateAttributeParams;
142
+ }
143
+
144
+ default:
145
+ return {
146
+ databaseId: base.databaseId,
147
+ tableId: base.tableId,
148
+ key: attr.key,
149
+ type,
150
+ required,
151
+ } as CreateAttributeParams;
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Map a schema Attribute into DatabaseAdapter UpdateAttributeParams
157
+ * Omits fields that are not explicitly provided, and guards enum updates
158
+ * so we never send an empty elements array (preserve existing on server).
159
+ */
160
+ export function mapToUpdateAttributeParams(
161
+ attr: Attribute,
162
+ base: { databaseId: string; tableId: string }
163
+ ): UpdateAttributeParams {
164
+ const type = String((attr as any).type || "").toLowerCase();
165
+ const params: UpdateAttributeParams = {
166
+ databaseId: base.databaseId,
167
+ tableId: base.tableId,
168
+ key: (attr as any).key,
169
+ };
170
+
171
+ const setIfDefined = <K extends keyof UpdateAttributeParams>(
172
+ key: K,
173
+ value: UpdateAttributeParams[K]
174
+ ) => {
175
+ if (value !== undefined) (params as any)[key] = value;
176
+ };
177
+
178
+ // Common fields
179
+ setIfDefined("type", type);
180
+ setIfDefined("required", (attr as any).required);
181
+ // Only send default if explicitly provided and not on required
182
+ if (!(attr as any).required && (attr as any).xdefault !== undefined) {
183
+ setIfDefined("default", (attr as any).xdefault as any);
184
+ }
185
+ setIfDefined("array", (attr as any).array);
186
+ // encrypt only applies to string types
187
+ if (type === "string") setIfDefined("encrypt", (attr as any).encrypted ?? (attr as any).encrypt);
188
+
189
+ // Numeric normalization
190
+ const toNum = (n: any) => (n === null || n === undefined ? undefined : (Number(n)));
191
+ let min = toNum((attr as any).min);
192
+ let max = toNum((attr as any).max);
193
+ if (min !== undefined && max !== undefined && min >= max) {
194
+ const tmp = min;
195
+ min = Math.min(min, max);
196
+ max = Math.max(tmp, max);
197
+ if (min === max) max = undefined;
198
+ }
199
+
200
+ switch (type) {
201
+ case "string":
202
+ setIfDefined("size", (attr as any).size);
203
+ break;
204
+ case "integer":
205
+ case "float":
206
+ case "double":
207
+ setIfDefined("min", min);
208
+ setIfDefined("max", max);
209
+ break;
210
+ case "enum": {
211
+ const elements = (attr as any).elements;
212
+ if (Array.isArray(elements) && elements.length > 0) {
213
+ // Only include when non-empty; otherwise preserve existing on server
214
+ setIfDefined("elements", elements as string[]);
215
+ }
216
+ break;
217
+ }
218
+ case "relationship": {
219
+ setIfDefined("relatedCollection", (attr as any).relatedCollection);
220
+ setIfDefined("relationType", (attr as any).relationType);
221
+ setIfDefined("twoWay", (attr as any).twoWay);
222
+ setIfDefined("twoWayKey", (attr as any).twoWayKey);
223
+ setIfDefined("onDelete", (attr as any).onDelete);
224
+ break;
225
+ }
226
+ }
227
+
228
+ return params;
229
+ }
@@ -244,12 +244,13 @@ export class SelectionDialogs {
244
244
  return; // Skip configured databases if only allowing new ones
245
245
  }
246
246
 
247
- choices.push({
248
- name,
249
- value: database.$id,
250
- short: database.name,
251
- checked: defaultSelected.includes(database.$id) || (!allowNewOnly && isConfigured)
252
- });
247
+ choices.push({
248
+ name,
249
+ value: database.$id,
250
+ short: database.name,
251
+ // Do not preselect anything unless explicitly provided
252
+ checked: defaultSelected.includes(database.$id)
253
+ });
253
254
  });
254
255
 
255
256
  if (choices.length === 0) {
@@ -327,12 +328,13 @@ export class SelectionDialogs {
327
328
  return; // Skip configured tables if only allowing new ones
328
329
  }
329
330
 
330
- choices.push({
331
- name,
332
- value: table.$id,
333
- short: table.name,
334
- checked: defaultSelected.includes(table.$id) || (!allowNewOnly && isConfigured)
335
- });
331
+ choices.push({
332
+ name,
333
+ value: table.$id,
334
+ short: table.name,
335
+ // Do not preselect anything unless explicitly provided
336
+ checked: defaultSelected.includes(table.$id)
337
+ });
336
338
  });
337
339
 
338
340
  if (choices.length === 0) {
@@ -436,12 +438,13 @@ export class SelectionDialogs {
436
438
  return; // Skip configured buckets if only allowing new ones
437
439
  }
438
440
 
439
- choices.push({
440
- name: ` ${name}`,
441
- value: bucket.$id,
442
- short: bucket.name,
443
- checked: defaultSelected.includes(bucket.$id) || (!allowNewOnly && isConfigured)
444
- });
441
+ choices.push({
442
+ name: ` ${name}`,
443
+ value: bucket.$id,
444
+ short: bucket.name,
445
+ // Do not preselect anything unless explicitly provided
446
+ checked: defaultSelected.includes(bucket.$id)
447
+ });
445
448
  });
446
449
  }
447
450
  });
@@ -480,12 +483,13 @@ export class SelectionDialogs {
480
483
  return; // Skip configured buckets if only allowing new ones
481
484
  }
482
485
 
483
- choices.push({
484
- name,
485
- value: bucket.$id,
486
- short: bucket.name,
487
- checked: defaultSelected.includes(bucket.$id) || (!allowNewOnly && isConfigured)
488
- });
486
+ choices.push({
487
+ name,
488
+ value: bucket.$id,
489
+ short: bucket.name,
490
+ // Do not preselect anything unless explicitly provided
491
+ checked: defaultSelected.includes(bucket.$id)
492
+ });
489
493
  });
490
494
  }
491
495
 
@@ -520,8 +524,37 @@ export class SelectionDialogs {
520
524
  /**
521
525
  * Shows final confirmation dialog with sync selection summary
522
526
  */
523
- static async confirmSyncSelection(selectionSummary: SyncSelectionSummary): Promise<boolean> {
524
- MessageFormatter.banner("Sync Selection Summary", "Review your selections before proceeding");
527
+ static async confirmSyncSelection(
528
+ selectionSummary: SyncSelectionSummary,
529
+ operationType: 'push' | 'pull' | 'sync' = 'sync'
530
+ ): Promise<boolean> {
531
+ const labels = {
532
+ push: {
533
+ banner: "Push Selection Summary",
534
+ subtitle: "Review selections before pushing to Appwrite",
535
+ confirm: "Proceed with push operation?",
536
+ success: "Push operation confirmed.",
537
+ cancel: "Push operation cancelled."
538
+ },
539
+ pull: {
540
+ banner: "Pull Selection Summary",
541
+ subtitle: "Review selections before pulling from Appwrite",
542
+ confirm: "Proceed with pull operation?",
543
+ success: "Pull operation confirmed.",
544
+ cancel: "Pull operation cancelled."
545
+ },
546
+ sync: {
547
+ banner: "Sync Selection Summary",
548
+ subtitle: "Review your selections before proceeding",
549
+ confirm: "Proceed with sync operation?",
550
+ success: "Sync operation confirmed.",
551
+ cancel: "Sync operation cancelled."
552
+ }
553
+ };
554
+
555
+ const label = labels[operationType];
556
+
557
+ MessageFormatter.banner(label.banner, label.subtitle);
525
558
 
526
559
  // Database summary
527
560
  console.log(chalk.bold.cyan("\nšŸ“Š Databases:"));
@@ -563,20 +596,20 @@ export class SelectionDialogs {
563
596
  const { confirmed } = await inquirer.prompt([{
564
597
  type: 'confirm',
565
598
  name: 'confirmed',
566
- message: chalk.green.bold('Proceed with sync operation?'),
599
+ message: chalk.green.bold(label.confirm),
567
600
  default: true
568
601
  }]);
569
602
 
570
603
  if (confirmed) {
571
- MessageFormatter.success("Sync operation confirmed.", { skipLogging: true });
572
- logger.info("Sync selection confirmed", {
604
+ MessageFormatter.success(label.success, { skipLogging: true });
605
+ logger.info(`${operationType} selection confirmed`, {
573
606
  databases: selectionSummary.totalDatabases,
574
607
  tables: selectionSummary.totalTables,
575
608
  buckets: selectionSummary.totalBuckets
576
609
  });
577
610
  } else {
578
- MessageFormatter.warning("Sync operation cancelled.", { skipLogging: true });
579
- logger.info("Sync selection cancelled by user");
611
+ MessageFormatter.warning(label.cancel, { skipLogging: true });
612
+ logger.info(`${operationType} selection cancelled by user`);
580
613
  }
581
614
 
582
615
  return confirmed;
@@ -713,4 +746,4 @@ export class SelectionDialogs {
713
746
  MessageFormatter.success(message, { skipLogging: true });
714
747
  logger.info(`Selection dialog success: ${message}`);
715
748
  }
716
- }
749
+ }
@@ -1,6 +1,10 @@
1
1
  import path from "path";
2
2
  import fs from "fs";
3
- import { type CollectionCreate, type Collection } from "appwrite-utils";
3
+ import {
4
+ CollectionCreateSchema,
5
+ type CollectionCreate,
6
+ type Collection
7
+ } from "appwrite-utils";
4
8
  import { register } from "tsx/esm/api";
5
9
  import { pathToFileURL } from "node:url";
6
10
  import yaml from "js-yaml";
@@ -216,7 +220,7 @@ export const loadYamlCollection = (filePath: string): CollectionCreate | null =>
216
220
  const parsedCollection = YamlCollectionSchema.parse(yamlData);
217
221
 
218
222
  // Convert YAML collection to CollectionCreate format
219
- const collection: CollectionCreate = {
223
+ const collectionInput: CollectionCreate = {
220
224
  name: parsedCollection.name,
221
225
  $id: parsedCollection.id || parsedCollection.name.toLowerCase().replace(/\s+/g, '_'),
222
226
  documentSecurity: parsedCollection.documentSecurity,
@@ -241,7 +245,7 @@ export const loadYamlCollection = (filePath: string): CollectionCreate | null =>
241
245
  twoWayKey: attr.twoWayKey,
242
246
  onDelete: attr.onDelete as any,
243
247
  side: attr.side as any,
244
- encrypted: (attr as any).encrypt,
248
+ encrypt: (attr as any).encrypt,
245
249
  format: (attr as any).format
246
250
  })),
247
251
  indexes: parsedCollection.indexes.map(idx => ({
@@ -253,6 +257,8 @@ export const loadYamlCollection = (filePath: string): CollectionCreate | null =>
253
257
  importDefs: parsedCollection.importDefs && Array.isArray(parsedCollection.importDefs) && parsedCollection.importDefs.length > 0 ? parsedCollection.importDefs : []
254
258
  };
255
259
 
260
+ const collection = CollectionCreateSchema.parse(collectionInput);
261
+
256
262
  return collection;
257
263
  } catch (error) {
258
264
  MessageFormatter.error(`Error loading YAML collection from ${filePath}`, error as Error, { prefix: "Config" });