diff --git a/lib/commands/push.ts b/lib/commands/push.ts index 060ee3c..df5b2b7 100644 --- a/lib/commands/push.ts +++ b/lib/commands/push.ts @@ -1373,8 +1373,12 @@ export class Push { }> { const { attempts, skipConfirmation = false } = options; const pollMaxDebounces = attempts ?? POLL_DEFAULT_VALUE; - const pools = new Pools(pollMaxDebounces); - const attributes = new Attributes(pools, skipConfirmation); + const pools = new Pools(pollMaxDebounces, this.projectClient); + const attributes = new Attributes( + pools, + skipConfirmation, + this.projectClient, + ); let tablesChanged = new Set(); const errors: any[] = []; @@ -1506,8 +1510,12 @@ export class Push { errors: Error[]; }> { const { skipConfirmation = false } = options; - const pools = new Pools(POLL_DEFAULT_VALUE); - const attributesHelper = new Attributes(pools, skipConfirmation); + const pools = new Pools(POLL_DEFAULT_VALUE, this.projectClient); + const attributesHelper = new Attributes( + pools, + skipConfirmation, + this.projectClient, + ); const errors: Error[] = []; diff --git a/lib/commands/utils/attributes.ts b/lib/commands/utils/attributes.ts index 9f1e582..3ba71f7 100644 --- a/lib/commands/utils/attributes.ts +++ b/lib/commands/utils/attributes.ts @@ -4,6 +4,7 @@ import { KeysAttributes } from "../../config.js"; import { log, success, error, cliConfig, drawTable } from "../../parser.js"; import { Pools } from "./pools.js"; import inquirer from "inquirer"; +import type { Client } from "@appwrite.io/console"; const changeableKeys = [ "status", @@ -53,12 +54,17 @@ const questionPushChangesConfirmation = [ export class Attributes { private pools: Pools; private skipConfirmation: boolean; + private projectClient?: Client; - constructor(pools?: Pools, skipConfirmation = false) { - this.pools = pools || new Pools(); + constructor(pools?: Pools, skipConfirmation = false, projectClient?: Client) { + this.pools = pools || new Pools(undefined, projectClient); this.skipConfirmation = skipConfirmation; + this.projectClient = projectClient; } + private getDatabasesService = async () => + getDatabasesService(this.projectClient); + private getConfirmation = async (): Promise => { if (cliConfig.force || this.skipConfirmation) { return true; @@ -197,7 +203,7 @@ export class Attributes { collectionId: string, attribute: any, ): Promise => { - const databasesService = await getDatabasesService(); + const databasesService = await this.getDatabasesService(); switch (attribute.type) { case "string": switch (attribute.format) { @@ -373,7 +379,7 @@ export class Attributes { collectionId: string, attribute: any, ): Promise => { - const databasesService = await getDatabasesService(); + const databasesService = await this.getDatabasesService(); switch (attribute.type) { case "string": switch (attribute.format) { @@ -533,7 +539,7 @@ export class Attributes { `Deleting ${isIndex ? "index" : "attribute"} ${attribute.key} of ${collection.name} ( ${collection["$id"]} )`, ); - const databasesService = await getDatabasesService(); + const databasesService = await this.getDatabasesService(); if (isIndex) { await databasesService.deleteIndex( collection["databaseId"], @@ -733,7 +739,7 @@ export class Attributes { ): Promise => { log(`Creating indexes ...`); - const databasesService = await getDatabasesService(); + const databasesService = await this.getDatabasesService(); for (let index of indexes) { await databasesService.createIndex({ databaseId: collection["databaseId"], diff --git a/lib/commands/utils/pools.ts b/lib/commands/utils/pools.ts index f596927..45aad01 100644 --- a/lib/commands/utils/pools.ts +++ b/lib/commands/utils/pools.ts @@ -1,19 +1,25 @@ import { getDatabasesService } from "../../services.js"; import { paginate } from "../../paginate.js"; import { log } from "../../parser.js"; +import type { Client } from "@appwrite.io/console"; export class Pools { private STEP_SIZE = 100; // Resources private POLL_DEBOUNCE = 2000; // Milliseconds private pollMaxDebounces = 30; private POLL_DEFAULT_VALUE = 30; + private projectClient?: Client; - constructor(pollMaxDebounces?: number) { + constructor(pollMaxDebounces?: number, projectClient?: Client) { if (pollMaxDebounces) { this.pollMaxDebounces = pollMaxDebounces; } + this.projectClient = projectClient; } + private getDatabasesService = async () => + getDatabasesService(this.projectClient); + public wipeAttributes = async ( databaseId: string, collectionId: string, @@ -23,7 +29,7 @@ export class Pools { return false; } - const databasesService = await getDatabasesService(); + const databasesService = await this.getDatabasesService(); const response = await databasesService.listAttributes( databaseId, collectionId, @@ -62,7 +68,7 @@ export class Pools { return false; } - const databasesService = await getDatabasesService(); + const databasesService = await this.getDatabasesService(); const response = await databasesService.listIndexes( databaseId, collectionId, @@ -117,7 +123,7 @@ export class Pools { const { attributes } = await paginate( async (args: any) => { - const databasesService = await getDatabasesService(); + const databasesService = await this.getDatabasesService(); return await databasesService.listAttributes({ databaseId: args.databaseId, collectionId: args.collectionId, @@ -175,7 +181,7 @@ export class Pools { const { attributes } = await paginate( async (args: any) => { - const databasesService = await getDatabasesService(); + const databasesService = await this.getDatabasesService(); return await databasesService.listAttributes( args.databaseId, args.collectionId, @@ -243,7 +249,7 @@ export class Pools { const { indexes } = await paginate( async (args: any) => { - const databasesService = await getDatabasesService(); + const databasesService = await this.getDatabasesService(); return await databasesService.listIndexes( args.databaseId, args.collectionId, diff --git a/package.json b/package.json index e632dc5..c5440ba 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "format": "prettier --write \"**/*.{js,ts,json,md}\"", "generate": "tsx scripts/generate-commands.ts", "prepublishOnly": "npm run build", - "test": "echo \"Error: no test specified\" && exit 1", + "test": "bun test", "linux-x64": "bun build cli.ts --compile --sourcemap=inline --target=bun-linux-x64 --outfile build/appwrite-cli-linux-x64", "linux-arm64": "bun build cli.ts --compile --sourcemap=inline --target=bun-linux-arm64 --outfile build/appwrite-cli-linux-arm64", "mac-x64": "bun build cli.ts --compile --sourcemap=inline --target=bun-darwin-x64 --outfile build/appwrite-cli-darwin-x64", diff --git a/tests/push.programmatic.tables.test.ts b/tests/push.programmatic.tables.test.ts new file mode 100644 index 0000000..80d4258 --- /dev/null +++ b/tests/push.programmatic.tables.test.ts @@ -0,0 +1,109 @@ +import { describe, expect, test, mock } from "bun:test"; + +describe("Push.pushTables programmatic mode", () => { + test("uses injected project client for table columns and indexes", async () => { + const projectClient = { _tag: "project-client" } as any; + const consoleClient = { _tag: "console-client" } as any; + + const databaseServiceClientArgs: any[] = []; + const tableServiceClientArgs: any[] = []; + + mock.module("../lib/services.js", () => { + const databasesService = { + createStringAttribute: async () => ({}), + createIndex: async () => ({}), + listAttributes: async () => ({ + total: 1, + attributes: [{ key: "title", status: "available" }], + }), + listIndexes: async () => ({ + total: 1, + indexes: [{ key: "idx_title", status: "available" }], + }), + }; + + const tablesService = { + getTable: async () => { + throw { code: 404 }; + }, + createTable: async () => ({}), + }; + + const throwIfNoProjectClient = (sdk?: any) => { + if (!sdk) { + throw new Error( + "Project is not set. Please run `appwrite init project`.", + ); + } + }; + + return { + getProxyService: async () => ({}), + getConsoleService: async () => ({}), + getFunctionsService: async () => ({}), + getSitesService: async () => ({}), + getStorageService: async () => ({}), + getMessagingService: async () => ({}), + getOrganizationsService: async () => ({}), + getTeamsService: async () => ({}), + getProjectsService: async () => ({}), + getDatabasesService: async (sdk?: any) => { + databaseServiceClientArgs.push(sdk); + throwIfNoProjectClient(sdk); + return databasesService as any; + }, + getTablesDBService: async (sdk?: any) => { + tableServiceClientArgs.push(sdk); + throwIfNoProjectClient(sdk); + return tablesService as any; + }, + }; + }); + + const { Push } = await import("../lib/commands/push.ts"); + const push = new Push(projectClient, consoleClient, true); + + const result = await push.pushTables( + [ + { + $id: "tbl_1", + databaseId: "db_1", + name: "Regression Table", + rowSecurity: true, + enabled: true, + columns: [ + { + key: "title", + type: "string", + size: 255, + required: true, + default: null, + array: false, + encrypt: false, + }, + ], + indexes: [ + { + key: "idx_title", + type: "key", + columns: ["title"], + orders: ["ASC"], + }, + ], + }, + ], + { attempts: 1, skipConfirmation: true }, + ); + + expect(result.errors).toHaveLength(0); + expect(result.successfullyPushed).toBe(1); + expect(databaseServiceClientArgs.length).toBeGreaterThan(0); + expect( + databaseServiceClientArgs.every((sdk) => sdk === projectClient), + ).toBe(true); + expect(tableServiceClientArgs.length).toBeGreaterThan(0); + expect(tableServiceClientArgs.every((sdk) => sdk === projectClient)).toBe( + true, + ); + }); +});