appwrite-cli 13.0.0 → 13.1.0-rc.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 (105) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +3 -3
  3. package/cli.ts +2 -0
  4. package/dist/bundle.cjs +52800 -50718
  5. package/dist/cli.js +2 -0
  6. package/dist/cli.js.map +1 -1
  7. package/dist/lib/client.d.ts.map +1 -1
  8. package/dist/lib/client.js +3 -4
  9. package/dist/lib/client.js.map +1 -1
  10. package/dist/lib/commands/config-validations.d.ts +53 -0
  11. package/dist/lib/commands/config-validations.d.ts.map +1 -0
  12. package/dist/lib/commands/config-validations.js +130 -0
  13. package/dist/lib/commands/config-validations.js.map +1 -0
  14. package/dist/lib/commands/config.d.ts +241 -236
  15. package/dist/lib/commands/config.d.ts.map +1 -1
  16. package/dist/lib/commands/config.js +85 -105
  17. package/dist/lib/commands/config.js.map +1 -1
  18. package/dist/lib/commands/generate.d.ts +7 -0
  19. package/dist/lib/commands/generate.d.ts.map +1 -0
  20. package/dist/lib/commands/generate.js +92 -0
  21. package/dist/lib/commands/generate.js.map +1 -0
  22. package/dist/lib/commands/generators/base.d.ts +58 -0
  23. package/dist/lib/commands/generators/base.d.ts.map +1 -0
  24. package/dist/lib/commands/generators/base.js +27 -0
  25. package/dist/lib/commands/generators/base.js.map +1 -0
  26. package/dist/lib/commands/generators/index.d.ts +26 -0
  27. package/dist/lib/commands/generators/index.d.ts.map +1 -0
  28. package/dist/lib/commands/generators/index.js +55 -0
  29. package/dist/lib/commands/generators/index.js.map +1 -0
  30. package/dist/lib/commands/generators/language-detector.d.ts +42 -0
  31. package/dist/lib/commands/generators/language-detector.d.ts.map +1 -0
  32. package/dist/lib/commands/generators/language-detector.js +127 -0
  33. package/dist/lib/commands/generators/language-detector.js.map +1 -0
  34. package/dist/lib/commands/generators/typescript/databases.d.ts +36 -0
  35. package/dist/lib/commands/generators/typescript/databases.d.ts.map +1 -0
  36. package/dist/lib/commands/generators/typescript/databases.js +489 -0
  37. package/dist/lib/commands/generators/typescript/databases.js.map +1 -0
  38. package/dist/lib/commands/pull.d.ts.map +1 -1
  39. package/dist/lib/commands/pull.js +15 -9
  40. package/dist/lib/commands/pull.js.map +1 -1
  41. package/dist/lib/commands/push.d.ts +6 -3
  42. package/dist/lib/commands/push.d.ts.map +1 -1
  43. package/dist/lib/commands/push.js +64 -41
  44. package/dist/lib/commands/push.js.map +1 -1
  45. package/dist/lib/commands/schema.d.ts +2 -2
  46. package/dist/lib/commands/schema.d.ts.map +1 -1
  47. package/dist/lib/commands/schema.js +2 -2
  48. package/dist/lib/commands/schema.js.map +1 -1
  49. package/dist/lib/commands/services/projects.js +7 -1
  50. package/dist/lib/commands/services/projects.js.map +1 -1
  51. package/dist/lib/commands/services/storage.js +2 -2
  52. package/dist/lib/commands/services/storage.js.map +1 -1
  53. package/dist/lib/commands/utils/attributes.d.ts +10 -2
  54. package/dist/lib/commands/utils/attributes.d.ts.map +1 -1
  55. package/dist/lib/commands/utils/attributes.js +35 -16
  56. package/dist/lib/commands/utils/attributes.js.map +1 -1
  57. package/dist/lib/commands/utils/pools.d.ts +0 -3
  58. package/dist/lib/commands/utils/pools.d.ts.map +1 -1
  59. package/dist/lib/commands/utils/pools.js +0 -34
  60. package/dist/lib/commands/utils/pools.js.map +1 -1
  61. package/dist/lib/config.d.ts.map +1 -1
  62. package/dist/lib/config.js +37 -141
  63. package/dist/lib/config.js.map +1 -1
  64. package/dist/lib/constants.d.ts +1 -1
  65. package/dist/lib/constants.d.ts.map +1 -1
  66. package/dist/lib/constants.js +1 -1
  67. package/dist/lib/constants.js.map +1 -1
  68. package/dist/lib/json.d.ts +8 -0
  69. package/dist/lib/json.d.ts.map +1 -0
  70. package/dist/lib/json.js +25 -0
  71. package/dist/lib/json.js.map +1 -0
  72. package/dist/lib/utils.d.ts +13 -1
  73. package/dist/lib/utils.d.ts.map +1 -1
  74. package/dist/lib/utils.js +45 -177
  75. package/dist/lib/utils.js.map +1 -1
  76. package/dist/package.json +3 -2
  77. package/docs/examples/projects/update-labels.md +3 -0
  78. package/install.ps1 +2 -2
  79. package/install.sh +1 -1
  80. package/lib/client.ts +3 -5
  81. package/lib/commands/config-validations.ts +199 -0
  82. package/lib/commands/config.ts +93 -120
  83. package/lib/commands/generate.ts +142 -0
  84. package/lib/commands/generators/base.ts +91 -0
  85. package/lib/commands/generators/index.ts +87 -0
  86. package/lib/commands/generators/language-detector.ts +163 -0
  87. package/lib/commands/generators/typescript/databases.ts +590 -0
  88. package/lib/commands/pull.ts +30 -9
  89. package/lib/commands/push.ts +105 -62
  90. package/lib/commands/schema.ts +3 -3
  91. package/lib/commands/services/projects.ts +13 -1
  92. package/lib/commands/services/storage.ts +2 -2
  93. package/lib/commands/utils/attributes.ts +49 -18
  94. package/lib/commands/utils/pools.ts +0 -67
  95. package/lib/config.ts +52 -142
  96. package/lib/constants.ts +1 -1
  97. package/lib/json.ts +28 -0
  98. package/lib/utils.ts +46 -221
  99. package/package.json +3 -2
  100. package/scoop/appwrite.config.json +3 -3
  101. package/dist/lib/commands/db.d.ts +0 -34
  102. package/dist/lib/commands/db.d.ts.map +0 -1
  103. package/dist/lib/commands/db.js +0 -247
  104. package/dist/lib/commands/db.js.map +0 -1
  105. package/lib/commands/db.ts +0 -324
@@ -17,7 +17,12 @@ import {
17
17
  KeysCollection,
18
18
  KeysTable,
19
19
  } from "../config.js";
20
- import { ConfigSchema, type SettingsType, type ConfigType } from "./config.js";
20
+ import {
21
+ ConfigSchema,
22
+ type SettingsType,
23
+ type ConfigType,
24
+ type CollectionType,
25
+ } from "./config.js";
21
26
  import { parseWithBetterErrors } from "./utils/error-formatter.js";
22
27
  import { createSettingsObject } from "../utils.js";
23
28
  import { Spinner, SPINNER_DOTS } from "../spinner.js";
@@ -127,6 +132,20 @@ interface PushTableOptions {
127
132
  skipConfirmation?: boolean;
128
133
  }
129
134
 
135
+ interface PushCollectionInput extends CollectionType {
136
+ databaseName?: string;
137
+ }
138
+
139
+ interface PushCollectionState extends PushCollectionInput {
140
+ remoteVersion?: {
141
+ name: string;
142
+ attributes: object[];
143
+ indexes: object[];
144
+ };
145
+ isExisted?: boolean;
146
+ isNewlyCreated?: boolean;
147
+ }
148
+
130
149
  export class Push {
131
150
  private projectClient: Client;
132
151
  private consoleClient: Client;
@@ -1414,26 +1433,26 @@ export class Push {
1414
1433
  for (let table of tables) {
1415
1434
  let columns = table.columns;
1416
1435
  let indexes = table.indexes;
1436
+ let hadChanges = false;
1417
1437
 
1418
1438
  if (table.isExisted) {
1419
- columns = await attributes.attributesToCreate(
1439
+ const columnsResult = await attributes.attributesToCreate(
1420
1440
  table.remoteVersion.columns,
1421
1441
  table.columns,
1422
1442
  table as Collection,
1423
1443
  );
1424
- indexes = await attributes.attributesToCreate(
1444
+ const indexesResult = await attributes.attributesToCreate(
1425
1445
  table.remoteVersion.indexes,
1426
1446
  table.indexes,
1427
1447
  table as Collection,
1428
1448
  true,
1429
1449
  );
1430
1450
 
1431
- if (
1432
- Array.isArray(columns) &&
1433
- columns.length <= 0 &&
1434
- Array.isArray(indexes) &&
1435
- indexes.length <= 0
1436
- ) {
1451
+ columns = columnsResult.attributes;
1452
+ indexes = indexesResult.attributes;
1453
+ hadChanges = columnsResult.hasChanges || indexesResult.hasChanges;
1454
+
1455
+ if (!hadChanges && columns.length <= 0 && indexes.length <= 0) {
1437
1456
  continue;
1438
1457
  }
1439
1458
  }
@@ -1466,25 +1485,28 @@ export class Push {
1466
1485
  }
1467
1486
 
1468
1487
  public async pushCollections(
1469
- collections: any[],
1488
+ collections: PushCollectionInput[],
1470
1489
  options: { skipConfirmation?: boolean } = {},
1471
1490
  ): Promise<{
1472
1491
  successfullyPushed: number;
1473
- errors: any[];
1492
+ errors: Error[];
1474
1493
  }> {
1475
1494
  const { skipConfirmation = false } = options;
1476
1495
  const pools = new Pools(POLL_DEFAULT_VALUE);
1477
- const attributes = new Attributes(pools, skipConfirmation);
1496
+ const attributesHelper = new Attributes(pools, skipConfirmation);
1478
1497
 
1479
- const errors: any[] = [];
1498
+ const errors: Error[] = [];
1499
+
1500
+ // Cast to state type since we'll be adding state properties
1501
+ const collectionsWithState = collections as PushCollectionState[];
1480
1502
 
1481
1503
  const databases = Array.from(
1482
- new Set(collections.map((collection: any) => collection["databaseId"])),
1504
+ new Set(collectionsWithState.map((collection) => collection.databaseId)),
1483
1505
  );
1484
1506
 
1485
1507
  // Parallel db actions
1486
1508
  await Promise.all(
1487
- databases.map(async (databaseId: any) => {
1509
+ databases.map(async (databaseId) => {
1488
1510
  const databasesService = await getDatabasesService(this.projectClient);
1489
1511
  try {
1490
1512
  const database = await databasesService.get(databaseId);
@@ -1492,7 +1514,7 @@ export class Push {
1492
1514
  // Note: We can't get the local database name here since we don't have access to localConfig
1493
1515
  // This will need to be handled by the caller if needed
1494
1516
  const localDatabaseName =
1495
- collections.find((c: any) => c.databaseId === databaseId)
1517
+ collectionsWithState.find((c) => c.databaseId === databaseId)
1496
1518
  ?.databaseName ?? databaseId;
1497
1519
 
1498
1520
  if (database.name !== localDatabaseName) {
@@ -1500,12 +1522,12 @@ export class Push {
1500
1522
 
1501
1523
  this.success(`Updated ${localDatabaseName} ( ${databaseId} ) name`);
1502
1524
  }
1503
- } catch (err: any) {
1504
- if (Number(err.code) === 404) {
1525
+ } catch (err: unknown) {
1526
+ if (err instanceof AppwriteException && Number(err.code) === 404) {
1505
1527
  this.log(`Database ${databaseId} not found. Creating it now ...`);
1506
1528
 
1507
1529
  const localDatabaseName =
1508
- collections.find((c: any) => c.databaseId === databaseId)
1530
+ collectionsWithState.find((c) => c.databaseId === databaseId)
1509
1531
  ?.databaseName ?? databaseId;
1510
1532
 
1511
1533
  await databasesService.create(databaseId, localDatabaseName);
@@ -1518,32 +1540,32 @@ export class Push {
1518
1540
 
1519
1541
  // Parallel collection actions
1520
1542
  await Promise.all(
1521
- collections.map(async (collection: any) => {
1543
+ collectionsWithState.map(async (collection) => {
1522
1544
  try {
1523
1545
  const databasesService = await getDatabasesService(
1524
1546
  this.projectClient,
1525
1547
  );
1526
1548
  const remoteCollection = await databasesService.getCollection(
1527
- collection["databaseId"],
1528
- collection["$id"],
1549
+ collection.databaseId,
1550
+ collection.$id,
1529
1551
  );
1530
1552
 
1531
1553
  if (remoteCollection.name !== collection.name) {
1532
- await databasesService.updateCollection(
1533
- collection["databaseId"],
1534
- collection["$id"],
1535
- collection.name,
1536
- );
1554
+ await databasesService.updateCollection({
1555
+ databaseId: collection.databaseId,
1556
+ collectionId: collection.$id,
1557
+ name: collection.name,
1558
+ });
1537
1559
 
1538
1560
  this.success(
1539
- `Updated ${collection.name} ( ${collection["$id"]} ) name`,
1561
+ `Updated ${collection.name} ( ${collection.$id} ) name`,
1540
1562
  );
1541
1563
  }
1542
1564
  collection.remoteVersion = remoteCollection;
1543
1565
 
1544
1566
  collection.isExisted = true;
1545
- } catch (e: any) {
1546
- if (Number(e.code) === 404) {
1567
+ } catch (e: unknown) {
1568
+ if (e instanceof AppwriteException && Number(e.code) === 404) {
1547
1569
  this.log(
1548
1570
  `Collection ${collection.name} does not exist in the project. Creating ... `,
1549
1571
  );
@@ -1551,14 +1573,19 @@ export class Push {
1551
1573
  this.projectClient,
1552
1574
  );
1553
1575
  await databasesService.createCollection({
1554
- databaseId: collection["databaseId"],
1555
- collectionId: collection["$id"],
1576
+ databaseId: collection.databaseId,
1577
+ collectionId: collection.$id,
1556
1578
  name: collection.name,
1557
1579
  documentSecurity: collection.documentSecurity,
1558
- permissions: collection["$permissions"],
1580
+ permissions: collection.$permissions,
1581
+ attributes: collection.attributes,
1582
+ indexes: collection.indexes,
1559
1583
  });
1584
+ collection.isNewlyCreated = true;
1560
1585
  } else {
1561
- errors.push(e);
1586
+ if (e instanceof Error) {
1587
+ errors.push(e);
1588
+ }
1562
1589
  throw e;
1563
1590
  }
1564
1591
  }
@@ -1567,56 +1594,72 @@ export class Push {
1567
1594
 
1568
1595
  let numberOfCollections = 0;
1569
1596
  // Serialize attribute actions
1570
- for (let collection of collections) {
1571
- let collectionAttributes = collection.attributes;
1572
- let indexes = collection.indexes;
1573
-
1574
- if (collection.isExisted) {
1575
- collectionAttributes = await attributes.attributesToCreate(
1576
- collection.remoteVersion.attributes,
1577
- collection.attributes,
1578
- collection as Collection,
1597
+ for (const collection of collectionsWithState) {
1598
+ // Skip newly created collections - their attributes and indexes were already created
1599
+ if (collection.isNewlyCreated) {
1600
+ numberOfCollections++;
1601
+ this.success(
1602
+ `Successfully pushed ${collection.name} ( ${collection.$id} )`,
1579
1603
  );
1580
- indexes = await attributes.attributesToCreate(
1581
- collection.remoteVersion.indexes,
1582
- collection.indexes,
1604
+ continue;
1605
+ }
1606
+
1607
+ if (!collection.isExisted) {
1608
+ continue;
1609
+ }
1610
+
1611
+ const collectionAttributesResult =
1612
+ await attributesHelper.attributesToCreate(
1613
+ collection.remoteVersion!.attributes,
1614
+ collection.attributes ?? [],
1583
1615
  collection as Collection,
1584
- true,
1585
1616
  );
1617
+ const indexesResult = await attributesHelper.attributesToCreate(
1618
+ collection.remoteVersion!.indexes,
1619
+ collection.indexes ?? [],
1620
+ collection as Collection,
1621
+ true,
1622
+ );
1586
1623
 
1587
- if (
1588
- Array.isArray(collectionAttributes) &&
1589
- collectionAttributes.length <= 0 &&
1590
- Array.isArray(indexes) &&
1591
- indexes.length <= 0
1592
- ) {
1593
- continue;
1594
- }
1624
+ const collectionAttributes = collectionAttributesResult.attributes;
1625
+ const indexes = indexesResult.attributes;
1626
+
1627
+ if (
1628
+ collectionAttributes.length <= 0 &&
1629
+ indexes.length <= 0 &&
1630
+ !collectionAttributesResult.hasChanges &&
1631
+ !indexesResult.hasChanges
1632
+ ) {
1633
+ continue;
1595
1634
  }
1596
1635
 
1597
1636
  this.log(
1598
- `Pushing collection ${collection.name} ( ${collection["databaseId"]} - ${collection["$id"]} ) attributes`,
1637
+ `Pushing collection ${collection.name} ( ${collection.databaseId} - ${collection.$id} ) attributes`,
1599
1638
  );
1600
1639
 
1601
1640
  try {
1602
- await attributes.createAttributes(
1641
+ await attributesHelper.createAttributes(
1603
1642
  collectionAttributes,
1604
1643
  collection as Collection,
1605
1644
  );
1606
1645
  } catch (e) {
1607
- errors.push(e);
1646
+ if (e instanceof Error) {
1647
+ errors.push(e);
1648
+ }
1608
1649
  throw e;
1609
1650
  }
1610
1651
 
1611
1652
  try {
1612
- await attributes.createIndexes(indexes, collection as Collection);
1653
+ await attributesHelper.createIndexes(indexes, collection as Collection);
1613
1654
  } catch (e) {
1614
- errors.push(e);
1655
+ if (e instanceof Error) {
1656
+ errors.push(e);
1657
+ }
1615
1658
  throw e;
1616
1659
  }
1617
1660
  numberOfCollections++;
1618
1661
  this.success(
1619
- `Successfully pushed ${collection.name} ( ${collection["$id"]} )`,
1662
+ `Successfully pushed ${collection.name} ( ${collection.$id} )`,
1620
1663
  );
1621
1664
  }
1622
1665
 
@@ -2193,7 +2236,7 @@ const pushTable = async ({
2193
2236
  const { successfullyPushed, errors } = result;
2194
2237
 
2195
2238
  if (successfullyPushed === 0) {
2196
- error("No tables were pushed.");
2239
+ warn("No tables were pushed.");
2197
2240
  } else {
2198
2241
  success(`Successfully pushed ${successfullyPushed} tables.`);
2199
2242
  }
@@ -7,7 +7,7 @@ import { parseWithBetterErrors } from "./utils/error-formatter.js";
7
7
  import JSONbig from "json-bigint";
8
8
  import * as fs from "fs";
9
9
  import * as path from "path";
10
- import { Db } from "./db.js";
10
+ import { TypeScriptDatabasesGenerator } from "./generators/typescript/databases.js";
11
11
 
12
12
  const JSONBig = JSONbig({ useNativeBigInt: true });
13
13
 
@@ -17,7 +17,7 @@ export class Schema {
17
17
 
18
18
  private pullCommandSilent: Pull;
19
19
 
20
- public db: Db;
20
+ public db: TypeScriptDatabasesGenerator;
21
21
 
22
22
  constructor({
23
23
  projectClient,
@@ -31,7 +31,7 @@ export class Schema {
31
31
 
32
32
  this.pullCommandSilent = new Pull(projectClient, consoleClient, true);
33
33
 
34
- this.db = new Db();
34
+ this.db = new TypeScriptDatabasesGenerator();
35
35
  }
36
36
 
37
37
  /**
@@ -42,7 +42,7 @@ export const projects = new Command("projects")
42
42
  projects
43
43
  .command(`list`)
44
44
  .description(`Get a list of all projects. You can use the query params to filter your results. `)
45
- .option(`--queries [queries...]`, `Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: name, teamId`)
45
+ .option(`--queries [queries...]`, `Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: name, teamId, labels, search`)
46
46
  .option(`--search <search>`, `Search term to filter your list results. Max length: 256 chars.`)
47
47
  .option(
48
48
  `--total [value]`,
@@ -429,6 +429,18 @@ projects
429
429
  ),
430
430
  );
431
431
 
432
+ projects
433
+ .command(`update-labels`)
434
+ .description(`Update the project labels by its unique ID. Labels can be used to easily filter projects in an organization.`)
435
+ .requiredOption(`--project-id <project-id>`, `Project unique ID.`)
436
+ .requiredOption(`--labels [labels...]`, `Array of project labels. Replaces the previous labels. Maximum of 1000 labels are allowed, each up to 36 alphanumeric characters long.`)
437
+ .action(
438
+ actionRunner(
439
+ async ({ projectId, labels }) =>
440
+ parse(await (await getProjectsClient()).updateLabels(projectId, labels)),
441
+ ),
442
+ );
443
+
432
444
  projects
433
445
  .command(`update-o-auth-2`)
434
446
  .description(`Update the OAuth2 provider configurations. Use this endpoint to set up or update the OAuth2 provider credentials or enable/disable providers. `)
@@ -71,7 +71,7 @@ storage
71
71
  )
72
72
  .option(`--maximum-file-size <maximum-file-size>`, `Maximum file size allowed in bytes. Maximum allowed value is 30MB.`, parseInteger)
73
73
  .option(`--allowed-file-extensions [allowed-file-extensions...]`, `Allowed file extensions. Maximum of 100 extensions are allowed, each 64 characters long.`)
74
- .option(`--compression <compression>`, `Compression algorithm choosen for compression. Can be one of none, [gzip](https://en.wikipedia.org/wiki/Gzip), or [zstd](https://en.wikipedia.org/wiki/Zstd), For file size above 20MB compression is skipped even if it's enabled`)
74
+ .option(`--compression <compression>`, `Compression algorithm chosen for compression. Can be one of none, [gzip](https://en.wikipedia.org/wiki/Gzip), or [zstd](https://en.wikipedia.org/wiki/Zstd), For file size above 20MB compression is skipped even if it's enabled`)
75
75
  .option(
76
76
  `--encryption [value]`,
77
77
  `Is encryption enabled? For file size above 20MB encryption is skipped even if it's enabled`,
@@ -128,7 +128,7 @@ storage
128
128
  )
129
129
  .option(`--maximum-file-size <maximum-file-size>`, `Maximum file size allowed in bytes. Maximum allowed value is 30MB.`, parseInteger)
130
130
  .option(`--allowed-file-extensions [allowed-file-extensions...]`, `Allowed file extensions. Maximum of 100 extensions are allowed, each 64 characters long.`)
131
- .option(`--compression <compression>`, `Compression algorithm choosen for compression. Can be one of none, [gzip](https://en.wikipedia.org/wiki/Gzip), or [zstd](https://en.wikipedia.org/wiki/Zstd), For file size above 20MB compression is skipped even if it's enabled`)
131
+ .option(`--compression <compression>`, `Compression algorithm chosen for compression. Can be one of none, [gzip](https://en.wikipedia.org/wiki/Gzip), or [zstd](https://en.wikipedia.org/wiki/Zstd), For file size above 20MB compression is skipped even if it's enabled`)
132
132
  .option(
133
133
  `--encryption [value]`,
134
134
  `Is encryption enabled? For file size above 20MB encryption is skipped even if it's enabled`,
@@ -480,40 +480,59 @@ export class Attributes {
480
480
  );
481
481
  };
482
482
 
483
+ /**
484
+ * Check if attribute is a child-side relationship
485
+ * Child-side relationships are auto-generated by Appwrite and should be skipped
486
+ */
487
+ private isChildSideRelationship = (attribute: any): boolean =>
488
+ attribute.type === "relationship" && attribute.side === "child";
489
+
483
490
  /**
484
491
  * Filter deleted and recreated attributes,
485
- * return list of attributes to create
492
+ * return list of attributes to create and whether any changes were made
486
493
  */
487
494
  public attributesToCreate = async (
488
495
  remoteAttributes: any[],
489
496
  localAttributes: any[],
490
497
  collection: Collection,
491
498
  isIndex: boolean = false,
492
- ): Promise<any[]> => {
493
- const deleting = remoteAttributes
499
+ ): Promise<{ attributes: any[]; hasChanges: boolean }> => {
500
+ // Filter out child-side relationships from both local and remote attributes for comparison
501
+ // Child-side relationships are auto-generated by Appwrite when creating two-way relationships
502
+ // from the parent side, so we should not compare or try to create them directly
503
+ const filteredLocalAttributes = localAttributes.filter(
504
+ (attr) => !this.isChildSideRelationship(attr),
505
+ );
506
+ let filteredRemoteAttributes = remoteAttributes.filter(
507
+ (attr) => !this.isChildSideRelationship(attr),
508
+ );
509
+
510
+ const deleting = filteredRemoteAttributes
494
511
  .filter(
495
- (attribute) => !this.attributesContains(attribute, localAttributes),
512
+ (attribute) =>
513
+ !this.attributesContains(attribute, filteredLocalAttributes),
496
514
  )
497
515
  .map((attr) => this.generateChangesObject(attr, collection, false));
498
- const adding = localAttributes
516
+ const adding = filteredLocalAttributes
499
517
  .filter(
500
- (attribute) => !this.attributesContains(attribute, remoteAttributes),
518
+ (attribute) =>
519
+ !this.attributesContains(attribute, filteredRemoteAttributes),
501
520
  )
502
521
  .map((attr) => this.generateChangesObject(attr, collection, true));
503
- const conflicts = remoteAttributes
522
+ const conflicts = filteredRemoteAttributes
504
523
  .map((attribute) =>
505
524
  this.checkAttributeChanges(
506
525
  attribute,
507
- this.attributesContains(attribute, localAttributes),
526
+ this.attributesContains(attribute, filteredLocalAttributes),
508
527
  collection,
509
528
  ),
510
529
  )
511
530
  .filter((attribute) => attribute !== undefined) as AttributeChange[];
512
- const changes = remoteAttributes
531
+ const changes = filteredRemoteAttributes
513
532
  .map((attribute) =>
514
533
  this.checkAttributeChanges(
515
534
  attribute,
516
- this.attributesContains(attribute, localAttributes),
535
+ this.attributesContains(attribute, filteredLocalAttributes),
517
536
  collection,
518
537
  false,
519
538
  ),
@@ -527,7 +546,7 @@ export class Attributes {
527
546
  let changedAttributes: any[] = [];
528
547
  const changing = [...deleting, ...adding, ...conflicts, ...changes];
529
548
  if (changing.length === 0) {
530
- return changedAttributes;
549
+ return { attributes: changedAttributes, hasChanges: false };
531
550
  }
532
551
 
533
552
  log(
@@ -573,7 +592,7 @@ export class Attributes {
573
592
  }
574
593
 
575
594
  if ((await this.getConfirmation()) !== true) {
576
- return changedAttributes;
595
+ return { attributes: changedAttributes, hasChanges: false };
577
596
  }
578
597
  }
579
598
 
@@ -584,7 +603,7 @@ export class Attributes {
584
603
  this.deleteAttribute(collection, changed, isIndex),
585
604
  ),
586
605
  );
587
- remoteAttributes = remoteAttributes.filter(
606
+ filteredRemoteAttributes = filteredRemoteAttributes.filter(
588
607
  (attribute) => !this.attributesContains(attribute, changedAttributes),
589
608
  );
590
609
  }
@@ -631,9 +650,11 @@ export class Attributes {
631
650
  }
632
651
  }
633
652
 
634
- return localAttributes.filter(
635
- (attribute) => !this.attributesContains(attribute, remoteAttributes),
653
+ const newAttributes = filteredLocalAttributes.filter(
654
+ (attribute) =>
655
+ !this.attributesContains(attribute, filteredRemoteAttributes),
636
656
  );
657
+ return { attributes: newAttributes, hasChanges: true };
637
658
  };
638
659
 
639
660
  public createIndexes = async (
@@ -664,13 +685,17 @@ export class Attributes {
664
685
  throw new Error("Index creation timed out.");
665
686
  }
666
687
 
667
- success(`Created ${indexes.length} indexes`);
688
+ if (indexes.length > 0) {
689
+ success(`Created ${indexes.length} indexes`);
690
+ }
668
691
  };
669
692
 
670
693
  public createAttributes = async (
671
694
  attributes: any[],
672
695
  collection: Collection,
673
696
  ): Promise<void> => {
697
+ log(`Creating attributes ...`);
698
+
674
699
  for (let attribute of attributes) {
675
700
  if (attribute.side !== "child") {
676
701
  await this.createAttribute(
@@ -694,13 +719,17 @@ export class Attributes {
694
719
  }
695
720
 
696
721
  const createdCount = attributes.filter((a) => a.side !== "child").length;
697
- success(`Created ${createdCount} attributes`);
722
+ if (createdCount > 0) {
723
+ success(`Created ${createdCount} attributes`);
724
+ }
698
725
  };
699
726
 
700
727
  public createColumns = async (
701
728
  columns: any[],
702
729
  table: Collection,
703
730
  ): Promise<void> => {
731
+ log(`Creating columns ...`);
732
+
704
733
  for (let column of columns) {
705
734
  if (column.side !== "child") {
706
735
  await this.createAttribute(table["databaseId"], table["$id"], column);
@@ -720,6 +749,8 @@ export class Attributes {
720
749
  }
721
750
 
722
751
  const createdCount = columns.filter((c) => c.side !== "child").length;
723
- success(`Created ${createdCount} columns`);
752
+ if (createdCount > 0) {
753
+ success(`Created ${createdCount} columns`);
754
+ }
724
755
  };
725
756
  }
@@ -14,14 +14,6 @@ export class Pools {
14
14
  }
15
15
  }
16
16
 
17
- public setPollMaxDebounces(value: number): void {
18
- this.pollMaxDebounces = value;
19
- }
20
-
21
- public getPollMaxDebounces(): number {
22
- return this.pollMaxDebounces;
23
- }
24
-
25
17
  public wipeAttributes = async (
26
18
  databaseId: string,
27
19
  collectionId: string,
@@ -226,65 +218,6 @@ export class Pools {
226
218
  );
227
219
  };
228
220
 
229
- public deleteIndexes = async (
230
- databaseId: string,
231
- collectionId: string,
232
- indexesKeys: any[],
233
- iteration: number = 1,
234
- ): Promise<boolean> => {
235
- if (iteration > this.pollMaxDebounces) {
236
- return false;
237
- }
238
-
239
- if (this.pollMaxDebounces === this.POLL_DEFAULT_VALUE) {
240
- let steps = Math.max(1, Math.ceil(indexesKeys.length / this.STEP_SIZE));
241
- if (steps > 1 && iteration === 1) {
242
- this.pollMaxDebounces *= steps;
243
-
244
- log(
245
- "Found a large number of indexes to be deleted. Increasing timeout to " +
246
- (this.pollMaxDebounces * this.POLL_DEBOUNCE) / 1000 / 60 +
247
- " minutes",
248
- );
249
- }
250
- }
251
-
252
- const { indexes } = await paginate(
253
- async (args: any) => {
254
- const databasesService = await getDatabasesService();
255
- return await databasesService.listIndexes(
256
- args.databaseId,
257
- args.collectionId,
258
- args.queries || [],
259
- );
260
- },
261
- {
262
- databaseId,
263
- collectionId,
264
- },
265
- 100,
266
- "indexes",
267
- );
268
-
269
- const indexKeySet = new Set(indexes.map((i: any) => i.key));
270
- const ready = indexesKeys.filter((index: any) =>
271
- indexKeySet.has(index.key),
272
- );
273
-
274
- if (ready.length === 0) {
275
- return true;
276
- }
277
-
278
- await new Promise((resolve) => setTimeout(resolve, this.POLL_DEBOUNCE));
279
-
280
- return await this.deleteIndexes(
281
- databaseId,
282
- collectionId,
283
- indexesKeys,
284
- iteration + 1,
285
- );
286
- };
287
-
288
221
  public expectIndexes = async (
289
222
  databaseId: string,
290
223
  collectionId: string,