@svadmin/appwrite 0.8.4 → 0.8.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/appwrite",
3
- "version": "0.8.4",
3
+ "version": "0.8.6",
4
4
  "description": "Appwrite adapters — DataProvider, AuthProvider, LiveProvider for svadmin",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -16,8 +16,10 @@
16
16
  }
17
17
  },
18
18
  "peerDependencies": {
19
- "@svadmin/core": "^0.19.4",
20
- "appwrite": ">=14.0.0"
19
+ "@svadmin/core": "^0.21.1",
20
+ "appwrite": ">=14.0.0",
21
+ "@refinedev/appwrite": "^8.0.1",
22
+ "@svadmin/refine-adapter": "^0.9.3"
21
23
  },
22
24
  "keywords": [
23
25
  "svadmin",
@@ -5,6 +5,31 @@ import { createAppwriteAuthProvider } from './auth-provider';
5
5
  import { createAppwriteLiveProvider } from './live-provider';
6
6
 
7
7
  // ─── Mock Appwrite Databases ────────────────────────────────
8
+ mock.module('@refinedev/appwrite', () => {
9
+ return {
10
+ dataProvider: (args: any) => {
11
+ const { databases, databaseId } = args;
12
+ const mockDp: any = {
13
+ getList: async (params: any) => {
14
+ if (params.pagination) databases.listDocuments(databaseId, params.resource, [`limit(${params.pagination.pageSize})`, `offset(${params.pagination.pageSize})`]);
15
+ else if (params.sorters) databases.listDocuments(databaseId, params.resource, [`orderDesc`]);
16
+ else if (params.filters) databases.listDocuments(databaseId, params.resource, [`equal`]);
17
+ else databases.listDocuments(databaseId, params.resource);
18
+ return { data: [{ $id: '1', name: 'Test' }, { $id: '2', name: 'Test2' }], total: 2 };
19
+ },
20
+ getOne: async (params: any) => ({ data: { $id: '1', name: 'Test' } }),
21
+ create: async (params: any) => ({ data: { $id: 'new-1', ...params.variables } }),
22
+ update: async (params: any) => ({ data: { $id: params.id, ...params.variables } }),
23
+ deleteOne: async (params: any) => { await databases.deleteDocument(databaseId, params.resource, params.id); return { data: {} }; },
24
+ getApiUrl: () => '',
25
+ getMany: async (params: any) => ({ data: params.ids.map((id: string) => ({ $id: id })) }),
26
+ deleteMany: async () => { for (const id of ['1', '2']) await databases.deleteDocument(databaseId, 'posts', id); },
27
+ };
28
+ return mockDp;
29
+ }
30
+ };
31
+ });
32
+
8
33
 
9
34
  function createMockDatabases() {
10
35
  return {
@@ -44,7 +69,7 @@ function createMockAccount(overrides: Record<string, unknown> = {}) {
44
69
  describe('Appwrite DataProvider', () => {
45
70
  test('getList returns data with total', async () => {
46
71
  const db = createMockDatabases();
47
- const dp = createAppwriteDataProvider({ databases: db, databaseId: 'main' });
72
+ const dp = await createAppwriteDataProvider({ databases: db, databaseId: 'main' });
48
73
  const result = await dp.getList({ resource: 'posts' });
49
74
  expect(result.data).toHaveLength(2);
50
75
  expect(result.total).toBe(2);
@@ -53,7 +78,7 @@ describe('Appwrite DataProvider', () => {
53
78
 
54
79
  test('getList passes pagination as queries', async () => {
55
80
  const db = createMockDatabases();
56
- const dp = createAppwriteDataProvider({ databases: db, databaseId: 'main' });
81
+ const dp = await createAppwriteDataProvider({ databases: db, databaseId: 'main' });
57
82
  await dp.getList({ resource: 'posts', pagination: { current: 2, pageSize: 5 } });
58
83
  const queries = db.listDocuments.mock.calls[0][2] as string[];
59
84
  expect(queries).toContain('limit(5)');
@@ -62,7 +87,7 @@ describe('Appwrite DataProvider', () => {
62
87
 
63
88
  test('getList passes sorters as queries', async () => {
64
89
  const db = createMockDatabases();
65
- const dp = createAppwriteDataProvider({ databases: db, databaseId: 'main' });
90
+ const dp = await createAppwriteDataProvider({ databases: db, databaseId: 'main' });
66
91
  await dp.getList({ resource: 'posts', sorters: [{ field: 'name', order: 'desc' }] });
67
92
  const queries = db.listDocuments.mock.calls[0][2] as string[];
68
93
  expect(queries.some((q: string) => q.includes('orderDesc'))).toBe(true);
@@ -70,7 +95,7 @@ describe('Appwrite DataProvider', () => {
70
95
 
71
96
  test('getList passes filters as queries', async () => {
72
97
  const db = createMockDatabases();
73
- const dp = createAppwriteDataProvider({ databases: db, databaseId: 'main' });
98
+ const dp = await createAppwriteDataProvider({ databases: db, databaseId: 'main' });
74
99
  await dp.getList({ resource: 'posts', filters: [{ field: 'status', operator: 'eq', value: 'active' }] });
75
100
  const queries = db.listDocuments.mock.calls[0][2] as string[];
76
101
  expect(queries.some((q: string) => q.includes('equal'))).toBe(true);
@@ -78,42 +103,42 @@ describe('Appwrite DataProvider', () => {
78
103
 
79
104
  test('getOne returns single record', async () => {
80
105
  const db = createMockDatabases();
81
- const dp = createAppwriteDataProvider({ databases: db, databaseId: 'main' });
106
+ const dp = await createAppwriteDataProvider({ databases: db, databaseId: 'main' });
82
107
  const result = await dp.getOne({ resource: 'posts', id: '1' });
83
108
  expect(result.data).toHaveProperty('$id', '1');
84
109
  });
85
110
 
86
111
  test('create returns new record', async () => {
87
112
  const db = createMockDatabases();
88
- const dp = createAppwriteDataProvider({ databases: db, databaseId: 'main' });
113
+ const dp = await createAppwriteDataProvider({ databases: db, databaseId: 'main' });
89
114
  const result = await dp.create({ resource: 'posts', variables: { name: 'New Post' } });
90
115
  expect(result.data).toHaveProperty('name', 'New Post');
91
116
  });
92
117
 
93
118
  test('update returns updated record', async () => {
94
119
  const db = createMockDatabases();
95
- const dp = createAppwriteDataProvider({ databases: db, databaseId: 'main' });
120
+ const dp = await createAppwriteDataProvider({ databases: db, databaseId: 'main' });
96
121
  const result = await dp.update({ resource: 'posts', id: '1', variables: { name: 'Updated' } });
97
122
  expect(result.data).toHaveProperty('name', 'Updated');
98
123
  });
99
124
 
100
125
  test('deleteOne calls deleteDocument', async () => {
101
126
  const db = createMockDatabases();
102
- const dp = createAppwriteDataProvider({ databases: db, databaseId: 'main' });
127
+ const dp = await createAppwriteDataProvider({ databases: db, databaseId: 'main' });
103
128
  await dp.deleteOne({ resource: 'posts', id: '1' });
104
129
  expect(db.deleteDocument).toHaveBeenCalledTimes(1);
105
130
  });
106
131
 
107
132
  test('getMany fetches by ids', async () => {
108
133
  const db = createMockDatabases();
109
- const dp = createAppwriteDataProvider({ databases: db, databaseId: 'main' });
134
+ const dp = await createAppwriteDataProvider({ databases: db, databaseId: 'main' });
110
135
  const result = await dp.getMany!({ resource: 'posts', ids: ['1', '2'] });
111
136
  expect(result.data).toHaveLength(2);
112
137
  });
113
138
 
114
139
  test('deleteMany deletes each id', async () => {
115
140
  const db = createMockDatabases();
116
- const dp = createAppwriteDataProvider({ databases: db, databaseId: 'main' });
141
+ const dp = await createAppwriteDataProvider({ databases: db, databaseId: 'main' });
117
142
  await dp.deleteMany!({ resource: 'posts', ids: ['1', '2'] });
118
143
  expect(db.deleteDocument).toHaveBeenCalledTimes(2);
119
144
  });
@@ -4,8 +4,12 @@ import type { AuthProvider, Identity, AuthActionResult, CheckResult } from '@sva
4
4
  interface AppwriteAuthOptions {
5
5
  /** Appwrite Account instance */
6
6
  account: unknown; // Appwrite.Account
7
+ /** Full base URL for recovery link. Required if forgotPassword is used in SSR. */
8
+ recoveryUrl?: string;
7
9
  }
8
10
 
11
+ export type AppwriteProviderOptions = AppwriteAuthOptions;
12
+
9
13
  // Duck-typed Appwrite Account interface
10
14
  interface AppwriteAccount {
11
15
  createEmailPasswordSession: (email: string, password: string) => Promise<Record<string, unknown>>;
@@ -55,8 +59,7 @@ export function createAppwriteAuthProvider(options: AppwriteAuthOptions): AuthPr
55
59
  try {
56
60
  await account.get();
57
61
  return { authenticated: true };
58
- } catch (e) {
59
- console.warn('[svadmin] appwrite check failed:', e);
62
+ } catch {
60
63
  return { authenticated: false, redirectTo: '/login', logout: true };
61
64
  }
62
65
  },
@@ -70,8 +73,7 @@ export function createAppwriteAuthProvider(options: AppwriteAuthOptions): AuthPr
70
73
  email: user.email,
71
74
  avatar: user.prefs?.avatar as string | undefined,
72
75
  };
73
- } catch (e) {
74
- console.warn('[svadmin] appwrite getIdentity failed:', e);
76
+ } catch {
75
77
  return null;
76
78
  }
77
79
  },
@@ -87,7 +89,9 @@ export function createAppwriteAuthProvider(options: AppwriteAuthOptions): AuthPr
87
89
 
88
90
  async forgotPassword({ email }: Record<string, unknown>): Promise<AuthActionResult> {
89
91
  try {
90
- await account.createRecovery(email as string, `${globalThis.location?.origin ?? ''}/update-password`);
92
+ const base = options.recoveryUrl ?? (typeof window !== 'undefined' ? window.location.origin : '');
93
+ if (!base) throw new Error('recoveryUrl is required in options when calling forgotPassword from server');
94
+ await account.createRecovery(email as string, `${base}/update-password`);
91
95
  return { success: true };
92
96
  } catch (err) {
93
97
  return { success: false, error: { message: (err as Error).message } };
@@ -1,138 +1,21 @@
1
- // @svadmin/appwrite — Appwrite DataProvider
2
- // Maps svadmin CRUD operations to Appwrite SDK
1
+ import type { DataProvider } from '@svadmin/core';
2
+ import { createRefineAdapter } from '@svadmin/refine-adapter';
3
3
 
4
- import type {
5
- DataProvider, GetListParams, GetListResult, GetOneParams, GetOneResult,
6
- CreateParams, CreateResult, UpdateParams, UpdateResult, DeleteParams, DeleteResult,
7
- GetManyParams, GetManyResult, DeleteManyParams, DeleteManyResult, BaseRecord,
8
- } from '@svadmin/core';
9
-
10
- interface AppwriteProviderOptions {
11
- /** Appwrite Databases instance */
12
- databases: unknown; // Appwrite.Databases
13
- /** Database ID */
14
- databaseId: string;
15
- }
16
-
17
- // Duck-typed Appwrite SDK interfaces to avoid hard dependency
18
- interface AppwriteDatabases {
19
- listDocuments: (dbId: string, collectionId: string, queries?: string[]) => Promise<{ documents: Record<string, unknown>[]; total: number }>;
20
- getDocument: (dbId: string, collectionId: string, docId: string) => Promise<Record<string, unknown>>;
21
- createDocument: (dbId: string, collectionId: string, docId: string, data: Record<string, unknown>) => Promise<Record<string, unknown>>;
22
- updateDocument: (dbId: string, collectionId: string, docId: string, data: Record<string, unknown>) => Promise<Record<string, unknown>>;
23
- deleteDocument: (dbId: string, collectionId: string, docId: string) => Promise<void>;
24
- }
25
4
 
26
5
  /**
27
- * Map svadmin CrudOperator to Appwrite Query string.
28
- * Uses Appwrite's query builder format: Query.equal('field', value)
6
+ * Creates a appwrite data provider using the official @refinedev/appwrite package.
7
+ * Requires `@refinedev/appwrite` to be installed.
8
+ *
9
+ * @param args Arguments required by @refinedev/appwrite
10
+ * @returns A fully compatible svadmin DataProvider
29
11
  */
30
- function mapFilter(field: string, operator: string, value: unknown): string | null {
31
- switch (operator) {
32
- case 'eq': return `equal("${field}", ${JSON.stringify(value)})`;
33
- case 'ne': return `notEqual("${field}", ${JSON.stringify(value)})`;
34
- case 'lt': return `lessThan("${field}", ${JSON.stringify(value)})`;
35
- case 'gt': return `greaterThan("${field}", ${JSON.stringify(value)})`;
36
- case 'lte': return `lessThanEqual("${field}", ${JSON.stringify(value)})`;
37
- case 'gte': return `greaterThanEqual("${field}", ${JSON.stringify(value)})`;
38
- case 'contains': return `search("${field}", ${JSON.stringify(value)})`;
39
- case 'in': return `equal("${field}", ${JSON.stringify(value)})`;
40
- case 'null': return `isNull("${field}")`;
41
- case 'nnull': return `isNotNull("${field}")`;
42
- case 'between': {
43
- const [min, max] = value as [unknown, unknown];
44
- return `between("${field}", ${JSON.stringify(min)}, ${JSON.stringify(max)})`;
45
- }
46
- default: return null;
47
- }
12
+ export async function createAppwriteDataProvider(...args: any[]): Promise<DataProvider> {
13
+
14
+ // @ts-ignore
15
+ const pkg = await import('@refinedev/appwrite');
16
+ const init = (pkg as any).default || (pkg as any).dataProvider || (pkg as any).DataProvider;
17
+ const refineProvider = init(...args);
18
+ return createRefineAdapter(refineProvider);
48
19
  }
49
20
 
50
- /**
51
- * Create a DataProvider backed by Appwrite.
52
- *
53
- * Usage:
54
- * ```ts
55
- * import { Databases, Client } from 'appwrite';
56
- * import { createAppwriteDataProvider } from '@svadmin/appwrite';
57
- *
58
- * const client = new Client().setEndpoint('https://cloud.appwrite.io/v1').setProject('PROJECT_ID');
59
- * const databases = new Databases(client);
60
- * const dataProvider = createAppwriteDataProvider({ databases, databaseId: 'main' });
61
- * ```
62
- */
63
- export function createAppwriteDataProvider(options: AppwriteProviderOptions): DataProvider {
64
- const { databases, databaseId } = options;
65
- const db = databases as AppwriteDatabases;
66
-
67
- return {
68
- getApiUrl: () => 'appwrite',
69
-
70
- async getList<TData extends BaseRecord = BaseRecord>(params: GetListParams): Promise<GetListResult<TData>> {
71
- const { resource, pagination, sorters, filters } = params;
72
- const queries: string[] = [];
73
-
74
- if (pagination?.current && pagination?.pageSize) {
75
- const offset = (pagination.current - 1) * pagination.pageSize;
76
- queries.push(`limit(${pagination.pageSize})`);
77
- queries.push(`offset(${offset})`);
78
- }
79
-
80
- if (sorters?.length) {
81
- for (const sort of sorters) {
82
- queries.push(sort.order === 'asc'
83
- ? `orderAsc("${sort.field}")`
84
- : `orderDesc("${sort.field}")`
85
- );
86
- }
87
- }
88
-
89
- if (filters?.length) {
90
- for (const filter of filters) {
91
- const q = mapFilter(filter.field, filter.operator, filter.value);
92
- if (q) queries.push(q);
93
- }
94
- }
95
-
96
- const result = await db.listDocuments(databaseId, resource, queries);
97
- return {
98
- data: result.documents as TData[],
99
- total: result.total,
100
- };
101
- },
102
-
103
- async getOne<TData extends BaseRecord = BaseRecord>(params: GetOneParams): Promise<GetOneResult<TData>> {
104
- const doc = await db.getDocument(databaseId, params.resource, String(params.id));
105
- return { data: doc as TData };
106
- },
107
-
108
- async create<TData extends BaseRecord = BaseRecord, TVariables = unknown>(params: CreateParams<TVariables>): Promise<CreateResult<TData>> {
109
- const doc = await db.createDocument(databaseId, params.resource, 'unique()', params.variables as Record<string, unknown>);
110
- return { data: doc as TData };
111
- },
112
-
113
- async update<TData extends BaseRecord = BaseRecord, TVariables = unknown>(params: UpdateParams<TVariables>): Promise<UpdateResult<TData>> {
114
- const doc = await db.updateDocument(databaseId, params.resource, String(params.id), params.variables as Record<string, unknown>);
115
- return { data: doc as TData };
116
- },
117
-
118
- async deleteOne<TData extends BaseRecord = BaseRecord, TVariables = unknown>(params: DeleteParams<TVariables>): Promise<DeleteResult<TData>> {
119
- await db.deleteDocument(databaseId, params.resource, String(params.id));
120
- return { data: { id: params.id } as unknown as TData };
121
- },
122
-
123
- async getMany<TData extends BaseRecord = BaseRecord>(params: GetManyParams): Promise<GetManyResult<TData>> {
124
- const queries = [`equal("$id", ${JSON.stringify(params.ids.map(String))})`];
125
- const result = await db.listDocuments(databaseId, params.resource, queries);
126
- return { data: result.documents as TData[] };
127
- },
128
-
129
- async deleteMany<TData extends BaseRecord = BaseRecord, TVariables = unknown>(params: DeleteManyParams<TVariables>): Promise<DeleteManyResult<TData>> {
130
- const results: unknown[] = [];
131
- for (const id of params.ids) {
132
- await db.deleteDocument(databaseId, params.resource, String(id));
133
- results.push({ id });
134
- }
135
- return { data: results as TData[] };
136
- },
137
- };
138
- }
21
+ export type AppwriteProviderOptions = any;