@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 +5 -3
- package/src/appwrite.test.ts +35 -10
- package/src/auth-provider.ts +9 -5
- package/src/data-provider.ts +15 -132
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svadmin/appwrite",
|
|
3
|
-
"version": "0.8.
|
|
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.
|
|
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",
|
package/src/appwrite.test.ts
CHANGED
|
@@ -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
|
});
|
package/src/auth-provider.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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 } };
|
package/src/data-provider.ts
CHANGED
|
@@ -1,138 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
-
*
|
|
28
|
-
*
|
|
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
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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;
|