@svadmin/sanity 0.9.20 → 0.10.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/sanity",
3
- "version": "0.9.20",
3
+ "version": "0.10.6",
4
4
  "description": "Sanity DataProvider for svadmin",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -20,12 +20,12 @@
20
20
  }
21
21
  },
22
22
  "peerDependencies": {
23
- "@svadmin/core": "^0.50.0"
23
+ "@svadmin/core": "^0.52.0"
24
24
  },
25
25
  "license": "MIT",
26
26
  "repository": {
27
27
  "type": "git",
28
- "url": "https://github.com/vibeunion/svadmin.git",
28
+ "url": "git+https://github.com/vibeunion/svadmin.git",
29
29
  "directory": "packages/sanity"
30
30
  },
31
31
  "peerDependenciesMeta": {
@@ -1,18 +1,38 @@
1
+ import { definedOptions } from '@svadmin/core/options';
2
+ import { decodeBaseRecord, decodeOneResult, decodeManyResult, withValidatedResponses, type DataTransport } from '@svadmin/core/schema';
1
3
  import type {
2
- DataProvider, GetListParams, GetListResult, GetOneParams, GetOneResult,
3
- CreateParams, CreateResult, UpdateParams, UpdateResult, DeleteParams, DeleteResult,
4
- GetManyParams, GetManyResult, CustomParams, CustomResult, CrudOperator, FieldFilter,
4
+ DataProvider, CrudOperator, FieldFilter,
5
5
  Filter, Sort, BaseRecord
6
6
  } from '@svadmin/core';
7
7
 
8
- interface SanityDoc {
9
- _id: string;
10
- _type: string;
11
- [key: string]: unknown;
8
+ function decodeDocument(value: unknown): BaseRecord {
9
+ const document = decodeBaseRecord(value);
10
+ const id = document['_id'];
11
+ if (typeof id !== 'string' || id.length === 0 || typeof document['_type'] !== 'string') {
12
+ throw new TypeError('Invalid Sanity document identity');
13
+ }
14
+ return { ...document, id };
12
15
  }
13
16
 
14
- interface SanityMutationResult {
15
- results?: Array<{ id: string }>;
17
+ function inputRecord(value: unknown): Record<string, unknown> {
18
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
19
+ throw new TypeError('Sanity variables must be an object');
20
+ }
21
+ return decodeBaseRecord(value);
22
+ }
23
+
24
+ function mutationReceipt(value: unknown, expectedId?: string | number): { id: string } {
25
+ return decodeOneResult({ data: value }, raw => {
26
+ const result = decodeBaseRecord(raw);
27
+ const results = result['results'];
28
+ if (!Array.isArray(results) || results.length !== 1) throw new TypeError('Missing Sanity mutation receipt');
29
+ const receipt = decodeBaseRecord(results[0]);
30
+ const id = receipt['id'];
31
+ if (typeof id !== 'string' || id.length === 0 || (expectedId !== undefined && id !== String(expectedId))) {
32
+ throw new TypeError('Invalid Sanity mutation identity');
33
+ }
34
+ return { id };
35
+ }, true).data;
16
36
  }
17
37
 
18
38
  const DEFAULT_JSON_HEADERS: Record<string, string> = { 'Content-Type': 'application/json' };
@@ -201,28 +221,28 @@ function buildCustomUrl(
201
221
  return parsed.toString();
202
222
  }
203
223
 
204
- async function parseResponse<TData>(response: Response): Promise<TData> {
224
+ async function parseResponse(response: Response): Promise<unknown> {
205
225
  if (response.status === 204 || response.status === 205) {
206
- return undefined as unknown as TData;
226
+ return undefined;
207
227
  }
208
228
 
209
229
  const contentLength = response.headers?.get('content-length');
210
230
  if (contentLength?.trim() === '0') {
211
- return undefined as unknown as TData;
231
+ return undefined;
212
232
  }
213
233
 
214
234
  const body = await response.text();
215
235
  if (!body || body.trim() === '') {
216
- return undefined as unknown as TData;
236
+ return undefined;
217
237
  }
218
238
 
219
- return JSON.parse(body) as TData;
239
+ return JSON.parse(body);
220
240
  }
221
241
 
222
242
  export function createSanityDataProvider(projectId: string, dataset: string, token?: string, apiVersion = '2023-05-03'): DataProvider {
223
243
  const apiUrl = `https://${projectId}.api.sanity.io/v${apiVersion}`;
224
244
  const providerHeaders: Record<string, string> = { ...DEFAULT_JSON_HEADERS };
225
- if (token) providerHeaders.Authorization = `Bearer ${token}`;
245
+ if (token) providerHeaders['Authorization'] = `Bearer ${token}`;
226
246
 
227
247
  async function query(groq: string, params: Record<string, unknown> = {}): Promise<unknown> {
228
248
  const url = new URL(`${apiUrl}/data/query/${dataset}`);
@@ -230,22 +250,22 @@ export function createSanityDataProvider(projectId: string, dataset: string, tok
230
250
  for (const [k, v] of Object.entries(params)) url.searchParams.set(`$${k}`, JSON.stringify(v));
231
251
  const res = await fetch(url.toString(), { headers: providerHeaders });
232
252
  if (!res.ok) throw new Error(`Sanity error: ${res.status}`);
233
- const data = await res.json();
234
- return data.result;
253
+ const data = decodeBaseRecord(await parseResponse(res));
254
+ return data['result'];
235
255
  }
236
256
 
237
- async function mutate(mutations: Record<string, unknown>[]): Promise<SanityMutationResult> {
257
+ async function mutate(mutations: Record<string, unknown>[]): Promise<unknown> {
238
258
  const res = await fetch(`${apiUrl}/data/mutate/${dataset}`, {
239
259
  method: 'POST', headers: providerHeaders, body: JSON.stringify({ mutations }),
240
260
  });
241
261
  if (!res.ok) throw new Error(`Sanity mutation error: ${res.status}`);
242
- return res.json();
262
+ return parseResponse(res);
243
263
  }
244
264
 
245
- return {
265
+ const transport: DataTransport = {
246
266
  getApiUrl: () => apiUrl,
247
267
 
248
- async getList<T extends BaseRecord = BaseRecord>({ resource, pagination, sorters, filters }: GetListParams): Promise<GetListResult<T>> {
268
+ async getList({ resource, pagination, sorters, filters }) {
249
269
  const { current = 1, pageSize = 10 } = pagination ?? {};
250
270
  const start = (current - 1) * pageSize;
251
271
  const end = start + pageSize;
@@ -259,50 +279,53 @@ export function createSanityDataProvider(projectId: string, dataset: string, tok
259
279
  query(`*[${where}]${orderStr}[${start}...${end}]`, params),
260
280
  query(`count(*[${where}])`, params),
261
281
  ]);
262
- return { data: ((data as SanityDoc[]) ?? []).map((d) => ({ ...d, id: d._id })) as unknown as unknown as T[], total: (total as number) ?? 0 };
282
+ return { data: decodeManyResult({ data }, decodeDocument).data, total };
263
283
  },
264
284
 
265
- async getOne<T extends BaseRecord = BaseRecord>({ resource, id }: GetOneParams): Promise<GetOneResult<T>> {
285
+ async getOne({ resource, id }) {
266
286
  const raw = await query('*[_type == $resource && _id == $id][0]', { resource, id });
267
- const data = raw as SanityDoc;
268
- return { data: { ...data, id: data._id } as unknown as unknown as T };
287
+ return decodeOneResult({ data: raw }, decodeDocument);
269
288
  },
270
289
 
271
- async create<T extends BaseRecord = BaseRecord>({ resource, variables }: CreateParams): Promise<CreateResult<T>> {
272
- const doc = { _type: resource, ...(variables as Record<string, unknown>) };
290
+ async create({ resource, variables }) {
291
+ const input = inputRecord(variables);
292
+ const doc = { ...input, _type: resource };
273
293
  const result = await mutate([{ create: doc }]);
274
- const id = result.results?.[0]?.id;
275
- return { data: { ...(variables as Record<string, unknown>), id, _id: id } as unknown as unknown as T };
294
+ const { id } = mutationReceipt(result);
295
+ return { data: { ...input, id, _id: id } };
276
296
  },
277
297
 
278
- async update<T extends BaseRecord = BaseRecord>({ id, variables }: UpdateParams): Promise<UpdateResult<T>> {
279
- await mutate([{ patch: { id, set: variables } }]);
280
- return { data: { ...(variables as Record<string, unknown>), id, _id: id } as unknown as unknown as T };
298
+ async update({ id, variables }) {
299
+ const input = inputRecord(variables);
300
+ const result = await mutate([{ patch: { id, set: input } }]);
301
+ const receipt = mutationReceipt(result, id);
302
+ return { data: { ...input, id: receipt.id, _id: receipt.id } };
281
303
  },
282
304
 
283
- async deleteOne<T extends BaseRecord = BaseRecord>({ id }: DeleteParams): Promise<DeleteResult<T>> {
284
- await mutate([{ delete: { id } }]);
285
- return { data: { id } as unknown as unknown as T };
305
+ async deleteOne({ id }) {
306
+ const result = await mutate([{ delete: { id } }]);
307
+ return { data: mutationReceipt(result, id) };
286
308
  },
287
309
 
288
- async getMany<T extends BaseRecord = BaseRecord>({ resource, ids }: GetManyParams): Promise<GetManyResult<T>> {
310
+ async getMany({ resource, ids }) {
289
311
  const data = await query('*[_type == $resource && _id in $ids]', { resource, ids });
290
- return { data: ((data as SanityDoc[]) ?? []).map((d) => ({ ...d, id: d._id })) as unknown as unknown as T[] };
312
+ return decodeManyResult({ data }, decodeDocument);
291
313
  },
292
314
 
293
- async custom<T = unknown>({ url, method, payload, query: customQuery, headers, sorters, filters }: CustomParams): Promise<CustomResult<T>> {
315
+ async custom({ url, method, payload, query: customQuery, headers, sorters, filters }) {
294
316
  const requestUrl = buildCustomUrl(url, apiUrl, customQuery, sorters, filters);
295
317
  const requestHeaders = mergeHeaders(
296
318
  isSameOrigin(apiUrl, requestUrl) ? providerHeaders : DEFAULT_JSON_HEADERS,
297
319
  headers,
298
320
  );
299
- const res = await fetch(requestUrl, {
321
+ const res = await fetch(requestUrl, definedOptions({
300
322
  method: method.toUpperCase(),
301
323
  headers: requestHeaders,
302
324
  body: payload === undefined ? undefined : JSON.stringify(payload),
303
- });
325
+ }));
304
326
  if (!res.ok) throw new Error(`Custom request failed: ${res.status}`);
305
- return { data: await parseResponse<T>(res) };
327
+ return { data: await parseResponse(res) };
306
328
  },
307
329
  };
330
+ return withValidatedResponses(transport);
308
331
  }