@tanglemedia/svelte-starter-directus-api 2.0.0 → 2.0.1

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.
@@ -1,14 +1,24 @@
1
- import { type DirectusClient, type RestClient } from '@directus/sdk';
1
+ import { type AllCollections, type DirectusClient, type RegularCollections, type RestClient } from '@directus/sdk';
2
2
  import { ApiAdapterAbstract, type AnyObject, type ApiAdapterRequestConfig, type ApiCreateManyQuery, type ApiDeleteManyQuery, type ApiId, type ApiResponse, type ApiUpdateManyQuery, type BaseApiMethods } from '@tanglemedia/svelte-starter-core';
3
3
  import type { DirectusConfig, SchemaShape } from '../types/adapter.types';
4
4
  export type AdapterClient<Schema extends SchemaShape = SchemaShape> = DirectusClient<Schema> & RestClient<Schema>;
5
- export declare class DirectusApiAdapter<Schema extends SchemaShape = SchemaShape, Path extends string = keyof SchemaShape> extends ApiAdapterAbstract<DirectusConfig, ApiAdapterRequestConfig, Path, AdapterClient> {
5
+ export declare class DirectusApiAdapter<Schema extends SchemaShape = SchemaShape, Path extends RegularCollections<Schema> = RegularCollections<Schema>> extends ApiAdapterAbstract<DirectusConfig, ApiAdapterRequestConfig, Path, AdapterClient> {
6
6
  protected config: DirectusConfig;
7
7
  private readonly directus;
8
8
  constructor(config: DirectusConfig, directus: AdapterClient<Schema>);
9
9
  getAdapterClient(): AdapterClient<Schema> | null;
10
- normalizeMeta(payload: AnyObject): AnyObject;
11
- transformResponse<T, M extends object = AnyObject>(res: T, status?: number): Promise<ApiResponse<T, Response, M>>;
10
+ normalizeMeta(response: AnyObject): {
11
+ total?: undefined;
12
+ displayed?: undefined;
13
+ } | {
14
+ total: number;
15
+ displayed: number;
16
+ };
17
+ includeTotals(collection: AllCollections<Schema>, filters?: Record<string, unknown>): Promise<number | null>;
18
+ transformResponse<T, M extends object = AnyObject>(res: {
19
+ data: T;
20
+ meta?: AnyObject;
21
+ }, status?: number): Promise<ApiResponse<T, Response, M>>;
12
22
  getConfig(configuration?: unknown): DirectusConfig;
13
23
  request<T>(method: BaseApiMethods, url: string, query?: Record<string, unknown>): Promise<T>;
14
24
  find<T>(collection: Path, query?: Record<string, unknown>): Promise<ApiResponse<T>>;
@@ -11,17 +11,36 @@ export class DirectusApiAdapter extends ApiAdapterAbstract {
11
11
  getAdapterClient() {
12
12
  return this.directus;
13
13
  }
14
- normalizeMeta(payload) {
14
+ normalizeMeta(response) {
15
+ const payload = response.data, meta = response.meta;
16
+ if (!meta) {
17
+ return {};
18
+ }
19
+ if (typeof meta.total === 'undefined') {
20
+ return {};
21
+ }
22
+ const displayed = Array.isArray(payload) ? payload.length : 0;
23
+ const total = Number(meta.total);
15
24
  // todo: Transform or attempt to get meta information
16
25
  // consider pulling count based on existing filters in order to determine pagination
17
- return payload;
26
+ return { total, displayed };
27
+ }
28
+ async includeTotals(collection, filters) {
29
+ if (true !== this.config.configuration.includeTotals) {
30
+ return null;
31
+ }
32
+ const data = await this.directus.request(aggregate(collection, {
33
+ aggregate: { count: '*' },
34
+ filters: filters || {}
35
+ }));
36
+ if (data && data.length === 1) {
37
+ return Number(data[0].count);
38
+ }
39
+ return 0;
18
40
  }
19
41
  async transformResponse(res, status = 200) {
20
- // const payload = (await res.json()) as T;
21
42
  return {
22
- body: await this.envelopeResponse(res.data || res
23
- // this.normalizeMeta(payload as AnyObject) as M
24
- ),
43
+ body: await this.envelopeResponse(res.data, (res.meta ? this.normalizeMeta(res) : {})),
25
44
  status: status,
26
45
  statusText: 'OK',
27
46
  headers: {},
@@ -33,7 +52,7 @@ export class DirectusApiAdapter extends ApiAdapterAbstract {
33
52
  // Add the 'configuration' property to the returned object
34
53
  const config = {
35
54
  ...this.config,
36
- configuration: configuration ?? {} // Use the provided configuration or an empty object if not provided
55
+ configuration: configuration ?? this.config.configuration ?? {} // Use the provided configuration or an empty object if not provided
37
56
  };
38
57
  return Object.assign(client, config);
39
58
  }
@@ -55,37 +74,42 @@ export class DirectusApiAdapter extends ApiAdapterAbstract {
55
74
  }
56
75
  async find(collection, query) {
57
76
  const response = await this.directus.request(readItems(collection, query));
58
- return this.transformResponse(response);
77
+ const total = await this.includeTotals(collection, query.filter);
78
+ if (null === total) {
79
+ return this.transformResponse({ data: response });
80
+ }
81
+ // console.log('find meta', response, total)
82
+ return this.transformResponse({ data: response, meta: { total } });
59
83
  }
60
84
  async findOne(collection, key, query) {
61
- const response = await this.directus.request(readItem(collection, key, query));
62
- return this.transformResponse(response);
85
+ const data = await this.directus.request(readItem(collection, key, query));
86
+ return this.transformResponse({ data });
63
87
  }
64
88
  async aggregate(collection, query) {
65
- const response = await this.directus.request(aggregate(collection, {
89
+ const data = await this.directus.request(aggregate(collection, {
66
90
  aggregate: {
67
91
  count: 'id'
68
92
  },
69
93
  query
70
94
  }));
71
- return this.transformResponse(response);
95
+ return this.transformResponse({ data });
72
96
  }
73
97
  async patch(collection, key, query) {
74
- const response = await this.directus.request(updateItem(collection, key, query));
75
- return this.transformResponse(response);
98
+ const data = await this.directus.request(updateItem(collection, key, query));
99
+ return this.transformResponse({ data });
76
100
  }
77
101
  async post(collection, data, query) {
78
102
  const response = await this.directus.request(createItem(collection, data, query));
79
- return this.transformResponse(response, 201);
103
+ return this.transformResponse({ data: response }, 201);
80
104
  }
81
105
  async delete(collection, key) {
82
- const response = await this.directus.request(deleteItem(collection, key));
83
- return this.transformResponse(response, 202);
106
+ const data = await this.directus.request(deleteItem(collection, key));
107
+ return this.transformResponse({ data }, 202);
84
108
  }
85
109
  async put(collection, key, payload) {
86
110
  if (key) {
87
- const response = await this.directus.request(updateItem(collection, key, payload));
88
- return this.transformResponse(response, 201);
111
+ const data = await this.directus.request(updateItem(collection, key, payload));
112
+ return this.transformResponse({ data }, 201);
89
113
  }
90
114
  else {
91
115
  console.error(`Error updating all ${collection}: no key specified`);
@@ -95,21 +119,21 @@ export class DirectusApiAdapter extends ApiAdapterAbstract {
95
119
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
96
120
  async upload(path = '', data) {
97
121
  const response = await this.directus.request(uploadFiles(data));
98
- return this.transformResponse(response, 201);
122
+ return this.transformResponse({ data: response }, 201);
99
123
  }
100
124
  async createMany(path, body) {
101
- const response = await this.directus.request(createItems(path, body.data));
102
- return this.transformResponse(response, 200);
125
+ const data = await this.directus.request(createItems(path, body.data));
126
+ return this.transformResponse({ data }, 200);
103
127
  }
104
128
  async updateMany(path, body) {
105
129
  if (!body.ids) {
106
130
  throw Error('You must provide an array of keys to update');
107
131
  }
108
- const response = await this.directus.request(updateItems(path, body.ids || [], body.data));
109
- return this.transformResponse(response, 200);
132
+ const data = await this.directus.request(updateItems(path, body.ids || [], body.data));
133
+ return this.transformResponse({ data }, 200);
110
134
  }
111
135
  async deleteMany(path, body) {
112
- const response = await this.directus.request(deleteItems(path, body.ids));
113
- return this.transformResponse(response, 201);
136
+ const data = await this.directus.request(deleteItems(path, body.ids));
137
+ return this.transformResponse({ data }, 201);
114
138
  }
115
139
  }
@@ -18,6 +18,7 @@ export type DirectusApiAdapterOptions = {
18
18
  rest?: {
19
19
  credentials?: RequestCredentials;
20
20
  };
21
+ includeTotals?: boolean;
21
22
  };
22
23
  export type DirectusConfig = BaseApiAdapterConfig<DirectusApiAdapterOptions>;
23
24
  export type SchemaShape = Record<string, object>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanglemedia/svelte-starter-directus-api",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "main": "src/index.ts",
5
5
  "types": "src/index.ts",
6
6
  "description": "directus API wrapper for all the directus sdk functionality",
@@ -22,6 +22,7 @@
22
22
  }
23
23
  },
24
24
  "devDependencies": {
25
+ "@directus/types": "^12.0.0",
25
26
  "@sveltejs/adapter-auto": "^3.2.4",
26
27
  "@sveltejs/package": "^2.3.4",
27
28
  "@sveltejs/vite-plugin-svelte": "^3.1.2",
@@ -35,7 +36,7 @@
35
36
  "vite": "^5.4.2",
36
37
  "vitest": "^2.0.5",
37
38
  "@internal/eslint-config": "0.0.0",
38
- "@tanglemedia/svelte-starter-core": "0.2.0"
39
+ "@tanglemedia/svelte-starter-core": "0.2.1"
39
40
  },
40
41
  "dependencies": {
41
42
  "@directus/sdk": "^17.0.0",
@@ -45,7 +46,7 @@
45
46
  },
46
47
  "peerDependencies": {
47
48
  "@sveltejs/kit": ">=2 <3",
48
- "@tanglemedia/svelte-starter-core": ">=0.2.0",
49
+ "@tanglemedia/svelte-starter-core": ">=0.2.1",
49
50
  "svelte": ">=4 <5"
50
51
  },
51
52
  "scripts": {
@@ -9,7 +9,9 @@ import {
9
9
  updateItem,
10
10
  updateItems,
11
11
  uploadFiles,
12
+ type AllCollections,
12
13
  type DirectusClient,
14
+ type RegularCollections,
13
15
  type RestClient
14
16
  } from '@directus/sdk';
15
17
  import {
@@ -30,7 +32,7 @@ export type AdapterClient<Schema extends SchemaShape = SchemaShape> = DirectusCl
30
32
 
31
33
  export class DirectusApiAdapter<
32
34
  Schema extends SchemaShape = SchemaShape,
33
- Path extends string = keyof SchemaShape
35
+ Path extends RegularCollections<Schema> = RegularCollections<Schema>
34
36
  > extends ApiAdapterAbstract<DirectusConfig, ApiAdapterRequestConfig, Path, AdapterClient> {
35
37
  constructor(
36
38
  protected config: DirectusConfig,
@@ -43,22 +45,51 @@ export class DirectusApiAdapter<
43
45
  return this.directus;
44
46
  }
45
47
 
46
- normalizeMeta(payload: AnyObject) {
48
+ normalizeMeta(response: AnyObject) {
49
+ const payload = response.data, meta = response.meta as Record<string, unknown>;
50
+
51
+ if (!meta) {
52
+ return {};
53
+ }
54
+
55
+ if (typeof meta.total === 'undefined') {
56
+ return {};
57
+ }
58
+
59
+ const displayed = Array.isArray(payload) ? payload.length : 0;
60
+
61
+ const total = Number(meta.total);
62
+
47
63
  // todo: Transform or attempt to get meta information
48
64
  // consider pulling count based on existing filters in order to determine pagination
49
- return payload;
65
+ return { total, displayed };
66
+ }
67
+
68
+ async includeTotals(collection: AllCollections<Schema>, filters?: Record<string, unknown>) {
69
+ if (true !== this.config.configuration.includeTotals) {
70
+ return null;
71
+ }
72
+
73
+ const data = await this.directus.request(aggregate(collection, {
74
+ aggregate: { count: '*' },
75
+ filters: filters || {}
76
+ }));
77
+
78
+ if (data && data.length === 1) {
79
+ return Number(data[0].count);
80
+ }
81
+
82
+ return 0;
50
83
  }
51
84
 
52
85
  async transformResponse<T, M extends object = AnyObject>(
53
- res: T,
86
+ res: {data: T, meta?: AnyObject},
54
87
  status: number = 200
55
88
  ): Promise<ApiResponse<T, Response, M>> {
56
- // const payload = (await res.json()) as T;
57
-
58
89
  return {
59
90
  body: await this.envelopeResponse<T, M>(
60
- res.data || res
61
- // this.normalizeMeta(payload as AnyObject) as M
91
+ res.data,
92
+ (res.meta ? this.normalizeMeta(res as AnyObject) : {}) as M
62
93
  ),
63
94
  status: status,
64
95
  statusText: 'OK',
@@ -73,7 +104,7 @@ export class DirectusApiAdapter<
73
104
  // Add the 'configuration' property to the returned object
74
105
  const config: DirectusConfig = {
75
106
  ...this.config,
76
- configuration: configuration ?? {} // Use the provided configuration or an empty object if not provided
107
+ configuration: configuration ?? this.config.configuration ?? {} // Use the provided configuration or an empty object if not provided
77
108
  };
78
109
 
79
110
  return Object.assign(client, config);
@@ -101,7 +132,13 @@ export class DirectusApiAdapter<
101
132
 
102
133
  public async find<T>(collection: Path, query?: Record<string, unknown>): Promise<ApiResponse<T>> {
103
134
  const response = await this.directus.request<T>(readItems(collection, query));
104
- return this.transformResponse(response);
135
+ const total = await this.includeTotals(collection, query.filter);
136
+ if (null === total) {
137
+ return this.transformResponse({ data: response });
138
+ }
139
+ // console.log('find meta', response, total)
140
+
141
+ return this.transformResponse({ data: response, meta: { total } });
105
142
  }
106
143
 
107
144
  public async findOne<T>(
@@ -109,15 +146,15 @@ export class DirectusApiAdapter<
109
146
  key?: ApiId,
110
147
  query?: Record<string, unknown>
111
148
  ): Promise<ApiResponse<T>> {
112
- const response = await this.directus.request<T>(readItem(collection, key, query));
113
- return this.transformResponse(response);
149
+ const data = await this.directus.request<T>(readItem(collection, key, query));
150
+ return this.transformResponse({ data });
114
151
  }
115
152
 
116
153
  public async aggregate<T>(
117
154
  collection: Path,
118
155
  query?: Record<string, unknown>
119
156
  ): Promise<ApiResponse<T>> {
120
- const response = await this.directus.request<T>(
157
+ const data = await this.directus.request<T>(
121
158
  aggregate(collection, {
122
159
  aggregate: {
123
160
  count: 'id'
@@ -125,7 +162,7 @@ export class DirectusApiAdapter<
125
162
  query
126
163
  })
127
164
  );
128
- return this.transformResponse(response);
165
+ return this.transformResponse({ data });
129
166
  }
130
167
 
131
168
  public async patch<T>(
@@ -133,8 +170,8 @@ export class DirectusApiAdapter<
133
170
  key: ApiId,
134
171
  query?: Record<string, unknown>
135
172
  ): Promise<ApiResponse<T>> {
136
- const response = await this.directus.request<T>(updateItem(collection, key, query));
137
- return this.transformResponse(response);
173
+ const data = await this.directus.request<T>(updateItem(collection, key, query));
174
+ return this.transformResponse({ data });
138
175
  }
139
176
 
140
177
  public async post<T>(
@@ -143,18 +180,18 @@ export class DirectusApiAdapter<
143
180
  query?: Record<string, unknown>
144
181
  ): Promise<ApiResponse<T>> {
145
182
  const response = await this.directus.request<T>(createItem(collection, data, query));
146
- return this.transformResponse(response, 201);
183
+ return this.transformResponse({ data: response }, 201);
147
184
  }
148
185
 
149
186
  public async delete<T>(collection: Path, key?: ApiId): Promise<ApiResponse<T>> {
150
- const response = await this.directus.request<T>(deleteItem(collection, key));
151
- return this.transformResponse(response, 202);
187
+ const data = await this.directus.request<T>(deleteItem(collection, key));
188
+ return this.transformResponse({ data }, 202);
152
189
  }
153
190
 
154
191
  public async put<T>(collection: Path, key?: ApiId, payload?: AnyObject): Promise<ApiResponse<T>> {
155
192
  if (key) {
156
- const response = await this.directus.request<T>(updateItem(collection, key, payload));
157
- return this.transformResponse(response, 201);
193
+ const data = await this.directus.request<T>(updateItem(collection, key, payload));
194
+ return this.transformResponse({ data }, 201);
158
195
  } else {
159
196
  console.error(`Error updating all ${collection}: no key specified`);
160
197
  throw new Error('No key specified');
@@ -164,24 +201,24 @@ export class DirectusApiAdapter<
164
201
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
165
202
  public async upload<T>(path: string = '', data: FormData): Promise<ApiResponse<T>> {
166
203
  const response = await this.directus.request<T>(uploadFiles(data));
167
- return this.transformResponse(response, 201);
204
+ return this.transformResponse({ data: response }, 201);
168
205
  }
169
206
 
170
207
  async createMany<T, R = T>(path: Path, body: ApiCreateManyQuery<T>) {
171
- const response = await this.directus.request<T>(createItems(path, body.data));
172
- return this.transformResponse<R[]>(response, 200);
208
+ const data = await this.directus.request<T>(createItems(path, body.data));
209
+ return this.transformResponse<R[]>({ data }, 200);
173
210
  }
174
211
 
175
212
  async updateMany<T, R = T>(path: Path, body: ApiUpdateManyQuery<T>) {
176
213
  if (!body.ids) {
177
214
  throw Error('You must provide an array of keys to update');
178
215
  }
179
- const response = await this.directus.request<T>(updateItems(path, body.ids || [], body.data));
180
- return this.transformResponse<R[]>(response, 200);
216
+ const data = await this.directus.request<T>(updateItems(path, body.ids || [], body.data));
217
+ return this.transformResponse<R[]>({ data }, 200);
181
218
  }
182
219
 
183
220
  async deleteMany<R = null>(path: Path, body: ApiDeleteManyQuery<ApiId>) {
184
- const response = await this.directus.request(deleteItems(path, body.ids));
185
- return this.transformResponse<R>(response as R, 201);
221
+ const data = await this.directus.request(deleteItems(path, body.ids));
222
+ return this.transformResponse<R>({ data } as R, 201);
186
223
  }
187
224
  }
@@ -23,6 +23,8 @@ export type DirectusApiAdapterOptions = {
23
23
  rest?: {
24
24
  credentials?: RequestCredentials;
25
25
  };
26
+
27
+ includeTotals?: boolean;
26
28
  };
27
29
 
28
30
  export type DirectusConfig = BaseApiAdapterConfig<DirectusApiAdapterOptions>;