@tanglemedia/svelte-starter-directus-api 7.0.0 → 9.0.0-next.0

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.
@@ -10,9 +10,7 @@ import {
10
10
  updateItems,
11
11
  uploadFiles,
12
12
  type AllCollections,
13
- type DirectusClient,
14
- type RegularCollections,
15
- type RestClient
13
+ type RegularCollections
16
14
  } from '@directus/sdk';
17
15
  import {
18
16
  ApiAdapterAbstract,
@@ -24,15 +22,19 @@ import {
24
22
  type ApiDeleteManyQuery,
25
23
  type ApiFindQuery,
26
24
  type ApiId,
25
+ type ApiPatchQuery,
26
+ type ApiPostQuery,
27
+ type ApiPutQuery,
27
28
  type ApiResponse,
28
29
  type ApiUpdateManyQuery,
29
30
  type BaseApiMethods,
30
31
  type HandleEvent
31
32
  } from '@tanglemedia/svelte-starter-core';
32
- import type { DirectusConfig, SchemaShape } from '../types/adapter.types';
33
+ import type { DirectusAdapterClient, DirectusConfig, SchemaShape } from '../types/adapter.types';
33
34
 
34
- export type AdapterClient<Schema extends SchemaShape = SchemaShape> = DirectusClient<Schema> &
35
- RestClient<Schema>;
35
+ type PathKey<Schema extends SchemaShape> = Extract<RegularCollections<Schema>, string>;
36
+
37
+ export type AdapterClient<Schema extends SchemaShape = SchemaShape> = DirectusAdapterClient<Schema>;
36
38
 
37
39
  const allowedQueryKeys = [
38
40
  'fields',
@@ -49,17 +51,15 @@ const allowedQueryKeys = [
49
51
  'aggregate',
50
52
  'deep',
51
53
  'alias'
52
- ];
54
+ ] as const;
55
+
56
+ type Q = Record<string, unknown>;
53
57
 
54
- /**
55
- * TODO: Various types fixes. The current adapter doesn't correctly follow the types defined under the ApiAdapterAbstract.
56
- * See the fetch adapter under the core
57
- */
58
58
  export class DirectusApiAdapter<
59
59
  Schema extends SchemaShape = SchemaShape,
60
- Path extends RegularCollections<Schema> = RegularCollections<Schema>
60
+ Path extends PathKey<Schema> = PathKey<Schema>
61
61
  >
62
- extends ApiAdapterAbstract<DirectusConfig, ApiAdapterRequestConfig, Path, AdapterClient>
62
+ extends ApiAdapterAbstract<DirectusConfig, ApiAdapterRequestConfig, Path, AdapterClient<Schema>>
63
63
  implements ApiAdapterHandle
64
64
  {
65
65
  constructor(
@@ -70,8 +70,7 @@ export class DirectusApiAdapter<
70
70
  }
71
71
 
72
72
  async handle(event: HandleEvent) {
73
- const f = event.fetch;
74
- this.directus.globals.fetch = f;
73
+ this.directus.globals.fetch = event.fetch;
75
74
  }
76
75
 
77
76
  getAdapterClient(): AdapterClient<Schema> | null {
@@ -82,51 +81,43 @@ export class DirectusApiAdapter<
82
81
  const payload = response.data,
83
82
  meta = response.meta as Record<string, unknown>;
84
83
 
85
- if (!meta) {
86
- return {};
87
- }
88
-
89
- if (typeof meta.total === 'undefined') {
84
+ if (!meta || typeof meta.total === 'undefined') {
90
85
  return {};
91
86
  }
92
87
 
93
88
  const displayed = Array.isArray(payload) ? payload.length : 0;
94
-
95
89
  const total = Number(meta.total);
96
90
 
97
- // todo: Transform or attempt to get meta information
98
- // consider pulling count based on existing filters in order to determine pagination
99
91
  return { total, displayed };
100
92
  }
101
93
 
102
- async includeTotals(collection: AllCollections<Schema>, query?: ApiFindQuery<any>) {
94
+ async includeTotals(collection: Path, query?: ApiFindQuery<any>) {
103
95
  if (true !== this.config.configuration.includeTotals) {
104
96
  return null;
105
97
  }
106
98
 
107
99
  const { filter, search } = query || {};
108
-
109
100
  // default to count. Note that there is a bug in directus where
110
101
  // the wrong counts are being returned if there are nested filters.
111
102
  // A quick fix is to set totalAggregate to countDistinct
112
103
  const k = this.config.configuration.totalAggregate || 'count';
113
104
  const f = this.config.configuration.totalField || 'id';
114
105
 
115
- const data = await this.directus.request(
116
- aggregate(collection, {
106
+ const data = await this.directus.request<Array<Record<string, unknown>>>(
107
+ aggregate(collection as unknown as AllCollections<Schema>, {
117
108
  aggregate: { [k]: f },
118
109
  query: {
119
110
  ...(filter ? { filter } : {}),
120
111
  ...(search ? { search } : {})
121
112
  }
122
- })
113
+ } as never)
123
114
  );
124
115
 
125
116
  let total = 0;
126
117
 
127
118
  if (data && data.length === 1) {
128
119
  const c = data[0][k];
129
- total = c && typeof c === 'object' ? Number(c[f]) : Number(c);
120
+ total = c && typeof c === 'object' ? Number((c as Record<string, unknown>)[f]) : Number(c);
130
121
  }
131
122
 
132
123
  return isNaN(total) ? 0 : total;
@@ -135,39 +126,27 @@ export class DirectusApiAdapter<
135
126
  async transformResponse<T, M extends object = AnyObject>(
136
127
  res: { data: T; meta?: AnyObject },
137
128
  status: number = 200
138
- ): Promise<ApiResponse<T, Response, M>> {
129
+ ): Promise<ApiResponse<T, null, M>> {
139
130
  return {
140
- body: await this.envelopeResponse<T, M>(
131
+ body: (await this.envelopeResponse<T, M>(
141
132
  res.data,
142
133
  (res.meta ? this.normalizeMeta(res as AnyObject) : {}) as M
143
- ),
144
- status: status,
134
+ )) as ApiResponse<T, null, M>['body'],
135
+ status,
145
136
  statusText: 'OK',
146
137
  headers: {},
147
138
  adapterResponse: null
148
139
  };
149
140
  }
150
141
 
151
- public getConfig(configuration?: unknown): DirectusConfig {
152
- const client = this.directus;
153
-
154
- // Add the 'configuration' property to the returned object
155
- const config: DirectusConfig = {
156
- ...this.config,
157
- configuration: configuration ?? this.config.configuration ?? {} // Use the provided configuration or an empty object if not provided
158
- };
159
-
160
- return Object.assign(client, config);
161
- }
162
-
163
- normalizeQuery(config: ApiAdapterRequestConfig = {}, merge?: Record<string, unknown>) {
164
- const q = { ...(config?.params || {}) };
165
- const c = { ...config } as Record<string, unknown>;
166
- // legacy support, this pulls the directus specific keys from the config
167
- const r = allowedQueryKeys.reduce<Record<string, unknown>>(
142
+ normalizeQuery(config: ApiAdapterRequestConfig = {}, merge?: Q) {
143
+ const q = { ...((config?.params as Q | undefined) || {}) };
144
+ const c = { ...config } as Q;
145
+ const r = allowedQueryKeys.reduce<Q>(
168
146
  (acc, k) => (typeof c[k] !== 'undefined' ? ((acc[k] = c[k]), acc) : acc),
169
147
  {}
170
148
  );
149
+
171
150
  return {
172
151
  ...q,
173
152
  ...r,
@@ -175,158 +154,241 @@ export class DirectusApiAdapter<
175
154
  };
176
155
  }
177
156
 
178
- public async request<T>(
157
+ getId(data: object, config?: ApiAdapterRequestConfig): ApiId | null {
158
+ const d = data as Record<string, unknown>;
159
+ const q = config?.params as Q | undefined;
160
+
161
+ if (typeof d.id === 'string' || typeof d.id === 'number') {
162
+ return d.id;
163
+ }
164
+
165
+ if (q && (typeof q.id === 'string' || typeof q.id === 'number')) {
166
+ return q.id;
167
+ }
168
+
169
+ return null;
170
+ }
171
+
172
+ stripId(config?: ApiAdapterRequestConfig) {
173
+ const q = config?.params as Q | undefined;
174
+
175
+ if (!q || typeof q.id === 'undefined') {
176
+ return config;
177
+ }
178
+
179
+ const { id: _id, ...params } = q;
180
+
181
+ return {
182
+ ...config,
183
+ params
184
+ };
185
+ }
186
+
187
+ public async request<T, R = T>(
179
188
  method: BaseApiMethods,
180
- url: string,
189
+ url: Path,
181
190
  query?: ApiAdapterRequestConfig
182
- ): Promise<T> {
191
+ ): Promise<ApiResponse<R>> {
183
192
  try {
184
- const response = await this.directus.request<T>(() => {
185
- const params = JSON.stringify(query);
186
- return {
187
- path: `${url}?${params}`,
188
- method: method
189
- };
190
- });
191
- return response;
193
+ const { body, headers } = query || {};
194
+ const params = this.normalizeQuery(query);
195
+
196
+ const data = await this.directus.request<R>(
197
+ (() =>
198
+ ({
199
+ path: url,
200
+ method,
201
+ headers,
202
+ params,
203
+ ...(typeof body === 'undefined'
204
+ ? {}
205
+ : {
206
+ body:
207
+ body instanceof FormData || typeof body === 'string'
208
+ ? body
209
+ : JSON.stringify(body)
210
+ })
211
+ })) as never
212
+ );
213
+
214
+ return this.transformResponse({ data }, method === 'POST' ? 201 : method === 'DELETE' ? 202 : 200);
192
215
  } catch (error) {
193
216
  console.error(`Error request:`, error);
194
217
  throw error;
195
218
  }
196
219
  }
197
220
 
198
- public async find<T, Q = T, R = T>(
221
+ public async find<T, F = T, R = T>(
199
222
  collection: Path,
200
- query: ApiFindQuery<Q>,
223
+ query: ApiFindQuery<F>,
201
224
  config?: ApiAdapterRequestConfig
202
225
  ): Promise<ApiResponse<R[]>> {
203
- query = this.normalizeQuery(config, query);
204
- const response = (await this.directus.request<T>(readItems(collection, query))) as R[];
226
+ const q = this.normalizeQuery(config, query as Q);
227
+ const data = await this.directus.request<R[]>(
228
+ readItems(collection as unknown as RegularCollections<Schema>, q as never)
229
+ );
205
230
  const total = await this.includeTotals(collection, query);
231
+
206
232
  if (null === total) {
207
- return this.transformResponse({ data: response });
233
+ return this.transformResponse({ data });
208
234
  }
209
- // console.log('find meta', response, total)
210
235
 
211
- return this.transformResponse({ data: response, meta: { total } });
236
+ return this.transformResponse({ data, meta: { total } });
212
237
  }
213
238
 
214
- public async findOne<T>(
239
+ public async findOne<T, R = T>(
215
240
  collection: Path,
216
- key?: ApiId,
241
+ key: ApiId,
217
242
  query?: ApiAdapterRequestConfig
218
- ): Promise<ApiResponse<T>> {
243
+ ): Promise<ApiResponse<R>> {
219
244
  const q = this.normalizeQuery(query);
220
- const data = await this.directus.request<T>(readItem(collection, key, query));
245
+ const data = await this.directus.request<R>(
246
+ readItem(collection as unknown as RegularCollections<Schema>, key, q as never)
247
+ );
221
248
  return this.transformResponse({ data });
222
249
  }
223
250
 
224
- public async aggregate<T, Q = T, R = T>(
251
+ public async aggregate<T, F = T, R = T>(
225
252
  collection: Path,
226
- query: ApiAggregateQuery<Q>,
253
+ query: ApiAggregateQuery<F>,
227
254
  config?: ApiAdapterRequestConfig
228
255
  ): Promise<ApiResponse<R>> {
229
- const { aggregate: aggregate_, ...rest } = this.normalizeQuery(config, query);
230
-
231
- const data = await this.directus.request<T>(
232
- aggregate(collection, {
233
- aggregate: aggregate_
234
- ? aggregate_
235
- : {
236
- count: 'id'
237
- },
256
+ const { aggregate: a, ...rest } = this.normalizeQuery(config, query as Q);
257
+
258
+ const data = await this.directus.request<R>(
259
+ aggregate(collection as unknown as AllCollections<Schema>, {
260
+ aggregate: a ? a : { count: 'id' },
238
261
  query: rest
239
- })
262
+ } as never)
240
263
  );
241
264
 
242
265
  return this.transformResponse({ data });
243
266
  }
244
267
 
245
- public async patch<T>(
268
+ public async patch<T extends object, R = T>(
246
269
  collection: Path,
247
270
  key: ApiId,
248
- query?: Record<string, unknown>,
271
+ data: ApiPatchQuery<T>,
249
272
  config?: ApiAdapterRequestConfig
250
- ): Promise<ApiResponse<T>> {
251
- const data = await this.directus.request<T>(updateItem(collection, key, query));
252
- return this.transformResponse({ data });
273
+ ): Promise<ApiResponse<R>> {
274
+ const q = this.normalizeQuery(config);
275
+ const res = await this.directus.request<R>(
276
+ updateItem(collection as never, key, data as never, q as never)
277
+ );
278
+ return this.transformResponse({ data: res });
253
279
  }
254
280
 
255
- public async post<T>(
281
+ public async post<T extends object, R = T>(
256
282
  collection: Path,
257
- data: AnyObject,
258
- query?: Record<string, unknown>,
283
+ data: ApiPostQuery<T>,
259
284
  config?: ApiAdapterRequestConfig
260
- ): Promise<ApiResponse<T>> {
261
- const response = await this.directus.request<T>(createItem(collection, data, query));
262
- return this.transformResponse({ data: response }, 201);
285
+ ): Promise<ApiResponse<R>> {
286
+ const q = this.normalizeQuery(config);
287
+ const res = await this.directus.request<R>(
288
+ createItem(collection as never, data as never, q as never)
289
+ );
290
+ return this.transformResponse({ data: res }, 201);
263
291
  }
264
292
 
265
- public async delete<T>(
293
+ public async delete<T, R = T>(
266
294
  collection: Path,
267
- key?: ApiId,
295
+ key: ApiId,
268
296
  config?: ApiAdapterRequestConfig
269
- ): Promise<ApiResponse<T>> {
270
- const data = await this.directus.request<T>(deleteItem(collection, key));
297
+ ): Promise<ApiResponse<R>> {
298
+ void config;
299
+ const data = await this.directus.request<R>(deleteItem(collection as never, key));
271
300
  return this.transformResponse({ data }, 202);
272
301
  }
273
302
 
274
303
  public async put<T extends object, R = T>(
275
304
  collection: Path,
276
- key?: ApiId,
277
- payload?: AnyObject,
278
- query?: ApiAdapterRequestConfig
305
+ data: ApiPutQuery<T>,
306
+ config?: ApiAdapterRequestConfig
307
+ ): Promise<ApiResponse<R>>;
308
+ public async put<T extends object, R = T>(
309
+ collection: Path,
310
+ key: ApiId,
311
+ data?: AnyObject,
312
+ config?: ApiAdapterRequestConfig
313
+ ): Promise<ApiResponse<R>>;
314
+ public async put<T extends object, R = T>(
315
+ collection: Path,
316
+ keyOrData: ApiId | ApiPutQuery<T>,
317
+ dataOrConfig?: AnyObject | ApiAdapterRequestConfig,
318
+ config?: ApiAdapterRequestConfig
279
319
  ): Promise<ApiResponse<R>> {
280
- if (key) {
281
- const data = (await this.directus.request<T>(
282
- updateItem(collection, key, payload, query)
283
- )) as R;
284
- return this.transformResponse({ data }, 201);
285
- } else {
286
- console.error(`Error updating all ${collection}: no key specified`);
320
+ const key =
321
+ typeof keyOrData === 'string' || typeof keyOrData === 'number'
322
+ ? keyOrData
323
+ : this.getId(keyOrData, dataOrConfig as ApiAdapterRequestConfig | undefined);
324
+ const data =
325
+ typeof keyOrData === 'string' || typeof keyOrData === 'number'
326
+ ? (dataOrConfig as AnyObject | undefined)
327
+ : keyOrData;
328
+ const q =
329
+ typeof keyOrData === 'string' || typeof keyOrData === 'number'
330
+ ? config
331
+ : (dataOrConfig as ApiAdapterRequestConfig | undefined);
332
+
333
+ if (!key) {
287
334
  throw new Error('No key specified');
288
335
  }
336
+
337
+ const res = await this.directus.request<R>(
338
+ updateItem(collection as never, key, data as never, this.normalizeQuery(this.stripId(q)) as never)
339
+ );
340
+
341
+ return this.transformResponse({ data: res }, 201);
289
342
  }
290
343
 
291
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
292
- public async upload<T>(
293
- path: string = '',
344
+ public async upload<T, R = T>(
345
+ path: Path,
294
346
  data: FormData,
295
- config?: ApiAdapterRequestConfig
296
- ): Promise<ApiResponse<T>> {
297
- const response = await this.directus.request<T>(uploadFiles(data));
298
- return this.transformResponse({ data: response }, 201);
347
+ config?: ApiAdapterRequestConfig & { method?: BaseApiMethods }
348
+ ): Promise<ApiResponse<R>> {
349
+ void path;
350
+ void config;
351
+ const res = await this.directus.request<R>(uploadFiles(data));
352
+ return this.transformResponse({ data: res }, 201);
299
353
  }
300
354
 
301
355
  async createMany<T, R = T>(
302
356
  path: Path,
303
357
  body: ApiCreateManyQuery<T>,
304
358
  config?: ApiAdapterRequestConfig
305
- ) {
306
- const data = await this.directus.request<T>(createItems(path, body.data, body?.query));
307
- return this.transformResponse<R[]>({ data }, 200);
359
+ ): Promise<ApiResponse<R[]>> {
360
+ const q = this.normalizeQuery(config, ((body as Q).query as Q | undefined) || {});
361
+ const data = await this.directus.request<R[]>(
362
+ createItems(path as never, body.data as never, q as never)
363
+ );
364
+ return this.transformResponse({ data }, 200);
308
365
  }
309
366
 
310
367
  async updateMany<T, R = T>(
311
368
  path: Path,
312
369
  body: ApiUpdateManyQuery<T>,
313
370
  config?: ApiAdapterRequestConfig
314
- ) {
371
+ ): Promise<ApiResponse<R[]>> {
315
372
  if (!body.ids) {
316
373
  throw Error('You must provide an array of keys to update');
317
374
  }
318
- const data = await this.directus.request<T>(
319
- updateItems(path, body.ids || [], body.data, body?.query)
375
+
376
+ const q = this.normalizeQuery(config, ((body as Q).query as Q | undefined) || {});
377
+ const ids = body.ids as string[] | number[];
378
+ const data = await this.directus.request<R[]>(
379
+ updateItems(path as never, ids, body.data as never, q as never)
320
380
  );
321
- return this.transformResponse<R[]>({ data }, 200);
381
+ return this.transformResponse({ data }, 200);
322
382
  }
323
383
 
324
- async deleteMany<R = null>(
384
+ async deleteMany<R>(
325
385
  path: Path,
326
386
  body: ApiDeleteManyQuery<ApiId>,
327
387
  config?: ApiAdapterRequestConfig
328
- ) {
329
- const data = await this.directus.request(deleteItems(path, body.ids));
330
- return this.transformResponse<R>({ data } as R, 201);
388
+ ): Promise<ApiResponse<R[]>> {
389
+ void config;
390
+ const ids = body.ids as string[] | number[];
391
+ const data = await this.directus.request<R[]>(deleteItems(path as never, ids));
392
+ return this.transformResponse({ data }, 201);
331
393
  }
332
394
  }
@@ -1,17 +1,25 @@
1
1
  import type {
2
2
  AdapterProviderRegister,
3
+ ApiAdapterRequestConfig,
3
4
  ApiAdapterInterface,
4
5
  ApiAdapterProviderInterface
5
6
  } from '@tanglemedia/svelte-starter-core';
6
7
  import { DirectusApiAdapter } from '../adapter/api-adapter';
7
- import type { DirectusConfig, SchemaShape } from '../types/adapter.types';
8
+ import type { DirectusAdapterClient, DirectusConfig, SchemaShape } from '../types/adapter.types';
8
9
  import { createDirectusClientFactory } from './directus.factory';
9
10
 
10
11
  export class DirectusApiProvider implements ApiAdapterProviderInterface<DirectusConfig> {
11
12
  async loadAdapter(
12
13
  key?: string,
13
14
  config?: DirectusConfig
14
- ): Promise<ApiAdapterInterface<DirectusConfig>> {
15
+ ): Promise<
16
+ ApiAdapterInterface<
17
+ DirectusConfig,
18
+ ApiAdapterRequestConfig,
19
+ string,
20
+ DirectusAdapterClient<SchemaShape>
21
+ >
22
+ > {
15
23
  if (!config) {
16
24
  throw new Error('Missing adapter configuration');
17
25
  }
@@ -21,7 +29,7 @@ export class DirectusApiProvider implements ApiAdapterProviderInterface<Directus
21
29
  return new DirectusApiAdapter(config, client);
22
30
  }
23
31
 
24
- createDirectusClient<T extends object>(config: DirectusConfig) {
32
+ createDirectusClient<T extends SchemaShape>(config: DirectusConfig): DirectusAdapterClient<T> {
25
33
  // todo: Determine if we can overwrite the fetch globals during runtime
26
34
  return createDirectusClientFactory<T>(config);
27
35
  }