@dyrected/sdk 0.0.1 → 1.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.
package/dist/index.cjs CHANGED
@@ -31,453 +31,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  DyrectedClient: () => DyrectedClient,
34
+ DyrectedError: () => DyrectedError,
34
35
  createClient: () => createClient
35
36
  });
36
37
  module.exports = __toCommonJS(index_exports);
37
38
  var import_qs = __toESM(require("qs"), 1);
38
39
 
39
- // ../../node_modules/.pnpm/hono@4.12.18/node_modules/hono/dist/request/constants.js
40
- var GET_MATCH_RESULT = /* @__PURE__ */ Symbol();
41
-
42
- // ../../node_modules/.pnpm/hono@4.12.18/node_modules/hono/dist/utils/body.js
43
- var parseBody = async (request, options = /* @__PURE__ */ Object.create(null)) => {
44
- const { all = false, dot = false } = options;
45
- const headers = request instanceof HonoRequest ? request.raw.headers : request.headers;
46
- const contentType = headers.get("Content-Type");
47
- if (contentType?.startsWith("multipart/form-data") || contentType?.startsWith("application/x-www-form-urlencoded")) {
48
- return parseFormData(request, { all, dot });
49
- }
50
- return {};
51
- };
52
- async function parseFormData(request, options) {
53
- const formData = await request.formData();
54
- if (formData) {
55
- return convertFormDataToBodyData(formData, options);
56
- }
57
- return {};
58
- }
59
- function convertFormDataToBodyData(formData, options) {
60
- const form = /* @__PURE__ */ Object.create(null);
61
- formData.forEach((value, key) => {
62
- const shouldParseAllValues = options.all || key.endsWith("[]");
63
- if (!shouldParseAllValues) {
64
- form[key] = value;
65
- } else {
66
- handleParsingAllValues(form, key, value);
67
- }
68
- });
69
- if (options.dot) {
70
- Object.entries(form).forEach(([key, value]) => {
71
- const shouldParseDotValues = key.includes(".");
72
- if (shouldParseDotValues) {
73
- handleParsingNestedValues(form, key, value);
74
- delete form[key];
75
- }
76
- });
77
- }
78
- return form;
79
- }
80
- var handleParsingAllValues = (form, key, value) => {
81
- if (form[key] !== void 0) {
82
- if (Array.isArray(form[key])) {
83
- ;
84
- form[key].push(value);
85
- } else {
86
- form[key] = [form[key], value];
87
- }
88
- } else {
89
- if (!key.endsWith("[]")) {
90
- form[key] = value;
91
- } else {
92
- form[key] = [value];
93
- }
94
- }
95
- };
96
- var handleParsingNestedValues = (form, key, value) => {
97
- if (/(?:^|\.)__proto__\./.test(key)) {
98
- return;
99
- }
100
- let nestedForm = form;
101
- const keys = key.split(".");
102
- keys.forEach((key2, index) => {
103
- if (index === keys.length - 1) {
104
- nestedForm[key2] = value;
105
- } else {
106
- if (!nestedForm[key2] || typeof nestedForm[key2] !== "object" || Array.isArray(nestedForm[key2]) || nestedForm[key2] instanceof File) {
107
- nestedForm[key2] = /* @__PURE__ */ Object.create(null);
108
- }
109
- nestedForm = nestedForm[key2];
110
- }
111
- });
112
- };
113
-
114
- // ../../node_modules/.pnpm/hono@4.12.18/node_modules/hono/dist/utils/url.js
115
- var tryDecode = (str, decoder) => {
116
- try {
117
- return decoder(str);
118
- } catch {
119
- return str.replace(/(?:%[0-9A-Fa-f]{2})+/g, (match2) => {
120
- try {
121
- return decoder(match2);
122
- } catch {
123
- return match2;
124
- }
125
- });
126
- }
127
- };
128
- var _decodeURI = (value) => {
129
- if (!/[%+]/.test(value)) {
130
- return value;
131
- }
132
- if (value.indexOf("+") !== -1) {
133
- value = value.replace(/\+/g, " ");
134
- }
135
- return value.indexOf("%") !== -1 ? tryDecode(value, decodeURIComponent_) : value;
136
- };
137
- var _getQueryParam = (url, key, multiple) => {
138
- let encoded;
139
- if (!multiple && key && !/[%+]/.test(key)) {
140
- let keyIndex2 = url.indexOf("?", 8);
141
- if (keyIndex2 === -1) {
142
- return void 0;
143
- }
144
- if (!url.startsWith(key, keyIndex2 + 1)) {
145
- keyIndex2 = url.indexOf(`&${key}`, keyIndex2 + 1);
146
- }
147
- while (keyIndex2 !== -1) {
148
- const trailingKeyCode = url.charCodeAt(keyIndex2 + key.length + 1);
149
- if (trailingKeyCode === 61) {
150
- const valueIndex = keyIndex2 + key.length + 2;
151
- const endIndex = url.indexOf("&", valueIndex);
152
- return _decodeURI(url.slice(valueIndex, endIndex === -1 ? void 0 : endIndex));
153
- } else if (trailingKeyCode == 38 || isNaN(trailingKeyCode)) {
154
- return "";
155
- }
156
- keyIndex2 = url.indexOf(`&${key}`, keyIndex2 + 1);
157
- }
158
- encoded = /[%+]/.test(url);
159
- if (!encoded) {
160
- return void 0;
161
- }
162
- }
163
- const results = {};
164
- encoded ??= /[%+]/.test(url);
165
- let keyIndex = url.indexOf("?", 8);
166
- while (keyIndex !== -1) {
167
- const nextKeyIndex = url.indexOf("&", keyIndex + 1);
168
- let valueIndex = url.indexOf("=", keyIndex);
169
- if (valueIndex > nextKeyIndex && nextKeyIndex !== -1) {
170
- valueIndex = -1;
171
- }
172
- let name = url.slice(
173
- keyIndex + 1,
174
- valueIndex === -1 ? nextKeyIndex === -1 ? void 0 : nextKeyIndex : valueIndex
175
- );
176
- if (encoded) {
177
- name = _decodeURI(name);
178
- }
179
- keyIndex = nextKeyIndex;
180
- if (name === "") {
181
- continue;
182
- }
183
- let value;
184
- if (valueIndex === -1) {
185
- value = "";
186
- } else {
187
- value = url.slice(valueIndex + 1, nextKeyIndex === -1 ? void 0 : nextKeyIndex);
188
- if (encoded) {
189
- value = _decodeURI(value);
190
- }
191
- }
192
- if (multiple) {
193
- if (!(results[name] && Array.isArray(results[name]))) {
194
- results[name] = [];
195
- }
196
- ;
197
- results[name].push(value);
198
- } else {
199
- results[name] ??= value;
200
- }
201
- }
202
- return key ? results[key] : results;
203
- };
204
- var getQueryParam = _getQueryParam;
205
- var getQueryParams = (url, key) => {
206
- return _getQueryParam(url, key, true);
207
- };
208
- var decodeURIComponent_ = decodeURIComponent;
209
-
210
- // ../../node_modules/.pnpm/hono@4.12.18/node_modules/hono/dist/request.js
211
- var tryDecodeURIComponent = (str) => tryDecode(str, decodeURIComponent_);
212
- var HonoRequest = class {
213
- /**
214
- * `.raw` can get the raw Request object.
215
- *
216
- * @see {@link https://hono.dev/docs/api/request#raw}
217
- *
218
- * @example
219
- * ```ts
220
- * // For Cloudflare Workers
221
- * app.post('/', async (c) => {
222
- * const metadata = c.req.raw.cf?.hostMetadata?
223
- * ...
224
- * })
225
- * ```
226
- */
227
- raw;
228
- #validatedData;
229
- // Short name of validatedData
230
- #matchResult;
231
- routeIndex = 0;
232
- /**
233
- * `.path` can get the pathname of the request.
234
- *
235
- * @see {@link https://hono.dev/docs/api/request#path}
236
- *
237
- * @example
238
- * ```ts
239
- * app.get('/about/me', (c) => {
240
- * const pathname = c.req.path // `/about/me`
241
- * })
242
- * ```
243
- */
244
- path;
245
- bodyCache = {};
246
- constructor(request, path = "/", matchResult = [[]]) {
247
- this.raw = request;
248
- this.path = path;
249
- this.#matchResult = matchResult;
250
- this.#validatedData = {};
251
- }
252
- param(key) {
253
- return key ? this.#getDecodedParam(key) : this.#getAllDecodedParams();
254
- }
255
- #getDecodedParam(key) {
256
- const paramKey = this.#matchResult[0][this.routeIndex][1][key];
257
- const param = this.#getParamValue(paramKey);
258
- return param && /\%/.test(param) ? tryDecodeURIComponent(param) : param;
259
- }
260
- #getAllDecodedParams() {
261
- const decoded = {};
262
- const keys = Object.keys(this.#matchResult[0][this.routeIndex][1]);
263
- for (const key of keys) {
264
- const value = this.#getParamValue(this.#matchResult[0][this.routeIndex][1][key]);
265
- if (value !== void 0) {
266
- decoded[key] = /\%/.test(value) ? tryDecodeURIComponent(value) : value;
267
- }
268
- }
269
- return decoded;
270
- }
271
- #getParamValue(paramKey) {
272
- return this.#matchResult[1] ? this.#matchResult[1][paramKey] : paramKey;
273
- }
274
- query(key) {
275
- return getQueryParam(this.url, key);
276
- }
277
- queries(key) {
278
- return getQueryParams(this.url, key);
279
- }
280
- header(name) {
281
- if (name) {
282
- return this.raw.headers.get(name) ?? void 0;
283
- }
284
- const headerData = {};
285
- this.raw.headers.forEach((value, key) => {
286
- headerData[key] = value;
287
- });
288
- return headerData;
289
- }
290
- async parseBody(options) {
291
- return parseBody(this, options);
292
- }
293
- #cachedBody = (key) => {
294
- const { bodyCache, raw } = this;
295
- const cachedBody = bodyCache[key];
296
- if (cachedBody) {
297
- return cachedBody;
298
- }
299
- const anyCachedKey = Object.keys(bodyCache)[0];
300
- if (anyCachedKey) {
301
- return bodyCache[anyCachedKey].then((body) => {
302
- if (anyCachedKey === "json") {
303
- body = JSON.stringify(body);
304
- }
305
- return new Response(body)[key]();
306
- });
307
- }
308
- return bodyCache[key] = raw[key]();
309
- };
310
- /**
311
- * `.json()` can parse Request body of type `application/json`
312
- *
313
- * @see {@link https://hono.dev/docs/api/request#json}
314
- *
315
- * @example
316
- * ```ts
317
- * app.post('/entry', async (c) => {
318
- * const body = await c.req.json()
319
- * })
320
- * ```
321
- */
322
- json() {
323
- return this.#cachedBody("text").then((text) => JSON.parse(text));
324
- }
325
- /**
326
- * `.text()` can parse Request body of type `text/plain`
327
- *
328
- * @see {@link https://hono.dev/docs/api/request#text}
329
- *
330
- * @example
331
- * ```ts
332
- * app.post('/entry', async (c) => {
333
- * const body = await c.req.text()
334
- * })
335
- * ```
336
- */
337
- text() {
338
- return this.#cachedBody("text");
339
- }
340
- /**
341
- * `.arrayBuffer()` parse Request body as an `ArrayBuffer`
342
- *
343
- * @see {@link https://hono.dev/docs/api/request#arraybuffer}
344
- *
345
- * @example
346
- * ```ts
347
- * app.post('/entry', async (c) => {
348
- * const body = await c.req.arrayBuffer()
349
- * })
350
- * ```
351
- */
352
- arrayBuffer() {
353
- return this.#cachedBody("arrayBuffer");
354
- }
355
- /**
356
- * Parses the request body as a `Blob`.
357
- * @example
358
- * ```ts
359
- * app.post('/entry', async (c) => {
360
- * const body = await c.req.blob();
361
- * });
362
- * ```
363
- * @see https://hono.dev/docs/api/request#blob
364
- */
365
- blob() {
366
- return this.#cachedBody("blob");
367
- }
368
- /**
369
- * Parses the request body as `FormData`.
370
- * @example
371
- * ```ts
372
- * app.post('/entry', async (c) => {
373
- * const body = await c.req.formData();
374
- * });
375
- * ```
376
- * @see https://hono.dev/docs/api/request#formdata
377
- */
378
- formData() {
379
- return this.#cachedBody("formData");
380
- }
381
- /**
382
- * Adds validated data to the request.
383
- *
384
- * @param target - The target of the validation.
385
- * @param data - The validated data to add.
386
- */
387
- addValidatedData(target, data) {
388
- this.#validatedData[target] = data;
389
- }
390
- valid(target) {
391
- return this.#validatedData[target];
392
- }
393
- /**
394
- * `.url()` can get the request url strings.
395
- *
396
- * @see {@link https://hono.dev/docs/api/request#url}
397
- *
398
- * @example
399
- * ```ts
400
- * app.get('/about/me', (c) => {
401
- * const url = c.req.url // `http://localhost:8787/about/me`
402
- * ...
403
- * })
404
- * ```
405
- */
406
- get url() {
407
- return this.raw.url;
408
- }
409
- /**
410
- * `.method()` can get the method name of the request.
411
- *
412
- * @see {@link https://hono.dev/docs/api/request#method}
413
- *
414
- * @example
415
- * ```ts
416
- * app.get('/about/me', (c) => {
417
- * const method = c.req.method // `GET`
418
- * })
419
- * ```
420
- */
421
- get method() {
422
- return this.raw.method;
423
- }
424
- get [GET_MATCH_RESULT]() {
425
- return this.#matchResult;
426
- }
427
- /**
428
- * `.matchedRoutes()` can return a matched route in the handler
429
- *
430
- * @deprecated
431
- *
432
- * Use matchedRoutes helper defined in "hono/route" instead.
433
- *
434
- * @see {@link https://hono.dev/docs/api/request#matchedroutes}
435
- *
436
- * @example
437
- * ```ts
438
- * app.use('*', async function logger(c, next) {
439
- * await next()
440
- * c.req.matchedRoutes.forEach(({ handler, method, path }, i) => {
441
- * const name = handler.name || (handler.length < 2 ? '[handler]' : '[middleware]')
442
- * console.log(
443
- * method,
444
- * ' ',
445
- * path,
446
- * ' '.repeat(Math.max(10 - path.length, 0)),
447
- * name,
448
- * i === c.req.routeIndex ? '<- respond from here' : ''
449
- * )
450
- * })
451
- * })
452
- * ```
453
- */
454
- get matchedRoutes() {
455
- return this.#matchResult[0].map(([[, route]]) => route);
456
- }
457
- /**
458
- * `routePath()` can retrieve the path registered within the handler
459
- *
460
- * @deprecated
461
- *
462
- * Use routePath helper defined in "hono/route" instead.
463
- *
464
- * @see {@link https://hono.dev/docs/api/request#routepath}
465
- *
466
- * @example
467
- * ```ts
468
- * app.get('/posts/:id', (c) => {
469
- * return c.json({ path: c.req.routePath })
470
- * })
471
- * ```
472
- */
473
- get routePath() {
474
- return this.#matchResult[0].map(([[, route]]) => route)[this.routeIndex].path;
475
- }
476
- };
477
-
478
- // ../../node_modules/.pnpm/hono@4.12.18/node_modules/hono/dist/router/reg-exp-router/node.js
479
- var regExpMetaChars = new Set(".\\+*[^]$()");
480
-
481
40
  // src/query-builder.ts
482
41
  var QueryBuilder = class {
483
42
  constructor(collection, executor) {
@@ -507,6 +66,10 @@ var QueryBuilder = class {
507
66
  this.args.depth = depth;
508
67
  return this;
509
68
  }
69
+ seed(data) {
70
+ this.args.initialData = data;
71
+ return this;
72
+ }
510
73
  async exec() {
511
74
  return this.executor(this.collection, this.args);
512
75
  }
@@ -517,28 +80,82 @@ var QueryBuilder = class {
517
80
  };
518
81
 
519
82
  // src/index.ts
83
+ var DyrectedError = class extends Error {
84
+ statusCode;
85
+ errors;
86
+ constructor(message, statusCode, errors = []) {
87
+ super(message);
88
+ this.name = "DyrectedError";
89
+ this.statusCode = statusCode;
90
+ this.errors = errors;
91
+ }
92
+ };
520
93
  var DyrectedClient = class {
521
94
  baseUrl;
522
95
  headers;
523
96
  fetch;
97
+ defaultDepth;
524
98
  constructor(config) {
525
99
  this.baseUrl = config.baseUrl.replace(/\/$/, "");
526
100
  this.fetch = config.fetch || fetch;
101
+ this.defaultDepth = config.defaultDepth ?? 1;
527
102
  this.headers = {
528
103
  "Content-Type": "application/json",
529
- ...config.apiKey ? { "X-API-Key": config.apiKey } : {},
104
+ ...config.apiKey ? { "x-api-key": config.apiKey } : {},
105
+ ...config.siteId ? { "x-site-id": config.siteId } : {},
530
106
  ...config.headers
531
107
  };
532
108
  }
109
+ /**
110
+ * Update the Authorization header with a Bearer token.
111
+ * Call this after a successful login.
112
+ */
113
+ setToken(token) {
114
+ this.headers["Authorization"] = `Bearer ${token}`;
115
+ }
116
+ /**
117
+ * Remove the Authorization header.
118
+ * Call this after logout.
119
+ */
120
+ clearToken() {
121
+ delete this.headers["Authorization"];
122
+ }
533
123
  getBaseUrl() {
534
124
  return this.baseUrl;
535
125
  }
536
126
  async getSchemas() {
537
127
  return this.request("/api/schemas");
538
128
  }
129
+ /**
130
+ * Fetch draft data for a specific preview token.
131
+ * Used in "token" preview mode.
132
+ */
133
+ async getPreviewData(token) {
134
+ return this.request(`/api/preview-data?token=${token}`);
135
+ }
539
136
  async find(collection, args = {}) {
540
- const query = import_qs.default.stringify(args, { addQueryPrefix: true });
137
+ const { initialData, ...queryArgs } = args;
138
+ const normalizedArgs = { ...queryArgs };
139
+ if (normalizedArgs.where && typeof normalizedArgs.where === "object") {
140
+ normalizedArgs.where = JSON.stringify(normalizedArgs.where);
141
+ }
142
+ const query = import_qs.default.stringify(normalizedArgs, { addQueryPrefix: true });
541
143
  const res = await this.request(`/api/collections/${collection}${query}`);
144
+ if (res.docs.length === 0 && initialData && initialData.length > 0) {
145
+ this.request(`/api/collections/${collection}/seed`, {
146
+ method: "POST",
147
+ body: JSON.stringify({ data: initialData })
148
+ }).catch((err) => console.error(`[dyrected/sdk] Failed to auto-seed collection "${collection}":`, err));
149
+ return {
150
+ docs: initialData,
151
+ total: initialData.length,
152
+ limit: initialData.length,
153
+ page: 1,
154
+ totalPages: 1,
155
+ hasNextPage: false,
156
+ hasPrevPage: false
157
+ };
158
+ }
542
159
  return res;
543
160
  }
544
161
  /**
@@ -554,18 +171,80 @@ var DyrectedClient = class {
554
171
  if (args.limit) qb.limit(args.limit);
555
172
  if (args.page) qb.page(args.page);
556
173
  if (args.depth) qb.depth(args.depth);
174
+ if (args.initialData) qb.seed(args.initialData);
557
175
  }
558
176
  return qb;
559
177
  },
560
178
  findOne: (id, args = {}) => this.findOne(slug, id, args),
561
179
  create: (data) => this.create(slug, data),
562
180
  update: (id, data) => this.update(slug, id, data),
563
- delete: (id) => this.delete(slug, id)
181
+ delete: (id) => this.delete(slug, id),
182
+ /**
183
+ * Upload a file to this collection. Sends as multipart/form-data.
184
+ * @param file - A File or Blob (browser) or Buffer with filename/mimeType (Node.js)
185
+ * @param data - Additional metadata fields to save alongside the file (e.g. alt, caption)
186
+ */
187
+ upload: (file, data) => this._upload(slug, file, data),
188
+ // ---- Auth methods (only meaningful when the collection has auth: true) ----
189
+ /**
190
+ * Log in with email + password. Returns a JWT token and the user document.
191
+ * Call `client.setToken(token)` afterwards to authenticate subsequent requests.
192
+ */
193
+ login: (email, password) => this.request(`/api/collections/${slug}/login`, {
194
+ method: "POST",
195
+ body: JSON.stringify({ email, password })
196
+ }),
197
+ /** Log out. Stateless — token must be discarded client-side; call client.clearToken() too. */
198
+ logout: () => this.request(`/api/collections/${slug}/logout`, { method: "POST" }),
199
+ /** Return the currently authenticated user (requires a token via setToken). */
200
+ me: () => this.request(`/api/collections/${slug}/me`),
201
+ /** Issue a fresh token for the currently authenticated user. */
202
+ refreshToken: () => this.request(`/api/collections/${slug}/refresh-token`, { method: "POST" }),
203
+ /** Check if this auth collection has any users (initialized). */
204
+ isInitialized: () => this.request(`/api/collections/${slug}/init`),
205
+ /** Register the very first user in an empty auth collection. */
206
+ registerFirstUser: (data) => this.request(`/api/collections/${slug}/first-user`, {
207
+ method: "POST",
208
+ body: JSON.stringify(data)
209
+ }),
210
+ /** Send an invitation email to a new user. Requires authentication. */
211
+ invite: (email) => this.request(`/api/collections/${slug}/invite`, {
212
+ method: "POST",
213
+ body: JSON.stringify({ email })
214
+ }),
215
+ /** Accept an invitation and create an account. Returns token + user. */
216
+ acceptInvite: (token, password, extraFields) => this.request(`/api/collections/${slug}/accept-invite`, {
217
+ method: "POST",
218
+ body: JSON.stringify({ token, password, ...extraFields })
219
+ })
220
+ };
221
+ }
222
+ /**
223
+ * Access a global by its slug with a fluent builder.
224
+ * @example client.global('site-settings').get()
225
+ * @example client.global('site-settings').update({ siteName: 'My Site' })
226
+ */
227
+ global(slug) {
228
+ return {
229
+ get: (args = {}) => this.getGlobal(slug, args),
230
+ update: (data) => this.updateGlobal(slug, data)
564
231
  };
565
232
  }
566
233
  async findOne(collection, id, args = {}) {
567
- const query = import_qs.default.stringify(args, { addQueryPrefix: true });
568
- return this.request(`/api/collections/${collection}/${id}${query}`);
234
+ const { initialData, ...queryArgs } = args;
235
+ const query = import_qs.default.stringify(queryArgs, { addQueryPrefix: true });
236
+ try {
237
+ return await this.request(`/api/collections/${collection}/${id}${query}`);
238
+ } catch (err) {
239
+ if (err instanceof DyrectedError && err.statusCode === 404 && initialData) {
240
+ this.request(`/api/collections/${collection}/seed`, {
241
+ method: "POST",
242
+ body: JSON.stringify({ data: [{ id, ...initialData }] })
243
+ }).catch((err2) => console.error(`[dyrected/sdk] Failed to auto-seed document "${id}" in collection "${collection}":`, err2));
244
+ return initialData;
245
+ }
246
+ throw err;
247
+ }
569
248
  }
570
249
  async create(collection, data) {
571
250
  return this.request(`/api/collections/${collection}`, {
@@ -585,8 +264,28 @@ var DyrectedClient = class {
585
264
  });
586
265
  }
587
266
  async getGlobal(slug, args = {}) {
588
- const query = import_qs.default.stringify(args, { addQueryPrefix: true });
589
- return this.request(`/api/globals/${slug}${query}`);
267
+ const { initialData, ...queryArgs } = args;
268
+ const query = import_qs.default.stringify(queryArgs, { addQueryPrefix: true });
269
+ try {
270
+ const res = await this.request(`/api/globals/${slug}${query}`);
271
+ if ((!res || Object.keys(res).length === 0) && initialData) {
272
+ this.request(`/api/globals/${slug}/seed`, {
273
+ method: "POST",
274
+ body: JSON.stringify({ data: initialData })
275
+ }).catch((err) => console.error(`[dyrected/sdk] Failed to auto-seed global "${slug}":`, err));
276
+ return initialData;
277
+ }
278
+ return res;
279
+ } catch (err) {
280
+ if (err instanceof DyrectedError && err.statusCode === 404 && initialData) {
281
+ this.request(`/api/globals/${slug}/seed`, {
282
+ method: "POST",
283
+ body: JSON.stringify({ data: initialData })
284
+ }).catch((err2) => console.error(`[dyrected/sdk] Failed to auto-seed global "${slug}":`, err2));
285
+ return initialData;
286
+ }
287
+ throw err;
288
+ }
590
289
  }
591
290
  async updateGlobal(slug, data) {
592
291
  return this.request(`/api/globals/${slug}`, {
@@ -594,37 +293,59 @@ var DyrectedClient = class {
594
293
  body: JSON.stringify(data)
595
294
  });
596
295
  }
597
- async listMedia(args = {}) {
598
- const query = import_qs.default.stringify(args, { addQueryPrefix: true });
599
- return this.request(`/api/media${query}`);
296
+ async listMedia(args = {}, collection = "media") {
297
+ return this.find(collection, args);
298
+ }
299
+ /** @deprecated Use client.collection('media').upload(file, data) instead */
300
+ async uploadMedia(file, collection = "media") {
301
+ return this._upload(collection, file);
600
302
  }
601
- async uploadMedia(file) {
303
+ /**
304
+ * Internal upload implementation shared by collection().upload() and uploadMedia().
305
+ */
306
+ async _upload(collection, file, data) {
602
307
  const formData = new FormData();
603
308
  formData.append("file", file);
309
+ if (data) {
310
+ for (const [key, value] of Object.entries(data)) {
311
+ formData.append(key, value);
312
+ }
313
+ }
604
314
  const { "Content-Type": _, ...headers } = this.headers;
605
- return this.request("/api/media", {
315
+ return this.request(`/api/collections/${collection}`, {
606
316
  method: "POST",
607
- headers,
317
+ headers: {
318
+ ...headers,
319
+ "Content-Type": void 0
320
+ },
608
321
  body: formData
609
322
  });
610
323
  }
611
- async deleteMedia(id) {
612
- return this.request(`/api/media/${id}`, {
613
- method: "DELETE"
614
- });
324
+ async deleteMedia(id, collection = "media") {
325
+ return this.delete(collection, id);
615
326
  }
616
327
  async request(path, init) {
617
328
  const url = `${this.baseUrl}${path}`;
329
+ const allHeaders = {
330
+ ...this.headers,
331
+ ...init?.headers
332
+ };
333
+ Object.keys(allHeaders).forEach((key) => {
334
+ if (allHeaders[key] === void 0) {
335
+ delete allHeaders[key];
336
+ }
337
+ });
618
338
  const res = await this.fetch(url, {
619
339
  ...init,
620
- headers: {
621
- ...this.headers,
622
- ...init?.headers
623
- }
340
+ headers: allHeaders
624
341
  });
625
342
  if (!res.ok) {
626
- const error = await res.json().catch(() => ({ message: "Unknown error" }));
627
- throw new Error(error.message || `Request failed with status ${res.status}`);
343
+ const body = await res.json().catch(() => ({ message: "Unknown error" }));
344
+ throw new DyrectedError(
345
+ body.message || `Request failed with status ${res.status}`,
346
+ res.status,
347
+ body.errors || []
348
+ );
628
349
  }
629
350
  return res.json();
630
351
  }
@@ -635,5 +356,6 @@ function createClient(config) {
635
356
  // Annotate the CommonJS export names for ESM import in node:
636
357
  0 && (module.exports = {
637
358
  DyrectedClient,
359
+ DyrectedError,
638
360
  createClient
639
361
  });