appwrite-utils-cli 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -1
- package/dist/adapters/TablesDBAdapter.js +7 -4
- package/dist/collections/attributes.d.ts +1 -1
- package/dist/collections/attributes.js +24 -6
- package/dist/collections/indexes.js +13 -3
- package/dist/collections/methods.d.ts +9 -0
- package/dist/collections/methods.js +268 -0
- 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 +28 -4
- package/package.json +1 -1
- package/src/adapters/TablesDBAdapter.ts +20 -17
- package/src/collections/attributes.ts +122 -99
- package/src/collections/indexes.ts +36 -28
- package/src/collections/methods.ts +292 -19
- 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 +44 -24
@@ -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 updateYamlCollections(): void {
|
37
44
|
const collections = this.config.collections;
|
@@ -291,12 +298,12 @@ export default appwriteConfig;
|
|
291
298
|
if (!collection.attributes) {
|
292
299
|
return;
|
293
300
|
}
|
294
|
-
collection.attributes.forEach((attr) => {
|
295
|
-
if (attr.type === "relationship" && attr.twoWay && attr.twoWayKey) {
|
296
|
-
const relationshipAttr = attr as RelationshipAttribute;
|
297
|
-
let isArrayParent = false;
|
298
|
-
let isArrayChild = false;
|
299
|
-
switch (relationshipAttr.relationType) {
|
301
|
+
collection.attributes.forEach((attr) => {
|
302
|
+
if (attr.type === "relationship" && attr.twoWay && attr.twoWayKey) {
|
303
|
+
const relationshipAttr = attr as RelationshipAttribute;
|
304
|
+
let isArrayParent = false;
|
305
|
+
let isArrayChild = false;
|
306
|
+
switch (relationshipAttr.relationType) {
|
300
307
|
case "oneToMany":
|
301
308
|
isArrayParent = true;
|
302
309
|
isArrayChild = false;
|
@@ -316,14 +323,14 @@ export default appwriteConfig;
|
|
316
323
|
default:
|
317
324
|
break;
|
318
325
|
}
|
319
|
-
this.addRelationship(
|
320
|
-
collection.name,
|
321
|
-
relationshipAttr.relatedCollection,
|
322
|
-
attr.key,
|
323
|
-
relationshipAttr.twoWayKey
|
324
|
-
isArrayParent,
|
325
|
-
isArrayChild
|
326
|
-
);
|
326
|
+
this.addRelationship(
|
327
|
+
collection.name,
|
328
|
+
this.resolveCollectionName(relationshipAttr.relatedCollection),
|
329
|
+
attr.key,
|
330
|
+
relationshipAttr.twoWayKey!,
|
331
|
+
isArrayParent,
|
332
|
+
isArrayChild
|
333
|
+
);
|
327
334
|
console.log(
|
328
335
|
`Extracted relationship: ${attr.key}\n\t${collection.name} -> ${relationshipAttr.relatedCollection}, databaseId: ${collection.databaseId}`
|
329
336
|
);
|
@@ -383,10 +390,10 @@ export default appwriteConfig;
|
|
383
390
|
// Generate Zod schemas (TypeScript)
|
384
391
|
if (format === "zod" || format === "both") {
|
385
392
|
this.config.collections.forEach((collection) => {
|
386
|
-
const schemaString = this.
|
387
|
-
collection.name,
|
388
|
-
collection.attributes || []
|
389
|
-
);
|
393
|
+
const schemaString = this.createSchemaStringV4(
|
394
|
+
collection.name,
|
395
|
+
collection.attributes || []
|
396
|
+
);
|
390
397
|
const camelCaseName = toCamelCase(collection.name);
|
391
398
|
const schemaPath = path.join(schemasPath, `${camelCaseName}.ts`);
|
392
399
|
fs.writeFileSync(schemaPath, schemaString, { encoding: "utf-8" });
|
@@ -409,119 +416,106 @@ export default appwriteConfig;
|
|
409
416
|
if (verbose) {
|
410
417
|
console.log(`✓ Schema generation completed (format: ${format})`);
|
411
418
|
}
|
412
|
-
}
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
const
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
419
|
+
}
|
420
|
+
|
421
|
+
// Zod v4 recursive getter-based schemas
|
422
|
+
createSchemaStringV4 = (name: string, attributes: Attribute[]): string => {
|
423
|
+
const pascalName = toPascalCase(name);
|
424
|
+
let imports = `import { z } from "zod";\n`;
|
425
|
+
|
426
|
+
// Use the relationshipMap to find related collections
|
427
|
+
const relationshipDetails = this.relationshipMap.get(name) || [];
|
428
|
+
let relatedCollections = relationshipDetails
|
429
|
+
.filter((detail, index, self) => {
|
430
|
+
const uniqueKey = `${detail.parentCollection}-${detail.childCollection}-${detail.parentKey}-${detail.childKey}`;
|
431
|
+
return (
|
432
|
+
index ===
|
433
|
+
self.findIndex(
|
426
434
|
(obj) =>
|
427
435
|
`${obj.parentCollection}-${obj.childCollection}-${obj.parentKey}-${obj.childKey}` ===
|
428
436
|
uniqueKey
|
429
437
|
)
|
430
438
|
);
|
431
439
|
})
|
432
|
-
.map((detail) => {
|
433
|
-
const relatedCollectionName = detail.isChild
|
434
|
-
? detail.parentCollection
|
435
|
-
: detail.childCollection;
|
436
|
-
const key = detail.isChild ? detail.childKey : detail.parentKey;
|
437
|
-
const isArray = detail.isArray ? "array" : "";
|
438
|
-
return [relatedCollectionName, key, isArray];
|
439
|
-
});
|
440
|
+
.map((detail) => {
|
441
|
+
const relatedCollectionName = detail.isChild
|
442
|
+
? detail.parentCollection
|
443
|
+
: detail.childCollection;
|
444
|
+
const key = detail.isChild ? detail.childKey : detail.parentKey;
|
445
|
+
const isArray = detail.isArray ? "array" : "";
|
446
|
+
return [relatedCollectionName, key, isArray];
|
447
|
+
});
|
448
|
+
|
449
|
+
// Include one-way relationship attributes directly (no twoWayKey)
|
450
|
+
const oneWayRels: Array<[string, string, string]> = [];
|
451
|
+
for (const attr of attributes) {
|
452
|
+
if (attr.type === "relationship" && attr.relatedCollection) {
|
453
|
+
const relatedName = this.resolveCollectionName(attr.relatedCollection);
|
454
|
+
const isArray =
|
455
|
+
attr.relationType === "oneToMany" || attr.relationType === "manyToMany"
|
456
|
+
? "array"
|
457
|
+
: "";
|
458
|
+
oneWayRels.push([relatedName, attr.key, isArray]);
|
459
|
+
}
|
460
|
+
}
|
461
|
+
|
462
|
+
// Merge and dedupe (by relatedName+key)
|
463
|
+
relatedCollections = [...relatedCollections, ...oneWayRels].filter(
|
464
|
+
(item, idx, self) =>
|
465
|
+
idx === self.findIndex((o) => `${o[0]}::${o[1]}` === `${item[0]}::${item[1]}`)
|
466
|
+
);
|
440
467
|
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
// Re-add imports after processing relationships
|
494
|
-
schemaString = `${imports}\n\n`;
|
495
|
-
|
496
|
-
schemaString += `export const ${pascalName}SchemaBase = z.object({\n`;
|
497
|
-
schemaString += ` $id: z.string(),\n`;
|
498
|
-
schemaString += ` $createdAt: z.string(),\n`;
|
499
|
-
schemaString += ` $updatedAt: z.string(),\n`;
|
500
|
-
schemaString += ` $permissions: z.array(z.string()),\n`;
|
501
|
-
for (const attribute of attributes) {
|
502
|
-
if (attribute.type === "relationship") {
|
503
|
-
continue;
|
504
|
-
}
|
505
|
-
schemaString += ` ${attribute.key}: ${this.typeToZod(attribute)},\n`;
|
506
|
-
}
|
507
|
-
schemaString += `});\n\n`;
|
508
|
-
schemaString += `export type ${pascalName}Base = z.infer<typeof ${pascalName}SchemaBase>`;
|
509
|
-
if (relatedTypes.length > 0) {
|
510
|
-
schemaString += ` & {\n ${relatedTypes}};\n\n`;
|
511
|
-
} else {
|
512
|
-
schemaString += `;\n\n`;
|
513
|
-
}
|
514
|
-
schemaString += `export const ${pascalName}Schema: z.ZodType<${pascalName}Base> = ${pascalName}SchemaBase`;
|
515
|
-
if (relatedTypes.length > 0) {
|
516
|
-
schemaString += `.extend({\n ${relatedTypesLazy}});\n\n`;
|
517
|
-
} else {
|
518
|
-
schemaString += `;\n`;
|
519
|
-
}
|
520
|
-
schemaString += `export type ${pascalName} = z.infer<typeof ${pascalName}Schema>;\n\n`;
|
521
|
-
}
|
522
|
-
|
523
|
-
return schemaString;
|
524
|
-
};
|
468
|
+
const hasRelationships = relatedCollections.length > 0;
|
469
|
+
|
470
|
+
// Build imports for related collections
|
471
|
+
if (hasRelationships) {
|
472
|
+
const importLines = relatedCollections.map((rel) => {
|
473
|
+
const relatedPascalName = toPascalCase(rel[0]);
|
474
|
+
const relatedCamelName = toCamelCase(rel[0]);
|
475
|
+
return `import { ${relatedPascalName}Schema } from "./${relatedCamelName}";`;
|
476
|
+
});
|
477
|
+
const unique = Array.from(new Set(importLines));
|
478
|
+
imports += unique.join("\n") + (unique.length ? "\n" : "");
|
479
|
+
}
|
480
|
+
|
481
|
+
let schemaString = `${imports}\n`;
|
482
|
+
|
483
|
+
// Single object schema with recursive getters (Zod v4)
|
484
|
+
schemaString += `export const ${pascalName}Schema = z.object({\n`;
|
485
|
+
schemaString += ` $id: z.string(),\n`;
|
486
|
+
schemaString += ` $createdAt: z.string(),\n`;
|
487
|
+
schemaString += ` $updatedAt: z.string(),\n`;
|
488
|
+
schemaString += ` $permissions: z.array(z.string()),\n`;
|
489
|
+
for (const attribute of attributes) {
|
490
|
+
if (attribute.type === "relationship") continue;
|
491
|
+
schemaString += ` ${attribute.key}: ${this.typeToZod(attribute)},\n`;
|
492
|
+
}
|
493
|
+
|
494
|
+
// Add recursive getters for relationships (respect required flag)
|
495
|
+
relatedCollections.forEach((rel) => {
|
496
|
+
const relatedPascalName = toPascalCase(rel[0]);
|
497
|
+
const isArray = rel[2] === "array";
|
498
|
+
const key = String(rel[1]);
|
499
|
+
const attrMeta = attributes.find(a => a.key === key && a.type === "relationship");
|
500
|
+
const isRequired = !!attrMeta?.required;
|
501
|
+
let getterBody = "";
|
502
|
+
if (isArray) {
|
503
|
+
getterBody = isRequired
|
504
|
+
? `${relatedPascalName}Schema.array()`
|
505
|
+
: `${relatedPascalName}Schema.array().nullish()`;
|
506
|
+
} else {
|
507
|
+
getterBody = isRequired
|
508
|
+
? `${relatedPascalName}Schema`
|
509
|
+
: `${relatedPascalName}Schema.nullish()`;
|
510
|
+
}
|
511
|
+
schemaString += ` get ${key}(){\n return ${getterBody}\n },\n`;
|
512
|
+
});
|
513
|
+
|
514
|
+
schemaString += `});\n\n`;
|
515
|
+
schemaString += `export type ${pascalName} = z.infer<typeof ${pascalName}Schema>;\n\n`;
|
516
|
+
|
517
|
+
return schemaString;
|
518
|
+
};
|
525
519
|
|
526
520
|
typeToZod = (attribute: Attribute) => {
|
527
521
|
let baseSchemaCode = "";
|
package/src/utils/loadConfigs.ts
CHANGED
@@ -4,7 +4,8 @@ import { type AppwriteConfig, type Collection, type CollectionCreate } from "app
|
|
4
4
|
import { register } from "tsx/esm/api"; // Import the register function
|
5
5
|
import { pathToFileURL } from "node:url";
|
6
6
|
import chalk from "chalk";
|
7
|
-
import { findYamlConfig, loadYamlConfig } from "../config/yamlConfig.js";
|
7
|
+
import { findYamlConfig, loadYamlConfig } from "../config/yamlConfig.js";
|
8
|
+
import { detectAppwriteVersionCached, fetchServerVersion, isVersionAtLeast } from "./versionDetection.js";
|
8
9
|
import yaml from "js-yaml";
|
9
10
|
import { z } from "zod";
|
10
11
|
import { MessageFormatter } from "../shared/messageFormatter.js";
|
@@ -165,17 +166,28 @@ export const loadConfigWithPath = async (
|
|
165
166
|
throw new Error("No valid configuration found");
|
166
167
|
}
|
167
168
|
|
168
|
-
// Determine collections
|
169
|
-
let
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
169
|
+
// Determine directory (collections or tables) based on server version / API mode
|
170
|
+
let dirName = "collections";
|
171
|
+
try {
|
172
|
+
const det = await detectAppwriteVersionCached(config.appwriteEndpoint, config.appwriteProject, config.appwriteKey);
|
173
|
+
if (det.apiMode === 'tablesdb' || isVersionAtLeast(det.serverVersion, '1.8.0')) {
|
174
|
+
dirName = 'tables';
|
175
|
+
} else {
|
176
|
+
// Try health version if not provided
|
177
|
+
const ver = await fetchServerVersion(config.appwriteEndpoint);
|
178
|
+
if (isVersionAtLeast(ver || undefined, '1.8.0')) dirName = 'tables';
|
179
|
+
}
|
180
|
+
} catch {}
|
181
|
+
|
182
|
+
// Determine collections directory based on actual config file location and dirName
|
183
|
+
let collectionsDir: string;
|
184
|
+
const configFileDir = path.dirname(actualConfigPath);
|
185
|
+
collectionsDir = path.join(configFileDir, dirName);
|
186
|
+
// Fallback if not found
|
187
|
+
if (!fs.existsSync(collectionsDir)) {
|
188
|
+
const fallback = path.join(configFileDir, dirName === 'tables' ? 'collections' : 'tables');
|
189
|
+
if (fs.existsSync(fallback)) collectionsDir = fallback;
|
190
|
+
}
|
179
191
|
|
180
192
|
// Load collections if they exist
|
181
193
|
if (fs.existsSync(collectionsDir)) {
|
@@ -267,22 +279,29 @@ export const loadConfig = async (
|
|
267
279
|
throw new Error("No valid configuration found");
|
268
280
|
}
|
269
281
|
|
270
|
-
// Determine collections
|
271
|
-
let
|
272
|
-
|
273
|
-
const
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
282
|
+
// Determine directory (collections or tables) based on server version / API mode
|
283
|
+
let dirName2 = "collections";
|
284
|
+
try {
|
285
|
+
const det = await detectAppwriteVersionCached(config.appwriteEndpoint, config.appwriteProject, config.appwriteKey);
|
286
|
+
if (det.apiMode === 'tablesdb' || isVersionAtLeast(det.serverVersion, '1.8.0')) {
|
287
|
+
dirName2 = 'tables';
|
288
|
+
} else {
|
289
|
+
const ver = await fetchServerVersion(config.appwriteEndpoint);
|
290
|
+
if (isVersionAtLeast(ver || undefined, '1.8.0')) dirName2 = 'tables';
|
291
|
+
}
|
292
|
+
} catch {}
|
293
|
+
|
294
|
+
let collectionsDir: string;
|
295
|
+
if (actualConfigPath) {
|
296
|
+
const configFileDir = path.dirname(actualConfigPath);
|
297
|
+
collectionsDir = path.join(configFileDir, dirName2);
|
298
|
+
} else {
|
299
|
+
collectionsDir = path.join(configDir, dirName2);
|
300
|
+
}
|
301
|
+
if (!fs.existsSync(collectionsDir)) {
|
302
|
+
const fallback = path.join(path.dirname(actualConfigPath || configDir), dirName2 === 'tables' ? 'collections' : 'tables');
|
303
|
+
if (fs.existsSync(fallback)) collectionsDir = fallback;
|
304
|
+
}
|
286
305
|
|
287
306
|
// Load collections if they exist
|
288
307
|
if (fs.existsSync(collectionsDir)) {
|
@@ -459,4 +478,4 @@ const loadYamlCollection = (filePath: string): CollectionCreate | null => {
|
|
459
478
|
console.error(`Error loading YAML collection from ${filePath}:`, error);
|
460
479
|
return null;
|
461
480
|
}
|
462
|
-
};
|
481
|
+
};
|