Skip to content

Commit 7014fa3

Browse files
authored
Merge pull request #19 from appwrite/add-appwrite-tools
feat: add Appwrite service integration and update package dependencies
2 parents 538c6fc + 42d9e57 commit 7014fa3

File tree

8 files changed

+539
-148
lines changed

8 files changed

+539
-148
lines changed

.github/workflows/try-module.yml

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
11
name: Publish to try-module.cloud
22

33
on:
4-
pull_request:
4+
pull_request:
55

66
permissions:
7-
pull-requests: write
8-
contents: read
7+
pull-requests: write
8+
contents: read
99

1010
jobs:
11-
publish:
12-
runs-on: ubuntu-latest
13-
steps:
14-
- name: Checkout code
15-
uses: actions/checkout@v4
16-
- name: Use Node.js
17-
uses: actions/setup-node@v2
18-
with:
19-
node-version: 20
20-
- name: Install dependencies
21-
run: npm ci
22-
- name: Build packages
23-
run: npm run build
24-
- name: Publish @appwrite.io/pink-svelte
25-
uses: TorstenDittmann/try-module-action@1.0.15
26-
with:
27-
organization: appwrite
28-
directory: ./
29-
secret: ${{ secrets.TRY_MODULE_SECRET }}
30-
github-token: ${{ secrets.GITHUB_TOKEN }}
11+
publish:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v4
16+
- name: Use Node.js
17+
uses: actions/setup-node@v2
18+
with:
19+
node-version: 20
20+
- name: Install dependencies
21+
run: npm ci
22+
- name: Build packages
23+
run: npm run build
24+
- name: Publish @appwrite.io/synapse
25+
uses: TorstenDittmann/try-module-action@main
26+
with:
27+
organization: appwrite
28+
directory: ./
29+
secret: ${{ secrets.TRY_MODULE_SECRET }}
30+
github-token: ${{ secrets.GITHUB_TOKEN }}

package-lock.json

Lines changed: 108 additions & 92 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@appwrite.io/synapse",
3-
"version": "0.4.9",
3+
"version": "0.5.0",
44
"description": "Operating system gateway for remote serverless environments",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",
@@ -28,7 +28,8 @@
2828
"mime-types": "^3.0.1",
2929
"node-pty": "^1.0.0",
3030
"prettier": "^3.5.3",
31-
"ws": "^8.18.2"
31+
"ws": "^8.18.2",
32+
"node-appwrite": "17.0.0"
3233
},
3334
"devDependencies": {
3435
"@types/archiver": "^6.0.3",

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export { Git } from "./services/git";
44
export { System } from "./services/system";
55
export { Terminal } from "./services/terminal";
66
export { Synapse } from "./synapse";
7+
export { Appwrite } from "./services/appwrite";

src/services/appwrite.ts

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import * as fsSync from "fs";
2+
import { Client, Databases, Sites, Storage, Teams, Users } from "node-appwrite";
3+
import { Synapse } from "../synapse";
4+
5+
export class Appwrite {
6+
private synapse: Synapse;
7+
private client: Client;
8+
private serviceInstances: Record<string, any> = {};
9+
private availableServices: Record<string, any> = {
10+
teams: Teams,
11+
users: Users,
12+
databases: Databases,
13+
storage: Storage,
14+
sites: Sites,
15+
};
16+
private workDir: string;
17+
18+
constructor(
19+
synapse: Synapse,
20+
workDir: string = process.cwd(),
21+
endpoint: string = "https://cloud.appwrite.io/v1",
22+
) {
23+
this.synapse = synapse;
24+
this.client = new Client().setEndpoint(endpoint);
25+
if (workDir) {
26+
if (!fsSync.existsSync(workDir)) {
27+
fsSync.mkdirSync(workDir, { recursive: true });
28+
}
29+
this.workDir = workDir;
30+
} else {
31+
this.workDir = process.cwd();
32+
}
33+
}
34+
35+
/**
36+
* Set the API endpoint
37+
* @param endpoint API endpoint
38+
* @returns this instance for method chaining
39+
*/
40+
setEndpoint(endpoint: string): Appwrite {
41+
this.client.setEndpoint(endpoint);
42+
return this;
43+
}
44+
45+
/**
46+
* Set the project ID
47+
* @param projectId Appwrite project ID
48+
* @returns this instance for method chaining
49+
*/
50+
setProject(projectId: string): Appwrite {
51+
this.client.setProject(projectId);
52+
return this;
53+
}
54+
55+
/**
56+
* Set the API key
57+
* @param apiKey Appwrite API key
58+
* @returns this instance for method chaining
59+
*/
60+
setKey(apiKey: string): Appwrite {
61+
this.client.setKey(apiKey);
62+
return this;
63+
}
64+
65+
/**
66+
* Set the JWT
67+
* @param jwt JWT token
68+
* @returns this instance for method chaining
69+
*/
70+
setJWT(jwt: string): Appwrite {
71+
this.client.setJWT(jwt);
72+
return this;
73+
}
74+
75+
/**
76+
* Set a session cookie
77+
* @param cookie Session cookie
78+
* @returns this instance for method chaining
79+
*/
80+
setSession(cookie: string): Appwrite {
81+
this.client.setSession(cookie);
82+
return this;
83+
}
84+
85+
/**
86+
* Get the Appwrite client instance
87+
*/
88+
getClient(): Client {
89+
return this.client;
90+
}
91+
92+
/**
93+
* Get the working directory
94+
* @returns the working directory
95+
*/
96+
getWorkDir(): string {
97+
return this.workDir;
98+
}
99+
100+
/**
101+
* Check if the SDK has been properly initialized
102+
* @returns boolean indicating if endpoint, project ID, and API key are all set
103+
*/
104+
isInitialized(): boolean {
105+
// Access the private config from the client to check initialization status
106+
const config = (this.client as any).config;
107+
108+
// Check if all required configuration values are set
109+
return !!(
110+
config?.endpoint &&
111+
config?.project &&
112+
(config?.key || config?.jwt || config?.session)
113+
);
114+
}
115+
116+
/**
117+
* Call a method on an Appwrite service
118+
* @param serviceName The name of the service (e.g., 'users', 'databases')
119+
* @param methodName The name of the method to call on the service
120+
* @param args Arguments to pass to the method
121+
* @returns The result of the method call
122+
* @throws Error if service or method does not exist
123+
*/
124+
async call(
125+
serviceName: string,
126+
methodName: string,
127+
args: object = {},
128+
): Promise<any> {
129+
// Check if SDK is initialized before making any calls
130+
if (!this.isInitialized()) {
131+
throw new Error(
132+
"Appwrite SDK is not properly initialized. Please ensure endpoint, project ID, and authentication (API key, JWT, or session) are set.",
133+
);
134+
}
135+
136+
// Convert service name to lowercase for case-insensitive matching
137+
const normalizedServiceName = serviceName.toLowerCase();
138+
139+
// Check if service exists
140+
if (!this.availableServices[normalizedServiceName]) {
141+
throw new Error(
142+
`Service '${serviceName}' does not exist in Appwrite SDK`,
143+
);
144+
}
145+
146+
// Get or create service instance
147+
if (!this.serviceInstances[normalizedServiceName]) {
148+
this.serviceInstances[normalizedServiceName] = new this.availableServices[
149+
normalizedServiceName
150+
](this.client);
151+
}
152+
153+
const service = this.serviceInstances[normalizedServiceName];
154+
155+
// Check if method exists
156+
if (typeof service[methodName] !== "function") {
157+
throw new Error(
158+
`Method '${methodName}' does not exist in service '${serviceName}'`,
159+
);
160+
}
161+
162+
// Call the method with provided arguments
163+
return service[methodName](...Object.values(args));
164+
}
165+
}

src/services/filesystem.ts

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -477,29 +477,24 @@ export class Filesystem {
477477
ig = null;
478478
}
479479

480-
// Initialize chokidar watcher
481480
const watcher = chokidar.watch(fullPath, {
482481
ignored: (filePath: string) => {
483-
if (ig) {
484-
const relativePath = path
485-
.relative(path.resolve(fullPath), path.resolve(filePath))
486-
.replace(/\\/g, "/");
487-
if (relativePath === "" || relativePath === ".") {
488-
return false;
489-
}
490-
if (relativePath.startsWith("..")) {
491-
return true;
492-
}
493-
if (ig.ignores(relativePath)) {
494-
this.log(`Ignoring file: ${relativePath}, filePath: ${filePath}`);
495-
return true;
496-
}
497-
if (
498-
IGNORE_PATTERNS.some((pattern) => relativePath.includes(pattern))
499-
) {
500-
this.log(`Ignoring file: ${relativePath}, filePath: ${filePath}`);
501-
return true;
502-
}
482+
const relativePath = path
483+
.relative(path.resolve(fullPath), path.resolve(filePath))
484+
.replace(/\\/g, "/");
485+
if (relativePath === "" || relativePath === ".") {
486+
return false;
487+
}
488+
if (relativePath.startsWith("..")) {
489+
return true;
490+
}
491+
if (IGNORE_PATTERNS.some((pattern) => relativePath.includes(pattern))) {
492+
this.log(`Ignoring file: ${relativePath}, filePath: ${filePath}`);
493+
return true;
494+
}
495+
if (ig && ig.ignores(relativePath)) {
496+
this.log(`Ignoring file: ${relativePath}, filePath: ${filePath}`);
497+
return true;
503498
}
504499
return false;
505500
},
@@ -665,13 +660,14 @@ export class Filesystem {
665660
}
666661

667662
/**
668-
* Creates a zip file containing all files in the workDir and returns it as a Buffer
669-
* @returns Promise<ZipResult> containing the zip file as a Buffer
663+
* Creates a gzip file containing all files in the workDir and returns it as a Buffer
664+
* @returns Promise<ZipResult> containing the gzip file as a Buffer
670665
*/
671-
async createZipFile(): Promise<ZipResult> {
666+
async createGzipFile(saveAs: string | null = null): Promise<ZipResult> {
672667
try {
673-
const archive = archiver("zip", {
674-
zlib: { level: 9 }, // Maximum compression
668+
const archive = archiver("tar", {
669+
gzip: true,
670+
gzipOptions: { level: 9 },
675671
});
676672

677673
// Use a more direct approach with streams
@@ -694,18 +690,44 @@ export class Filesystem {
694690
.relative(this.workDir, fullPath)
695691
.replace(/\\/g, "/");
696692

693+
// Skip .git directory and other common ignored patterns
694+
if (
695+
IGNORE_PATTERNS.some((pattern) => relativePath.includes(pattern))
696+
) {
697+
continue;
698+
}
699+
697700
if (entry.isDirectory()) {
698701
await addDirectory(fullPath);
699702
} else {
700-
archive.file(fullPath, { name: relativePath });
703+
try {
704+
const stats = await fs.stat(fullPath);
705+
if (stats.isFile()) {
706+
archive.file(fullPath, { name: relativePath });
707+
}
708+
} catch (err) {
709+
console.warn(`Skipping file ${fullPath}: ${err}`);
710+
}
701711
}
702712
}
703713
};
704714

705-
// Process files and finalize archive
715+
// Process files
706716
await addDirectory(this.workDir);
717+
718+
// Finalize archive
707719
archive.finalize();
708720

721+
if (saveAs) {
722+
const fullSavePath = this.resolvePath(saveAs);
723+
const writeStream = fsSync.createWriteStream(fullSavePath);
724+
archive.pipe(writeStream);
725+
await new Promise<void>((resolve, reject) => {
726+
writeStream.on("finish", () => resolve());
727+
writeStream.on("error", (error) => reject(error));
728+
});
729+
}
730+
709731
// Wait for archive to complete and return result
710732
const buffer = await archivePromise;
711733
return {

0 commit comments

Comments
 (0)