appwrite-utils-cli 1.4.1 → 1.5.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/README.md +22 -1
- package/dist/adapters/TablesDBAdapter.js +7 -4
- package/dist/collections/attributes.d.ts +1 -1
- package/dist/collections/attributes.js +42 -7
- package/dist/collections/indexes.js +13 -3
- package/dist/collections/methods.d.ts +9 -0
- package/dist/collections/methods.js +268 -0
- package/dist/databases/setup.js +6 -2
- package/dist/interactiveCLI.js +2 -1
- package/dist/migrations/appwriteToX.d.ts +2 -2
- package/dist/migrations/comprehensiveTransfer.js +12 -0
- package/dist/migrations/dataLoader.d.ts +5 -5
- package/dist/migrations/relationships.d.ts +2 -2
- package/dist/shared/jsonSchemaGenerator.d.ts +1 -0
- package/dist/shared/jsonSchemaGenerator.js +6 -2
- package/dist/shared/operationQueue.js +14 -1
- package/dist/shared/schemaGenerator.d.ts +2 -1
- package/dist/shared/schemaGenerator.js +61 -78
- package/dist/storage/schemas.d.ts +8 -8
- package/dist/utils/loadConfigs.js +44 -19
- package/dist/utils/schemaStrings.d.ts +2 -1
- package/dist/utils/schemaStrings.js +61 -78
- package/dist/utils/setupFiles.js +19 -1
- package/dist/utils/versionDetection.d.ts +6 -0
- package/dist/utils/versionDetection.js +30 -0
- package/dist/utilsController.js +32 -5
- package/package.json +1 -1
- package/src/adapters/TablesDBAdapter.ts +20 -17
- package/src/collections/attributes.ts +198 -156
- package/src/collections/indexes.ts +36 -28
- package/src/collections/methods.ts +292 -19
- package/src/databases/setup.ts +11 -7
- package/src/interactiveCLI.ts +8 -7
- package/src/migrations/comprehensiveTransfer.ts +22 -8
- package/src/shared/jsonSchemaGenerator.ts +36 -29
- package/src/shared/operationQueue.ts +48 -33
- package/src/shared/schemaGenerator.ts +128 -134
- package/src/utils/loadConfigs.ts +48 -29
- package/src/utils/schemaStrings.ts +124 -130
- package/src/utils/setupFiles.ts +21 -5
- package/src/utils/versionDetection.ts +48 -21
- package/src/utilsController.ts +59 -32
@@ -22,16 +22,23 @@ interface RelationshipDetail {
|
|
22
22
|
isChild: boolean;
|
23
23
|
}
|
24
24
|
|
25
|
-
export class SchemaGenerator {
|
26
|
-
private relationshipMap = new Map<string, RelationshipDetail[]>();
|
27
|
-
private config: AppwriteConfig;
|
28
|
-
private appwriteFolderPath: string;
|
29
|
-
|
30
|
-
constructor(config: AppwriteConfig, appwriteFolderPath: string) {
|
31
|
-
this.config = config;
|
32
|
-
this.appwriteFolderPath = appwriteFolderPath;
|
33
|
-
this.extractRelationships();
|
34
|
-
}
|
25
|
+
export class SchemaGenerator {
|
26
|
+
private relationshipMap = new Map<string, RelationshipDetail[]>();
|
27
|
+
private config: AppwriteConfig;
|
28
|
+
private appwriteFolderPath: string;
|
29
|
+
|
30
|
+
constructor(config: AppwriteConfig, appwriteFolderPath: string) {
|
31
|
+
this.config = config;
|
32
|
+
this.appwriteFolderPath = appwriteFolderPath;
|
33
|
+
this.extractRelationships();
|
34
|
+
}
|
35
|
+
|
36
|
+
private resolveCollectionName = (idOrName: string): string => {
|
37
|
+
const col = this.config.collections?.find(
|
38
|
+
(c) => c.$id === (idOrName as any) || c.name === idOrName
|
39
|
+
);
|
40
|
+
return col?.name ?? idOrName;
|
41
|
+
};
|
35
42
|
|
36
43
|
public updateTsSchemas(): void {
|
37
44
|
const collections = this.config.collections;
|
@@ -182,12 +189,12 @@ export class SchemaGenerator {
|
|
182
189
|
if (!collection.attributes) {
|
183
190
|
return;
|
184
191
|
}
|
185
|
-
collection.attributes.forEach((attr) => {
|
186
|
-
if (attr.type === "relationship" && attr.twoWay && attr.twoWayKey) {
|
187
|
-
const relationshipAttr = attr as RelationshipAttribute;
|
188
|
-
let isArrayParent = false;
|
189
|
-
let isArrayChild = false;
|
190
|
-
switch (relationshipAttr.relationType) {
|
192
|
+
collection.attributes.forEach((attr) => {
|
193
|
+
if (attr.type === "relationship" && attr.twoWay && attr.twoWayKey) {
|
194
|
+
const relationshipAttr = attr as RelationshipAttribute;
|
195
|
+
let isArrayParent = false;
|
196
|
+
let isArrayChild = false;
|
197
|
+
switch (relationshipAttr.relationType) {
|
191
198
|
case "oneToMany":
|
192
199
|
isArrayParent = true;
|
193
200
|
isArrayChild = false;
|
@@ -207,14 +214,14 @@ export class SchemaGenerator {
|
|
207
214
|
default:
|
208
215
|
break;
|
209
216
|
}
|
210
|
-
this.addRelationship(
|
211
|
-
collection.name,
|
212
|
-
relationshipAttr.relatedCollection,
|
213
|
-
attr.key,
|
214
|
-
relationshipAttr.twoWayKey
|
215
|
-
isArrayParent,
|
216
|
-
isArrayChild
|
217
|
-
);
|
217
|
+
this.addRelationship(
|
218
|
+
collection.name,
|
219
|
+
this.resolveCollectionName(relationshipAttr.relatedCollection),
|
220
|
+
attr.key,
|
221
|
+
relationshipAttr.twoWayKey!,
|
222
|
+
isArrayParent,
|
223
|
+
isArrayChild
|
224
|
+
);
|
218
225
|
console.log(
|
219
226
|
`Extracted relationship: ${attr.key}\n\t${collection.name} -> ${relationshipAttr.relatedCollection}, databaseId: ${collection.databaseId}`
|
220
227
|
);
|
@@ -259,10 +266,10 @@ export class SchemaGenerator {
|
|
259
266
|
return;
|
260
267
|
}
|
261
268
|
this.config.collections.forEach((collection) => {
|
262
|
-
const schemaString = this.
|
263
|
-
collection.name,
|
264
|
-
collection.attributes
|
265
|
-
);
|
269
|
+
const schemaString = this.createSchemaStringV4(
|
270
|
+
collection.name,
|
271
|
+
collection.attributes
|
272
|
+
);
|
266
273
|
const camelCaseName = toCamelCase(collection.name);
|
267
274
|
const schemaPath = path.join(
|
268
275
|
this.appwriteFolderPath,
|
@@ -274,117 +281,104 @@ export class SchemaGenerator {
|
|
274
281
|
});
|
275
282
|
}
|
276
283
|
|
277
|
-
|
278
|
-
|
279
|
-
|
284
|
+
// Zod v4 recursive getter-based schemas
|
285
|
+
createSchemaStringV4 = (name: string, attributes: Attribute[]): string => {
|
286
|
+
const pascalName = toPascalCase(name);
|
287
|
+
let imports = `import { z } from "zod";\n`;
|
280
288
|
|
281
289
|
// Use the relationshipMap to find related collections
|
282
|
-
const relationshipDetails = this.relationshipMap.get(name) || [];
|
283
|
-
|
284
|
-
.filter((detail, index, self) => {
|
285
|
-
const uniqueKey = `${detail.parentCollection}-${detail.childCollection}-${detail.parentKey}-${detail.childKey}`;
|
286
|
-
return (
|
287
|
-
index ===
|
288
|
-
self.findIndex(
|
290
|
+
const relationshipDetails = this.relationshipMap.get(name) || [];
|
291
|
+
let relatedCollections = relationshipDetails
|
292
|
+
.filter((detail, index, self) => {
|
293
|
+
const uniqueKey = `${detail.parentCollection}-${detail.childCollection}-${detail.parentKey}-${detail.childKey}`;
|
294
|
+
return (
|
295
|
+
index ===
|
296
|
+
self.findIndex(
|
289
297
|
(obj) =>
|
290
298
|
`${obj.parentCollection}-${obj.childCollection}-${obj.parentKey}-${obj.childKey}` ===
|
291
299
|
uniqueKey
|
292
300
|
)
|
293
301
|
);
|
294
302
|
})
|
295
|
-
.map((detail) => {
|
296
|
-
const relatedCollectionName = detail.isChild
|
297
|
-
? detail.parentCollection
|
298
|
-
: detail.childCollection;
|
299
|
-
const key = detail.isChild ? detail.childKey : detail.parentKey;
|
300
|
-
const isArray = detail.isArray ? "array" : "";
|
301
|
-
return [relatedCollectionName, key, isArray];
|
302
|
-
});
|
303
|
-
|
304
|
-
//
|
305
|
-
const
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
schemaString += `});\n\n`;
|
323
|
-
schemaString += `export type ${pascalName} = z.infer<typeof ${pascalName}Schema>;\n\n`;
|
324
|
-
} else {
|
325
|
-
// Complex case: has relationships, generate BaseSchema + extended schema pattern
|
326
|
-
let relatedTypes = "";
|
327
|
-
let relatedTypesLazy = "";
|
328
|
-
let curNum = 0;
|
329
|
-
let maxNum = relatedCollections.length;
|
330
|
-
|
331
|
-
relatedCollections.forEach((relatedCollection) => {
|
332
|
-
console.log(relatedCollection);
|
333
|
-
let relatedPascalName = toPascalCase(relatedCollection[0]);
|
334
|
-
let relatedCamelName = toCamelCase(relatedCollection[0]);
|
335
|
-
curNum++;
|
336
|
-
let endNameTypes = relatedPascalName;
|
337
|
-
let endNameLazy = `${relatedPascalName}Schema`;
|
338
|
-
if (relatedCollection[2] === "array") {
|
339
|
-
endNameTypes += "[]";
|
340
|
-
endNameLazy += ".array().default([])";
|
341
|
-
} else if (!(relatedCollection[2] === "array")) {
|
342
|
-
endNameTypes += " | null";
|
343
|
-
endNameLazy += ".nullish()";
|
344
|
-
}
|
345
|
-
imports += `import { ${relatedPascalName}Schema, type ${relatedPascalName} } from "./${relatedCamelName}";\n`;
|
346
|
-
relatedTypes += `${relatedCollection[1]}?: ${endNameTypes};\n`;
|
347
|
-
if (relatedTypes.length > 0 && curNum !== maxNum) {
|
348
|
-
relatedTypes += " ";
|
349
|
-
}
|
350
|
-
relatedTypesLazy += `${relatedCollection[1]}: z.lazy(() => ${endNameLazy}),\n`;
|
351
|
-
if (relatedTypesLazy.length > 0 && curNum !== maxNum) {
|
352
|
-
relatedTypesLazy += " ";
|
353
|
-
}
|
354
|
-
});
|
355
|
-
|
356
|
-
// Re-add imports after processing relationships
|
357
|
-
schemaString = `${imports}\n\n`;
|
358
|
-
|
359
|
-
schemaString += `export const ${pascalName}SchemaBase = z.object({\n`;
|
360
|
-
schemaString += ` $id: z.string(),\n`;
|
361
|
-
schemaString += ` $createdAt: z.string(),\n`;
|
362
|
-
schemaString += ` $updatedAt: z.string(),\n`;
|
363
|
-
schemaString += ` $permissions: z.array(z.string()),\n`;
|
364
|
-
for (const attribute of attributes) {
|
365
|
-
if (attribute.type === "relationship") {
|
366
|
-
continue;
|
367
|
-
}
|
368
|
-
schemaString += ` ${attribute.key}: ${this.typeToZod(attribute)},\n`;
|
369
|
-
}
|
370
|
-
schemaString += `});\n\n`;
|
371
|
-
schemaString += `export type ${pascalName}Base = z.infer<typeof ${pascalName}SchemaBase>`;
|
372
|
-
if (relatedTypes.length > 0) {
|
373
|
-
schemaString += ` & {\n ${relatedTypes}};\n\n`;
|
374
|
-
} else {
|
375
|
-
schemaString += `;\n\n`;
|
376
|
-
}
|
377
|
-
schemaString += `export const ${pascalName}Schema: z.ZodType<${pascalName}Base> = ${pascalName}SchemaBase`;
|
378
|
-
if (relatedTypes.length > 0) {
|
379
|
-
schemaString += `.extend({\n ${relatedTypesLazy}});\n\n`;
|
380
|
-
} else {
|
381
|
-
schemaString += `;\n`;
|
382
|
-
}
|
383
|
-
schemaString += `export type ${pascalName} = z.infer<typeof ${pascalName}Schema>;\n\n`;
|
384
|
-
}
|
303
|
+
.map((detail) => {
|
304
|
+
const relatedCollectionName = detail.isChild
|
305
|
+
? detail.parentCollection
|
306
|
+
: detail.childCollection;
|
307
|
+
const key = detail.isChild ? detail.childKey : detail.parentKey;
|
308
|
+
const isArray = detail.isArray ? "array" : "";
|
309
|
+
return [relatedCollectionName, key, isArray];
|
310
|
+
});
|
311
|
+
|
312
|
+
// Include one-way relationship attributes directly (no twoWayKey)
|
313
|
+
const oneWayRels: Array<[string, string, string]> = [];
|
314
|
+
for (const attr of attributes) {
|
315
|
+
if (attr.type === "relationship" && attr.relatedCollection) {
|
316
|
+
const relatedName = this.resolveCollectionName(attr.relatedCollection);
|
317
|
+
const isArray =
|
318
|
+
attr.relationType === "oneToMany" || attr.relationType === "manyToMany"
|
319
|
+
? "array"
|
320
|
+
: "";
|
321
|
+
oneWayRels.push([relatedName, attr.key, isArray]);
|
322
|
+
}
|
323
|
+
}
|
324
|
+
|
325
|
+
// Merge and dedupe (by relatedName+key)
|
326
|
+
relatedCollections = [...relatedCollections, ...oneWayRels].filter(
|
327
|
+
(item, idx, self) =>
|
328
|
+
idx === self.findIndex((o) => `${o[0]}::${o[1]}` === `${item[0]}::${item[1]}`)
|
329
|
+
);
|
385
330
|
|
386
|
-
|
387
|
-
|
331
|
+
const hasRelationships = relatedCollections.length > 0;
|
332
|
+
|
333
|
+
// Build imports for related collections
|
334
|
+
if (hasRelationships) {
|
335
|
+
const importLines = relatedCollections.map((rel) => {
|
336
|
+
const relatedPascalName = toPascalCase(rel[0]);
|
337
|
+
const relatedCamelName = toCamelCase(rel[0]);
|
338
|
+
return `import { ${relatedPascalName}Schema } from "./${relatedCamelName}";`;
|
339
|
+
});
|
340
|
+
const unique = Array.from(new Set(importLines));
|
341
|
+
imports += unique.join("\n") + (unique.length ? "\n" : "");
|
342
|
+
}
|
343
|
+
|
344
|
+
let schemaString = `${imports}\n`;
|
345
|
+
|
346
|
+
// Single object schema with recursive getters (Zod v4)
|
347
|
+
schemaString += `export const ${pascalName}Schema = z.object({\n`;
|
348
|
+
schemaString += ` $id: z.string(),\n`;
|
349
|
+
schemaString += ` $createdAt: z.string(),\n`;
|
350
|
+
schemaString += ` $updatedAt: z.string(),\n`;
|
351
|
+
schemaString += ` $permissions: z.array(z.string()),\n`;
|
352
|
+
for (const attribute of attributes) {
|
353
|
+
if (attribute.type === "relationship") continue;
|
354
|
+
schemaString += ` ${attribute.key}: ${this.typeToZod(attribute)},\n`;
|
355
|
+
}
|
356
|
+
|
357
|
+
// Add recursive getters for relationships (respect required flag)
|
358
|
+
relatedCollections.forEach((rel) => {
|
359
|
+
const relatedPascalName = toPascalCase(rel[0]);
|
360
|
+
const isArray = rel[2] === "array";
|
361
|
+
const key = String(rel[1]);
|
362
|
+
const attrMeta = attributes.find(a => a.key === key && a.type === "relationship");
|
363
|
+
const isRequired = !!attrMeta?.required;
|
364
|
+
let getterBody = "";
|
365
|
+
if (isArray) {
|
366
|
+
getterBody = isRequired
|
367
|
+
? `${relatedPascalName}Schema.array()`
|
368
|
+
: `${relatedPascalName}Schema.array().nullish()`;
|
369
|
+
} else {
|
370
|
+
getterBody = isRequired
|
371
|
+
? `${relatedPascalName}Schema`
|
372
|
+
: `${relatedPascalName}Schema.nullish()`;
|
373
|
+
}
|
374
|
+
schemaString += ` get ${key}(){\n return ${getterBody}\n },\n`;
|
375
|
+
});
|
376
|
+
|
377
|
+
schemaString += `});\n\n`;
|
378
|
+
schemaString += `export type ${pascalName} = z.infer<typeof ${pascalName}Schema>;\n\n`;
|
379
|
+
|
380
|
+
return schemaString;
|
381
|
+
};
|
388
382
|
|
389
383
|
typeToZod = (attribute: Attribute) => {
|
390
384
|
let baseSchemaCode = "";
|
package/src/utils/setupFiles.ts
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
import { mkdirSync, writeFileSync, existsSync } from "node:fs";
|
2
2
|
import path from "node:path";
|
3
3
|
import type { AppwriteConfig } from "appwrite-utils";
|
4
|
-
import { findAppwriteConfig } from "./loadConfigs.js";
|
4
|
+
import { findAppwriteConfig } from "./loadConfigs.js";
|
5
|
+
import { loadYamlConfig } from "../config/yamlConfig.js";
|
6
|
+
import { fetchServerVersion, isVersionAtLeast } from "./versionDetection.js";
|
5
7
|
import { findYamlConfig } from "../config/yamlConfig.js";
|
6
8
|
import { ID } from "node-appwrite";
|
7
9
|
import { ulid } from "ulidx";
|
@@ -265,16 +267,30 @@ export const setupDirsFiles = async (
|
|
265
267
|
const appwriteSchemaFolder = path.join(appwriteFolder, "schemas");
|
266
268
|
const appwriteYamlSchemaFolder = path.join(appwriteFolder, ".yaml_schemas");
|
267
269
|
const appwriteDataFolder = path.join(appwriteFolder, "importData");
|
268
|
-
|
270
|
+
// Decide between collections or tables folder
|
271
|
+
let useTables = false;
|
272
|
+
try {
|
273
|
+
// Try reading YAML config if present to detect version
|
274
|
+
const yamlPath = findYamlConfig(basePath);
|
275
|
+
if (yamlPath) {
|
276
|
+
const cfg = await loadYamlConfig(yamlPath);
|
277
|
+
if (cfg) {
|
278
|
+
const ver = await fetchServerVersion(cfg.appwriteEndpoint);
|
279
|
+
if (isVersionAtLeast(ver || undefined, '1.8.0')) useTables = true;
|
280
|
+
}
|
281
|
+
}
|
282
|
+
} catch {}
|
283
|
+
const targetFolderName = useTables ? "tables" : "collections";
|
284
|
+
const collectionsFolder = path.join(appwriteFolder, targetFolderName);
|
269
285
|
|
270
286
|
// Create directory structure
|
271
287
|
if (!existsSync(appwriteFolder)) {
|
272
288
|
mkdirSync(appwriteFolder, { recursive: true });
|
273
289
|
}
|
274
290
|
|
275
|
-
if (!existsSync(collectionsFolder)) {
|
276
|
-
mkdirSync(collectionsFolder, { recursive: true });
|
277
|
-
}
|
291
|
+
if (!existsSync(collectionsFolder)) {
|
292
|
+
mkdirSync(collectionsFolder, { recursive: true });
|
293
|
+
}
|
278
294
|
|
279
295
|
// Handle configuration file creation - YAML is now default
|
280
296
|
if (useYaml) {
|
@@ -12,12 +12,12 @@
|
|
12
12
|
|
13
13
|
export type ApiMode = 'legacy' | 'tablesdb';
|
14
14
|
|
15
|
-
export interface VersionDetectionResult {
|
16
|
-
apiMode: ApiMode;
|
17
|
-
detectionMethod: 'endpoint_probe' | 'health_check' | 'fallback';
|
18
|
-
serverVersion?: string;
|
19
|
-
confidence: 'high' | 'medium' | 'low';
|
20
|
-
}
|
15
|
+
export interface VersionDetectionResult {
|
16
|
+
apiMode: ApiMode;
|
17
|
+
detectionMethod: 'endpoint_probe' | 'health_check' | 'fallback';
|
18
|
+
serverVersion?: string;
|
19
|
+
confidence: 'high' | 'medium' | 'low';
|
20
|
+
}
|
21
21
|
|
22
22
|
/**
|
23
23
|
* Detects Appwrite API version and TablesDB support
|
@@ -55,13 +55,13 @@ export async function detectAppwriteVersion(
|
|
55
55
|
console.warn('SDK capability probe failed:', error instanceof Error ? error.message : 'Unknown error');
|
56
56
|
}
|
57
57
|
|
58
|
-
// Fallback to legacy mode
|
59
|
-
return {
|
60
|
-
apiMode: 'legacy',
|
61
|
-
detectionMethod: 'fallback',
|
62
|
-
confidence: 'low'
|
63
|
-
};
|
64
|
-
}
|
58
|
+
// Fallback to legacy mode
|
59
|
+
return {
|
60
|
+
apiMode: 'legacy',
|
61
|
+
detectionMethod: 'fallback',
|
62
|
+
confidence: 'low'
|
63
|
+
};
|
64
|
+
}
|
65
65
|
|
66
66
|
/**
|
67
67
|
* Test TablesDB endpoint availability - most reliable detection method
|
@@ -82,15 +82,15 @@ async function probeTablesDbEndpoint(
|
|
82
82
|
signal: AbortSignal.timeout(5000)
|
83
83
|
});
|
84
84
|
|
85
|
-
if (response.ok || response.status === 404) {
|
85
|
+
if (response.ok || response.status === 404) {
|
86
86
|
// 200 = TablesDB available, 404 = endpoint exists but no tables
|
87
87
|
// Both indicate TablesDB support
|
88
|
-
return {
|
89
|
-
apiMode: 'tablesdb',
|
90
|
-
detectionMethod: 'endpoint_probe',
|
91
|
-
confidence: 'high'
|
92
|
-
};
|
93
|
-
}
|
88
|
+
return {
|
89
|
+
apiMode: 'tablesdb',
|
90
|
+
detectionMethod: 'endpoint_probe',
|
91
|
+
confidence: 'high'
|
92
|
+
};
|
93
|
+
}
|
94
94
|
|
95
95
|
// 501 Not Implemented or other errors = no TablesDB support
|
96
96
|
throw new Error(`TablesDB endpoint returned ${response.status}: ${response.statusText}`);
|
@@ -262,4 +262,31 @@ export async function detectSdkSupport(): Promise<{
|
|
262
262
|
*/
|
263
263
|
export function clearVersionDetectionCache(): void {
|
264
264
|
detectionCache.clear();
|
265
|
-
}
|
265
|
+
}
|
266
|
+
|
267
|
+
/**
|
268
|
+
* Fetch server version from /health/version (no auth required)
|
269
|
+
*/
|
270
|
+
export async function fetchServerVersion(endpoint: string): Promise<string | null> {
|
271
|
+
try {
|
272
|
+
const clean = endpoint.replace(/\/$/, '');
|
273
|
+
const res = await fetch(`${clean}/health/version`, { method: 'GET', signal: AbortSignal.timeout(5000) });
|
274
|
+
if (!res.ok) return null;
|
275
|
+
const data = await res.json().catch(() => null) as any;
|
276
|
+
const version = (data && (data.version || data.build || data.release)) ?? null;
|
277
|
+
return typeof version === 'string' ? version : null;
|
278
|
+
} catch {
|
279
|
+
return null;
|
280
|
+
}
|
281
|
+
}
|
282
|
+
|
283
|
+
/** Compare semantic versions (basic) */
|
284
|
+
export function isVersionAtLeast(current: string | undefined, target: string): boolean {
|
285
|
+
if (!current) return false;
|
286
|
+
const toNums = (v: string) => v.split('.').map(n => parseInt(n, 10));
|
287
|
+
const [a1=0,a2=0,a3=0] = toNums(current);
|
288
|
+
const [b1,b2,b3] = toNums(target);
|
289
|
+
if (a1 !== b1) return a1 > b1;
|
290
|
+
if (a2 !== b2) return a2 > b2;
|
291
|
+
return a3 >= b3;
|
292
|
+
}
|
package/src/utilsController.ts
CHANGED
@@ -27,13 +27,14 @@ import {
|
|
27
27
|
wipeOtherDatabases,
|
28
28
|
ensureCollectionsExist,
|
29
29
|
} from "./databases/setup.js";
|
30
|
-
import {
|
31
|
-
createOrUpdateCollections,
|
32
|
-
wipeDatabase,
|
33
|
-
generateSchemas,
|
34
|
-
fetchAllCollections,
|
35
|
-
wipeCollection,
|
36
|
-
} from "./collections/methods.js";
|
30
|
+
import {
|
31
|
+
createOrUpdateCollections,
|
32
|
+
wipeDatabase,
|
33
|
+
generateSchemas,
|
34
|
+
fetchAllCollections,
|
35
|
+
wipeCollection,
|
36
|
+
} from "./collections/methods.js";
|
37
|
+
import { wipeAllTables, wipeTableRows } from "./collections/methods.js";
|
37
38
|
import {
|
38
39
|
backupDatabase,
|
39
40
|
ensureDatabaseConfigBucketsExist,
|
@@ -57,7 +58,8 @@ import {
|
|
57
58
|
transferUsersLocalToRemote,
|
58
59
|
type TransferOptions,
|
59
60
|
} from "./migrations/transfer.js";
|
60
|
-
import { getClient } from "./utils/getClientFromConfig.js";
|
61
|
+
import { getClient } from "./utils/getClientFromConfig.js";
|
62
|
+
import { getAdapterFromConfig } from "./utils/getClientFromConfig.js";
|
61
63
|
import { fetchAllDatabases } from "./databases/methods.js";
|
62
64
|
import {
|
63
65
|
listFunctions,
|
@@ -413,14 +415,23 @@ export class UtilsController {
|
|
413
415
|
MessageFormatter.success("All functions synchronized successfully!", { prefix: "Functions" });
|
414
416
|
}
|
415
417
|
|
416
|
-
async wipeDatabase(database: Models.Database, wipeBucket: boolean = false) {
|
417
|
-
await this.init();
|
418
|
-
if (!this.database) throw new Error("Database not initialized");
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
418
|
+
async wipeDatabase(database: Models.Database, wipeBucket: boolean = false) {
|
419
|
+
await this.init();
|
420
|
+
if (!this.database || !this.config) throw new Error("Database not initialized");
|
421
|
+
try {
|
422
|
+
const { adapter, apiMode } = await getAdapterFromConfig(this.config);
|
423
|
+
if (apiMode === 'tablesdb') {
|
424
|
+
await wipeAllTables(adapter, database.$id);
|
425
|
+
} else {
|
426
|
+
await wipeDatabase(this.database, database.$id);
|
427
|
+
}
|
428
|
+
} catch {
|
429
|
+
await wipeDatabase(this.database, database.$id);
|
430
|
+
}
|
431
|
+
if (wipeBucket) {
|
432
|
+
await this.wipeBucketFromDatabase(database);
|
433
|
+
}
|
434
|
+
}
|
424
435
|
|
425
436
|
async wipeBucketFromDatabase(database: Models.Database) {
|
426
437
|
// Check configured bucket in database config
|
@@ -448,14 +459,23 @@ export class UtilsController {
|
|
448
459
|
}
|
449
460
|
}
|
450
461
|
|
451
|
-
async wipeCollection(
|
452
|
-
database: Models.Database,
|
453
|
-
collection: Models.Collection
|
454
|
-
) {
|
455
|
-
await this.init();
|
456
|
-
if (!this.database) throw new Error("Database not initialized");
|
457
|
-
|
458
|
-
|
462
|
+
async wipeCollection(
|
463
|
+
database: Models.Database,
|
464
|
+
collection: Models.Collection
|
465
|
+
) {
|
466
|
+
await this.init();
|
467
|
+
if (!this.database || !this.config) throw new Error("Database not initialized");
|
468
|
+
try {
|
469
|
+
const { adapter, apiMode } = await getAdapterFromConfig(this.config);
|
470
|
+
if (apiMode === 'tablesdb') {
|
471
|
+
await wipeTableRows(adapter, database.$id, collection.$id);
|
472
|
+
} else {
|
473
|
+
await wipeCollection(this.database, database.$id, collection.$id);
|
474
|
+
}
|
475
|
+
} catch {
|
476
|
+
await wipeCollection(this.database, database.$id, collection.$id);
|
477
|
+
}
|
478
|
+
}
|
459
479
|
|
460
480
|
async wipeDocumentStorage(bucketId: string) {
|
461
481
|
await this.init();
|
@@ -581,10 +601,10 @@ export class UtilsController {
|
|
581
601
|
await generator.updateConfig(this.config, isYamlProject);
|
582
602
|
}
|
583
603
|
|
584
|
-
async syncDb(
|
585
|
-
databases: Models.Database[] = [],
|
586
|
-
collections: Models.Collection[] = []
|
587
|
-
) {
|
604
|
+
async syncDb(
|
605
|
+
databases: Models.Database[] = [],
|
606
|
+
collections: Models.Collection[] = []
|
607
|
+
) {
|
588
608
|
await this.init();
|
589
609
|
if (!this.database) {
|
590
610
|
MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
|
@@ -594,10 +614,17 @@ export class UtilsController {
|
|
594
614
|
const allDatabases = await fetchAllDatabases(this.database);
|
595
615
|
databases = allDatabases;
|
596
616
|
}
|
597
|
-
|
598
|
-
await this.
|
599
|
-
await this.
|
600
|
-
|
617
|
+
// Ensure DBs exist (this may internally ensure migrations exists)
|
618
|
+
await this.ensureDatabasesExist(databases);
|
619
|
+
await this.ensureDatabaseConfigBucketsExist(databases);
|
620
|
+
|
621
|
+
// Do not push collections to the migrations database (prevents duplicate runs)
|
622
|
+
const dbsForCollections = databases.filter(
|
623
|
+
(db) => (this.config?.useMigrations ?? true) ? db.name.toLowerCase() !== "migrations" : true
|
624
|
+
);
|
625
|
+
|
626
|
+
await this.createOrUpdateCollectionsForDatabases(dbsForCollections, collections);
|
627
|
+
}
|
601
628
|
|
602
629
|
getAppwriteFolderPath() {
|
603
630
|
return this.appwriteFolderPath;
|