@dyrected/sdk 2.5.60 → 2.5.62

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
@@ -22,7 +22,9 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  DyrectedClient: () => DyrectedClient,
24
24
  DyrectedError: () => DyrectedError,
25
- createClient: () => createClient
25
+ PREVIEW_TOKEN_PARAM: () => PREVIEW_TOKEN_PARAM,
26
+ createClient: () => createClient,
27
+ getPreviewToken: () => getPreviewToken
26
28
  });
27
29
  module.exports = __toCommonJS(index_exports);
28
30
 
@@ -163,6 +165,13 @@ var DyrectedClient = class {
163
165
  getBaseUrl() {
164
166
  return this.baseUrl;
165
167
  }
168
+ /**
169
+ * Inject the client's configured `defaultDepth` when a read did not specify
170
+ * its own `depth`. A per-call `depth` (including `0`) always wins.
171
+ */
172
+ applyDefaultDepth(args) {
173
+ return args.depth === void 0 ? { ...args, depth: this.defaultDepth } : args;
174
+ }
166
175
  async getSchemas() {
167
176
  return this.request("/api/schemas");
168
177
  }
@@ -177,27 +186,48 @@ var DyrectedClient = class {
177
186
  }
178
187
  async getPreference(key, options) {
179
188
  const scopeParam = options?.scope ? `?scope=${options.scope}` : "";
180
- return this.request(`/api/preferences/${encodeURIComponent(key)}${scopeParam}`);
189
+ return this.request(
190
+ `/api/preferences/${encodeURIComponent(key)}${scopeParam}`
191
+ );
181
192
  }
182
193
  async setPreference(key, value, options) {
183
194
  const scopeParam = options?.scope ? `?scope=${options.scope}` : "";
184
- return this.request(`/api/preferences/${encodeURIComponent(key)}${scopeParam}`, {
185
- method: "PUT",
186
- body: JSON.stringify({ value })
187
- });
195
+ return this.request(
196
+ `/api/preferences/${encodeURIComponent(key)}${scopeParam}`,
197
+ {
198
+ method: "PUT",
199
+ body: JSON.stringify({ value })
200
+ }
201
+ );
188
202
  }
189
203
  async deletePreference(key, options) {
190
204
  const scopeParam = options?.scope ? `?scope=${options.scope}` : "";
191
- return this.request(`/api/preferences/${encodeURIComponent(key)}${scopeParam}`, {
192
- method: "DELETE"
193
- });
205
+ return this.request(
206
+ `/api/preferences/${encodeURIComponent(key)}${scopeParam}`,
207
+ {
208
+ method: "DELETE"
209
+ }
210
+ );
194
211
  }
195
212
  /**
196
213
  * Fetch draft data for a specific preview token.
197
214
  * Used in "token" preview mode.
198
215
  */
199
216
  async getPreviewData(token) {
200
- return this.request(`/api/preview-data?token=${token}`);
217
+ return this.request(`/api/preview-data?token=${encodeURIComponent(token)}`);
218
+ }
219
+ /**
220
+ * Mint a short-lived preview token that carries the current (unsaved) draft
221
+ * data. Used by the Admin in `previewMode: "token"` to hand draft content to
222
+ * a server-rendered frontend that cannot receive it over `postMessage`.
223
+ *
224
+ * Requires an authenticated request (the Admin is logged in).
225
+ */
226
+ async createPreviewToken(input) {
227
+ return this.request(`/api/preview-token`, {
228
+ method: "POST",
229
+ body: JSON.stringify(input)
230
+ });
201
231
  }
202
232
  async find(collection, args = {}) {
203
233
  const { initialData, ...queryArgs } = args;
@@ -205,13 +235,22 @@ var DyrectedClient = class {
205
235
  if (queryArgs.where && typeof queryArgs.where === "object") {
206
236
  normalizedArgs.where = JSON.stringify(queryArgs.where);
207
237
  }
208
- const query = stringifyQuery(normalizedArgs, { addQueryPrefix: true });
209
- const res = await this.request(`/api/collections/${collection}${query}`);
238
+ const query = stringifyQuery(this.applyDefaultDepth(normalizedArgs), {
239
+ addQueryPrefix: true
240
+ });
241
+ const res = await this.request(
242
+ `/api/collections/${collection}${query}`
243
+ );
210
244
  if (res.docs.length === 0 && initialData && initialData.length > 0) {
211
245
  this.request(`/api/collections/${collection}/seed`, {
212
246
  method: "POST",
213
247
  body: JSON.stringify({ data: initialData })
214
- }).catch((err) => console.error(`[dyrected/sdk] Failed to auto-seed collection "${collection}":`, err));
248
+ }).catch(
249
+ (err) => console.error(
250
+ `[dyrected/sdk] Failed to auto-seed collection "${collection}":`,
251
+ err
252
+ )
253
+ );
215
254
  return {
216
255
  docs: initialData,
217
256
  total: initialData.length,
@@ -235,11 +274,12 @@ var DyrectedClient = class {
235
274
  (collectionName, queryArgs) => this.find(collectionName, queryArgs)
236
275
  );
237
276
  if (args) {
238
- if (args.where && typeof args.where === "object") qb.where(args.where);
277
+ if (args.where && typeof args.where === "object")
278
+ qb.where(args.where);
239
279
  if (args.sort) qb.sort(args.sort);
240
280
  if (args.limit) qb.limit(args.limit);
241
281
  if (args.page) qb.page(args.page);
242
- if (args.depth) qb.depth(args.depth);
282
+ if (args.depth !== void 0) qb.depth(args.depth);
243
283
  if (args.initialData) qb.seed(args.initialData);
244
284
  }
245
285
  return qb;
@@ -332,7 +372,12 @@ var DyrectedClient = class {
332
372
  * comment: 'Please add more detail to section 2.',
333
373
  * })
334
374
  */
335
- transition: (id, transitionName, opts) => this.transition(slug, id, transitionName, opts),
375
+ transition: (id, transitionName, opts) => this.transition(
376
+ slug,
377
+ id,
378
+ transitionName,
379
+ opts
380
+ ),
336
381
  /**
337
382
  * Fetch the workflow history for a single document — every transition that
338
383
  * has ever been performed, newest first.
@@ -362,7 +407,9 @@ var DyrectedClient = class {
362
407
  }
363
408
  async findOne(collection, id, args = {}) {
364
409
  const { initialData, ...queryArgs } = args;
365
- const query = stringifyQuery(queryArgs, { addQueryPrefix: true });
410
+ const query = stringifyQuery(this.applyDefaultDepth(queryArgs), {
411
+ addQueryPrefix: true
412
+ });
366
413
  try {
367
414
  return await this.request(`/api/collections/${collection}/${id}${query}`);
368
415
  } catch (err) {
@@ -371,7 +418,10 @@ var DyrectedClient = class {
371
418
  method: "POST",
372
419
  body: JSON.stringify({ data: [{ id, ...initialData }] })
373
420
  }).catch(
374
- (err2) => console.error(`[dyrected/sdk] Failed to auto-seed document "${id}" in collection "${collection}":`, err2)
421
+ (err2) => console.error(
422
+ `[dyrected/sdk] Failed to auto-seed document "${id}" in collection "${collection}":`,
423
+ err2
424
+ )
375
425
  );
376
426
  return initialData;
377
427
  }
@@ -410,10 +460,13 @@ var DyrectedClient = class {
410
460
  * const updated = await client.transition('posts', postId, 'publish')
411
461
  */
412
462
  async transition(collection, id, transitionName, opts = {}) {
413
- return this.request(`/api/collections/${collection}/${id}/transitions/${encodeURIComponent(transitionName)}`, {
414
- method: "POST",
415
- body: JSON.stringify(opts)
416
- });
463
+ return this.request(
464
+ `/api/collections/${collection}/${id}/transitions/${encodeURIComponent(transitionName)}`,
465
+ {
466
+ method: "POST",
467
+ body: JSON.stringify(opts)
468
+ }
469
+ );
417
470
  }
418
471
  /**
419
472
  * Fetch the workflow history for a document.
@@ -426,7 +479,9 @@ var DyrectedClient = class {
426
479
  */
427
480
  async workflowHistory(collection, id, args = {}) {
428
481
  const query = args.limit ? `?limit=${args.limit}` : "";
429
- return this.request(`/api/collections/${collection}/${id}/workflow-history${query}`);
482
+ return this.request(
483
+ `/api/collections/${collection}/${id}/workflow-history${query}`
484
+ );
430
485
  }
431
486
  /**
432
487
  * Fetch audit entries across every audited collection the current caller can read.
@@ -434,7 +489,9 @@ var DyrectedClient = class {
434
489
  * Sends `GET /api/audit`.
435
490
  */
436
491
  async audit(args = {}) {
437
- const query = stringifyQuery(normalizeQueryArgs(args), { addQueryPrefix: true });
492
+ const query = stringifyQuery(normalizeQueryArgs(args), {
493
+ addQueryPrefix: true
494
+ });
438
495
  return this.request(`/api/audit${query}`);
439
496
  }
440
497
  /**
@@ -443,7 +500,9 @@ var DyrectedClient = class {
443
500
  * Sends `GET /api/collections/:collection/__audit`.
444
501
  */
445
502
  async collectionAudit(collection, args = {}) {
446
- const query = stringifyQuery(normalizeQueryArgs(args), { addQueryPrefix: true });
503
+ const query = stringifyQuery(normalizeQueryArgs(args), {
504
+ addQueryPrefix: true
505
+ });
447
506
  return this.request(`/api/collections/${collection}/__audit${query}`);
448
507
  }
449
508
  async deleteMany(collection, ids) {
@@ -454,7 +513,9 @@ var DyrectedClient = class {
454
513
  }
455
514
  async getGlobal(slug, args = {}) {
456
515
  const { initialData, ...queryArgs } = args;
457
- const query = stringifyQuery(queryArgs, { addQueryPrefix: true });
516
+ const query = stringifyQuery(this.applyDefaultDepth(queryArgs), {
517
+ addQueryPrefix: true
518
+ });
458
519
  try {
459
520
  const res = await this.request(`/api/globals/${slug}${query}`);
460
521
  if ((!res || isFunctionallyEmpty(res)) && !isFunctionallyEmpty(initialData)) {
@@ -462,7 +523,12 @@ var DyrectedClient = class {
462
523
  this.request(`/api/globals/${slug}/seed`, {
463
524
  method: "POST",
464
525
  body: JSON.stringify({ data: initialData })
465
- }).catch((err) => console.error(`[dyrected/sdk] Failed to auto-seed global "${slug}":`, err));
526
+ }).catch(
527
+ (err) => console.error(
528
+ `[dyrected/sdk] Failed to auto-seed global "${slug}":`,
529
+ err
530
+ )
531
+ );
466
532
  return initialData;
467
533
  }
468
534
  return res;
@@ -471,7 +537,12 @@ var DyrectedClient = class {
471
537
  this.request(`/api/globals/${slug}/seed`, {
472
538
  method: "POST",
473
539
  body: JSON.stringify({ data: initialData })
474
- }).catch((err2) => console.error(`[dyrected/sdk] Failed to auto-seed global "${slug}":`, err2));
540
+ }).catch(
541
+ (err2) => console.error(
542
+ `[dyrected/sdk] Failed to auto-seed global "${slug}":`,
543
+ err2
544
+ )
545
+ );
475
546
  return initialData;
476
547
  }
477
548
  throw err;
@@ -484,8 +555,13 @@ var DyrectedClient = class {
484
555
  });
485
556
  }
486
557
  async listMedia(args = {}, collection = "media") {
487
- const query = stringifyQuery(normalizeQueryArgs(args), { addQueryPrefix: true });
488
- return this.request(`/api/collections/${collection}${query}`);
558
+ const query = stringifyQuery(
559
+ this.applyDefaultDepth(normalizeQueryArgs(args)),
560
+ { addQueryPrefix: true }
561
+ );
562
+ return this.request(
563
+ `/api/collections/${collection}${query}`
564
+ );
489
565
  }
490
566
  /** @deprecated Use client.collection('media').upload(file, data) instead */
491
567
  async uploadMedia(file, collection = "media") {
@@ -530,7 +606,9 @@ var DyrectedClient = class {
530
606
  reject(new DyrectedError("Upload aborted", 0));
531
607
  return;
532
608
  }
533
- options.signal.addEventListener("abort", () => xhr.abort(), { once: true });
609
+ options.signal.addEventListener("abort", () => xhr.abort(), {
610
+ once: true
611
+ });
534
612
  }
535
613
  xhr.upload.onprogress = (event) => {
536
614
  if (event.lengthComputable) {
@@ -558,7 +636,13 @@ var DyrectedClient = class {
558
636
  })
559
637
  );
560
638
  }
561
- reject(new DyrectedError(body.message || `Request failed with status ${xhr.status}`, xhr.status, body.code));
639
+ reject(
640
+ new DyrectedError(
641
+ body.message || `Request failed with status ${xhr.status}`,
642
+ xhr.status,
643
+ body.code
644
+ )
645
+ );
562
646
  };
563
647
  xhr.onerror = () => reject(new DyrectedError("Network error during upload", 0));
564
648
  xhr.onabort = () => reject(new DyrectedError("Upload aborted", 0));
@@ -577,7 +661,9 @@ var DyrectedClient = class {
577
661
  });
578
662
  if (res && typeof res.ok === "boolean") {
579
663
  if (!res.ok) {
580
- const body = await res.json().catch(() => ({ message: "Unknown error" }));
664
+ const body = await res.json().catch(() => ({
665
+ message: "Unknown error"
666
+ }));
581
667
  if (res.status === 429 && typeof window !== "undefined") {
582
668
  window.dispatchEvent(
583
669
  new CustomEvent("dyrected:rate-limit", {
@@ -586,7 +672,11 @@ var DyrectedClient = class {
586
672
  );
587
673
  }
588
674
  console.log("[DyrectedError]", body, res.status);
589
- throw new DyrectedError(body.message || `Request failed with status ${res.status}`, res.status, body.code);
675
+ throw new DyrectedError(
676
+ body.message || `Request failed with status ${res.status}`,
677
+ res.status,
678
+ body.code
679
+ );
590
680
  }
591
681
  return res.json();
592
682
  }
@@ -596,6 +686,22 @@ var DyrectedClient = class {
596
686
  function createClient(config) {
597
687
  return new DyrectedClient(config);
598
688
  }
689
+ var PREVIEW_TOKEN_PARAM = "dyPreview";
690
+ function getPreviewToken(search) {
691
+ if (!search) return null;
692
+ let value;
693
+ if (typeof search === "string") {
694
+ value = new URLSearchParams(
695
+ search.startsWith("?") ? search.slice(1) : search
696
+ ).get(PREVIEW_TOKEN_PARAM);
697
+ } else if (search instanceof URLSearchParams) {
698
+ value = search.get(PREVIEW_TOKEN_PARAM);
699
+ } else {
700
+ value = search[PREVIEW_TOKEN_PARAM];
701
+ }
702
+ if (Array.isArray(value)) value = value[0];
703
+ return typeof value === "string" && value.length > 0 ? value : null;
704
+ }
599
705
  function isFunctionallyEmpty(obj) {
600
706
  if (obj === null || obj === void 0 || obj === "") return true;
601
707
  if (Array.isArray(obj)) {
@@ -605,7 +711,9 @@ function isFunctionallyEmpty(obj) {
605
711
  if (typeof obj === "object") {
606
712
  const keys = Object.keys(obj);
607
713
  if (keys.length === 0) return true;
608
- return keys.every((key) => isFunctionallyEmpty(obj[key]));
714
+ return keys.every(
715
+ (key) => isFunctionallyEmpty(obj[key])
716
+ );
609
717
  }
610
718
  return false;
611
719
  }
@@ -639,5 +747,7 @@ function normalizeQueryArgs(args) {
639
747
  0 && (module.exports = {
640
748
  DyrectedClient,
641
749
  DyrectedError,
642
- createClient
750
+ PREVIEW_TOKEN_PARAM,
751
+ createClient,
752
+ getPreviewToken
643
753
  });
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { PaginatedResult, CollectionConfig, GlobalConfig, AdminConfig, PublicAdminAuthConfig, FileData, WorkflowMetadata } from '@dyrected/core';
1
+ import { PaginatedResult, Block, CollectionConfig, GlobalConfig, AdminConfig, PublicAdminAuthConfig, FileData, WorkflowMetadata } from '@dyrected/core';
2
2
  export { AdminIconName, Block, CollectionConfig, EmailField, Field, FieldType, GlobalConfig, IconField, LifecycleEvent, FileData as Media, NumberField, PaginatedResult, TextField, TextareaField, UrlField, WorkflowMetadata } from '@dyrected/core';
3
3
 
4
4
  type UnknownRecord$1 = Record<string, unknown>;
@@ -27,6 +27,7 @@ declare class QueryBuilder<T = UnknownRecord$1> {
27
27
 
28
28
  type UnknownRecord = Record<string, unknown>;
29
29
  type SchemaResponse = {
30
+ blocks?: Block[];
30
31
  collections: CollectionConfig[];
31
32
  globals: GlobalConfig[];
32
33
  admin?: AdminConfig;
@@ -129,13 +130,63 @@ interface DyrectedClientConfig {
129
130
  siteId?: string;
130
131
  headers?: Record<string, string>;
131
132
  fetch?: typeof fetch;
132
- /** Default depth for relationship population. Applied to every request unless overridden per-call. */
133
+ /**
134
+ * Default relationship population depth applied to document reads
135
+ * (`find`, `findOne`, `global().get()`, and media listing) when a call
136
+ * does not pass its own `depth`. Defaults to `1`.
137
+ */
133
138
  defaultDepth?: number;
134
139
  }
135
140
  interface BaseSchema {
136
141
  collections: Record<string, UnknownRecord>;
137
142
  globals: Record<string, UnknownRecord>;
138
143
  }
144
+ /**
145
+ * The structural bound used as the generic *constraint* for {@link DyrectedClient}
146
+ * and the framework hooks. Its map values are `object` rather than
147
+ * `Record<string, unknown>`.
148
+ *
149
+ * This matters because a generated `DyrectedSchema` is built from named
150
+ * `interface`s (one per collection/global), and TypeScript interfaces have **no
151
+ * implicit index signature** — so they are assignable to `object` but *not* to
152
+ * `Record<string, unknown>`. Constraining against {@link BaseSchema} would
153
+ * reject every real generated schema; constraining against `SchemaShape`
154
+ * accepts them while {@link BaseSchema} remains the rich fallback default.
155
+ */
156
+ interface SchemaShape {
157
+ collections: Record<string, object>;
158
+ globals: Record<string, object>;
159
+ }
160
+ /**
161
+ * Global registration seam for your generated schema.
162
+ *
163
+ * `dyrected generate:types` emits a module augmentation that registers your
164
+ * `DyrectedSchema` here:
165
+ *
166
+ * ```ts
167
+ * declare module "@dyrected/sdk" {
168
+ * interface Register { schema: DyrectedSchema }
169
+ * }
170
+ * ```
171
+ *
172
+ * Once that generated file is part of your compilation, every client and
173
+ * framework hook is typed against your schema automatically — no per-call
174
+ * generics. Until then, this stays empty and everything falls back to the
175
+ * loosely-typed {@link BaseSchema}.
176
+ */
177
+ interface Register {
178
+ }
179
+ /**
180
+ * The schema the SDK and framework hooks type themselves against. Resolves to
181
+ * your registered {@link Register} schema when the generated types are present,
182
+ * otherwise to {@link BaseSchema}. This is the default type parameter for
183
+ * {@link DyrectedClient} and {@link createClient}, so `client.collection("...")`
184
+ * and the React/Vue hooks pick up your collections and document shapes with no
185
+ * explicit generic.
186
+ */
187
+ type RegisteredSchema = Register extends {
188
+ schema: infer S;
189
+ } ? S extends SchemaShape ? S : BaseSchema : BaseSchema;
139
190
  /**
140
191
  * Options for file uploads.
141
192
  * When `onProgress` is provided and the runtime supports XMLHttpRequest (browsers),
@@ -148,7 +199,7 @@ interface UploadOptions {
148
199
  /** Abort the in-flight upload. */
149
200
  signal?: AbortSignal;
150
201
  }
151
- declare class DyrectedClient<TSchema extends BaseSchema = BaseSchema> {
202
+ declare class DyrectedClient<TSchema extends SchemaShape = RegisteredSchema> {
152
203
  private baseUrl;
153
204
  private headers;
154
205
  private fetch;
@@ -171,6 +222,11 @@ declare class DyrectedClient<TSchema extends BaseSchema = BaseSchema> {
171
222
  */
172
223
  getAuthHeaders(): Record<string, string>;
173
224
  getBaseUrl(): string;
225
+ /**
226
+ * Inject the client's configured `defaultDepth` when a read did not specify
227
+ * its own `depth`. A per-call `depth` (including `0`) always wins.
228
+ */
229
+ private applyDefaultDepth;
174
230
  getSchemas(): Promise<SchemaResponse>;
175
231
  getAdminAuthConfig(): Promise<PublicAdminAuthConfig>;
176
232
  exchangeAdminAuth(providerId: string, body: Record<string, unknown>): Promise<{
@@ -200,6 +256,21 @@ declare class DyrectedClient<TSchema extends BaseSchema = BaseSchema> {
200
256
  * Used in "token" preview mode.
201
257
  */
202
258
  getPreviewData<T = unknown>(token: string): Promise<T>;
259
+ /**
260
+ * Mint a short-lived preview token that carries the current (unsaved) draft
261
+ * data. Used by the Admin in `previewMode: "token"` to hand draft content to
262
+ * a server-rendered frontend that cannot receive it over `postMessage`.
263
+ *
264
+ * Requires an authenticated request (the Admin is logged in).
265
+ */
266
+ createPreviewToken(input: {
267
+ collectionSlug: string;
268
+ documentId?: string;
269
+ data: unknown;
270
+ }): Promise<{
271
+ token: string;
272
+ expiresAt: string;
273
+ }>;
203
274
  find<K extends keyof TSchema["collections"]>(collection: K & string, args?: QueryArgs<TSchema["collections"][K]>): Promise<PaginatedResult<TSchema["collections"][K]>>;
204
275
  /**
205
276
  * Returns a fluent query builder for a collection.
@@ -412,6 +483,24 @@ declare class DyrectedClient<TSchema extends BaseSchema = BaseSchema> {
412
483
  }>;
413
484
  private request;
414
485
  }
415
- declare function createClient<TSchema extends BaseSchema = BaseSchema>(config: DyrectedClientConfig): DyrectedClient<TSchema>;
486
+ declare function createClient<TSchema extends SchemaShape = RegisteredSchema>(config: DyrectedClientConfig): DyrectedClient<TSchema>;
487
+ /**
488
+ * The query-string parameter the Admin appends to a preview URL in
489
+ * `previewMode: "token"`. Read it on your frontend to decide whether to fetch
490
+ * draft data instead of published content.
491
+ */
492
+ declare const PREVIEW_TOKEN_PARAM = "dyPreview";
493
+ /**
494
+ * Extract the preview token from a request's query string. Accepts a raw query
495
+ * string, a `URLSearchParams`, or a plain params object (e.g. Nuxt's
496
+ * `route.query` or Next's `searchParams`). Returns `null` when absent.
497
+ *
498
+ * @example
499
+ * const token = getPreviewToken(route.query);
500
+ * const doc = token
501
+ * ? (await client.getPreviewData(token)).data
502
+ * : (await client.find("pages", { where })).docs[0];
503
+ */
504
+ declare function getPreviewToken(search: string | URLSearchParams | Record<string, unknown> | undefined | null): string | null;
416
505
 
417
- export { type AuditEntry, type BaseSchema, DyrectedClient, type DyrectedClientConfig, DyrectedError, type InferSchema, type TransitionOptions, type UploadOptions, type WorkflowDocument, type WorkflowHistoryEntry, createClient };
506
+ export { type AuditEntry, type BaseSchema, DyrectedClient, type DyrectedClientConfig, DyrectedError, type InferSchema, PREVIEW_TOKEN_PARAM, type Register, type RegisteredSchema, type SchemaShape, type TransitionOptions, type UploadOptions, type WorkflowDocument, type WorkflowHistoryEntry, createClient, getPreviewToken };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { PaginatedResult, CollectionConfig, GlobalConfig, AdminConfig, PublicAdminAuthConfig, FileData, WorkflowMetadata } from '@dyrected/core';
1
+ import { PaginatedResult, Block, CollectionConfig, GlobalConfig, AdminConfig, PublicAdminAuthConfig, FileData, WorkflowMetadata } from '@dyrected/core';
2
2
  export { AdminIconName, Block, CollectionConfig, EmailField, Field, FieldType, GlobalConfig, IconField, LifecycleEvent, FileData as Media, NumberField, PaginatedResult, TextField, TextareaField, UrlField, WorkflowMetadata } from '@dyrected/core';
3
3
 
4
4
  type UnknownRecord$1 = Record<string, unknown>;
@@ -27,6 +27,7 @@ declare class QueryBuilder<T = UnknownRecord$1> {
27
27
 
28
28
  type UnknownRecord = Record<string, unknown>;
29
29
  type SchemaResponse = {
30
+ blocks?: Block[];
30
31
  collections: CollectionConfig[];
31
32
  globals: GlobalConfig[];
32
33
  admin?: AdminConfig;
@@ -129,13 +130,63 @@ interface DyrectedClientConfig {
129
130
  siteId?: string;
130
131
  headers?: Record<string, string>;
131
132
  fetch?: typeof fetch;
132
- /** Default depth for relationship population. Applied to every request unless overridden per-call. */
133
+ /**
134
+ * Default relationship population depth applied to document reads
135
+ * (`find`, `findOne`, `global().get()`, and media listing) when a call
136
+ * does not pass its own `depth`. Defaults to `1`.
137
+ */
133
138
  defaultDepth?: number;
134
139
  }
135
140
  interface BaseSchema {
136
141
  collections: Record<string, UnknownRecord>;
137
142
  globals: Record<string, UnknownRecord>;
138
143
  }
144
+ /**
145
+ * The structural bound used as the generic *constraint* for {@link DyrectedClient}
146
+ * and the framework hooks. Its map values are `object` rather than
147
+ * `Record<string, unknown>`.
148
+ *
149
+ * This matters because a generated `DyrectedSchema` is built from named
150
+ * `interface`s (one per collection/global), and TypeScript interfaces have **no
151
+ * implicit index signature** — so they are assignable to `object` but *not* to
152
+ * `Record<string, unknown>`. Constraining against {@link BaseSchema} would
153
+ * reject every real generated schema; constraining against `SchemaShape`
154
+ * accepts them while {@link BaseSchema} remains the rich fallback default.
155
+ */
156
+ interface SchemaShape {
157
+ collections: Record<string, object>;
158
+ globals: Record<string, object>;
159
+ }
160
+ /**
161
+ * Global registration seam for your generated schema.
162
+ *
163
+ * `dyrected generate:types` emits a module augmentation that registers your
164
+ * `DyrectedSchema` here:
165
+ *
166
+ * ```ts
167
+ * declare module "@dyrected/sdk" {
168
+ * interface Register { schema: DyrectedSchema }
169
+ * }
170
+ * ```
171
+ *
172
+ * Once that generated file is part of your compilation, every client and
173
+ * framework hook is typed against your schema automatically — no per-call
174
+ * generics. Until then, this stays empty and everything falls back to the
175
+ * loosely-typed {@link BaseSchema}.
176
+ */
177
+ interface Register {
178
+ }
179
+ /**
180
+ * The schema the SDK and framework hooks type themselves against. Resolves to
181
+ * your registered {@link Register} schema when the generated types are present,
182
+ * otherwise to {@link BaseSchema}. This is the default type parameter for
183
+ * {@link DyrectedClient} and {@link createClient}, so `client.collection("...")`
184
+ * and the React/Vue hooks pick up your collections and document shapes with no
185
+ * explicit generic.
186
+ */
187
+ type RegisteredSchema = Register extends {
188
+ schema: infer S;
189
+ } ? S extends SchemaShape ? S : BaseSchema : BaseSchema;
139
190
  /**
140
191
  * Options for file uploads.
141
192
  * When `onProgress` is provided and the runtime supports XMLHttpRequest (browsers),
@@ -148,7 +199,7 @@ interface UploadOptions {
148
199
  /** Abort the in-flight upload. */
149
200
  signal?: AbortSignal;
150
201
  }
151
- declare class DyrectedClient<TSchema extends BaseSchema = BaseSchema> {
202
+ declare class DyrectedClient<TSchema extends SchemaShape = RegisteredSchema> {
152
203
  private baseUrl;
153
204
  private headers;
154
205
  private fetch;
@@ -171,6 +222,11 @@ declare class DyrectedClient<TSchema extends BaseSchema = BaseSchema> {
171
222
  */
172
223
  getAuthHeaders(): Record<string, string>;
173
224
  getBaseUrl(): string;
225
+ /**
226
+ * Inject the client's configured `defaultDepth` when a read did not specify
227
+ * its own `depth`. A per-call `depth` (including `0`) always wins.
228
+ */
229
+ private applyDefaultDepth;
174
230
  getSchemas(): Promise<SchemaResponse>;
175
231
  getAdminAuthConfig(): Promise<PublicAdminAuthConfig>;
176
232
  exchangeAdminAuth(providerId: string, body: Record<string, unknown>): Promise<{
@@ -200,6 +256,21 @@ declare class DyrectedClient<TSchema extends BaseSchema = BaseSchema> {
200
256
  * Used in "token" preview mode.
201
257
  */
202
258
  getPreviewData<T = unknown>(token: string): Promise<T>;
259
+ /**
260
+ * Mint a short-lived preview token that carries the current (unsaved) draft
261
+ * data. Used by the Admin in `previewMode: "token"` to hand draft content to
262
+ * a server-rendered frontend that cannot receive it over `postMessage`.
263
+ *
264
+ * Requires an authenticated request (the Admin is logged in).
265
+ */
266
+ createPreviewToken(input: {
267
+ collectionSlug: string;
268
+ documentId?: string;
269
+ data: unknown;
270
+ }): Promise<{
271
+ token: string;
272
+ expiresAt: string;
273
+ }>;
203
274
  find<K extends keyof TSchema["collections"]>(collection: K & string, args?: QueryArgs<TSchema["collections"][K]>): Promise<PaginatedResult<TSchema["collections"][K]>>;
204
275
  /**
205
276
  * Returns a fluent query builder for a collection.
@@ -412,6 +483,24 @@ declare class DyrectedClient<TSchema extends BaseSchema = BaseSchema> {
412
483
  }>;
413
484
  private request;
414
485
  }
415
- declare function createClient<TSchema extends BaseSchema = BaseSchema>(config: DyrectedClientConfig): DyrectedClient<TSchema>;
486
+ declare function createClient<TSchema extends SchemaShape = RegisteredSchema>(config: DyrectedClientConfig): DyrectedClient<TSchema>;
487
+ /**
488
+ * The query-string parameter the Admin appends to a preview URL in
489
+ * `previewMode: "token"`. Read it on your frontend to decide whether to fetch
490
+ * draft data instead of published content.
491
+ */
492
+ declare const PREVIEW_TOKEN_PARAM = "dyPreview";
493
+ /**
494
+ * Extract the preview token from a request's query string. Accepts a raw query
495
+ * string, a `URLSearchParams`, or a plain params object (e.g. Nuxt's
496
+ * `route.query` or Next's `searchParams`). Returns `null` when absent.
497
+ *
498
+ * @example
499
+ * const token = getPreviewToken(route.query);
500
+ * const doc = token
501
+ * ? (await client.getPreviewData(token)).data
502
+ * : (await client.find("pages", { where })).docs[0];
503
+ */
504
+ declare function getPreviewToken(search: string | URLSearchParams | Record<string, unknown> | undefined | null): string | null;
416
505
 
417
- export { type AuditEntry, type BaseSchema, DyrectedClient, type DyrectedClientConfig, DyrectedError, type InferSchema, type TransitionOptions, type UploadOptions, type WorkflowDocument, type WorkflowHistoryEntry, createClient };
506
+ export { type AuditEntry, type BaseSchema, DyrectedClient, type DyrectedClientConfig, DyrectedError, type InferSchema, PREVIEW_TOKEN_PARAM, type Register, type RegisteredSchema, type SchemaShape, type TransitionOptions, type UploadOptions, type WorkflowDocument, type WorkflowHistoryEntry, createClient, getPreviewToken };
package/dist/index.js CHANGED
@@ -135,6 +135,13 @@ var DyrectedClient = class {
135
135
  getBaseUrl() {
136
136
  return this.baseUrl;
137
137
  }
138
+ /**
139
+ * Inject the client's configured `defaultDepth` when a read did not specify
140
+ * its own `depth`. A per-call `depth` (including `0`) always wins.
141
+ */
142
+ applyDefaultDepth(args) {
143
+ return args.depth === void 0 ? { ...args, depth: this.defaultDepth } : args;
144
+ }
138
145
  async getSchemas() {
139
146
  return this.request("/api/schemas");
140
147
  }
@@ -149,27 +156,48 @@ var DyrectedClient = class {
149
156
  }
150
157
  async getPreference(key, options) {
151
158
  const scopeParam = options?.scope ? `?scope=${options.scope}` : "";
152
- return this.request(`/api/preferences/${encodeURIComponent(key)}${scopeParam}`);
159
+ return this.request(
160
+ `/api/preferences/${encodeURIComponent(key)}${scopeParam}`
161
+ );
153
162
  }
154
163
  async setPreference(key, value, options) {
155
164
  const scopeParam = options?.scope ? `?scope=${options.scope}` : "";
156
- return this.request(`/api/preferences/${encodeURIComponent(key)}${scopeParam}`, {
157
- method: "PUT",
158
- body: JSON.stringify({ value })
159
- });
165
+ return this.request(
166
+ `/api/preferences/${encodeURIComponent(key)}${scopeParam}`,
167
+ {
168
+ method: "PUT",
169
+ body: JSON.stringify({ value })
170
+ }
171
+ );
160
172
  }
161
173
  async deletePreference(key, options) {
162
174
  const scopeParam = options?.scope ? `?scope=${options.scope}` : "";
163
- return this.request(`/api/preferences/${encodeURIComponent(key)}${scopeParam}`, {
164
- method: "DELETE"
165
- });
175
+ return this.request(
176
+ `/api/preferences/${encodeURIComponent(key)}${scopeParam}`,
177
+ {
178
+ method: "DELETE"
179
+ }
180
+ );
166
181
  }
167
182
  /**
168
183
  * Fetch draft data for a specific preview token.
169
184
  * Used in "token" preview mode.
170
185
  */
171
186
  async getPreviewData(token) {
172
- return this.request(`/api/preview-data?token=${token}`);
187
+ return this.request(`/api/preview-data?token=${encodeURIComponent(token)}`);
188
+ }
189
+ /**
190
+ * Mint a short-lived preview token that carries the current (unsaved) draft
191
+ * data. Used by the Admin in `previewMode: "token"` to hand draft content to
192
+ * a server-rendered frontend that cannot receive it over `postMessage`.
193
+ *
194
+ * Requires an authenticated request (the Admin is logged in).
195
+ */
196
+ async createPreviewToken(input) {
197
+ return this.request(`/api/preview-token`, {
198
+ method: "POST",
199
+ body: JSON.stringify(input)
200
+ });
173
201
  }
174
202
  async find(collection, args = {}) {
175
203
  const { initialData, ...queryArgs } = args;
@@ -177,13 +205,22 @@ var DyrectedClient = class {
177
205
  if (queryArgs.where && typeof queryArgs.where === "object") {
178
206
  normalizedArgs.where = JSON.stringify(queryArgs.where);
179
207
  }
180
- const query = stringifyQuery(normalizedArgs, { addQueryPrefix: true });
181
- const res = await this.request(`/api/collections/${collection}${query}`);
208
+ const query = stringifyQuery(this.applyDefaultDepth(normalizedArgs), {
209
+ addQueryPrefix: true
210
+ });
211
+ const res = await this.request(
212
+ `/api/collections/${collection}${query}`
213
+ );
182
214
  if (res.docs.length === 0 && initialData && initialData.length > 0) {
183
215
  this.request(`/api/collections/${collection}/seed`, {
184
216
  method: "POST",
185
217
  body: JSON.stringify({ data: initialData })
186
- }).catch((err) => console.error(`[dyrected/sdk] Failed to auto-seed collection "${collection}":`, err));
218
+ }).catch(
219
+ (err) => console.error(
220
+ `[dyrected/sdk] Failed to auto-seed collection "${collection}":`,
221
+ err
222
+ )
223
+ );
187
224
  return {
188
225
  docs: initialData,
189
226
  total: initialData.length,
@@ -207,11 +244,12 @@ var DyrectedClient = class {
207
244
  (collectionName, queryArgs) => this.find(collectionName, queryArgs)
208
245
  );
209
246
  if (args) {
210
- if (args.where && typeof args.where === "object") qb.where(args.where);
247
+ if (args.where && typeof args.where === "object")
248
+ qb.where(args.where);
211
249
  if (args.sort) qb.sort(args.sort);
212
250
  if (args.limit) qb.limit(args.limit);
213
251
  if (args.page) qb.page(args.page);
214
- if (args.depth) qb.depth(args.depth);
252
+ if (args.depth !== void 0) qb.depth(args.depth);
215
253
  if (args.initialData) qb.seed(args.initialData);
216
254
  }
217
255
  return qb;
@@ -304,7 +342,12 @@ var DyrectedClient = class {
304
342
  * comment: 'Please add more detail to section 2.',
305
343
  * })
306
344
  */
307
- transition: (id, transitionName, opts) => this.transition(slug, id, transitionName, opts),
345
+ transition: (id, transitionName, opts) => this.transition(
346
+ slug,
347
+ id,
348
+ transitionName,
349
+ opts
350
+ ),
308
351
  /**
309
352
  * Fetch the workflow history for a single document — every transition that
310
353
  * has ever been performed, newest first.
@@ -334,7 +377,9 @@ var DyrectedClient = class {
334
377
  }
335
378
  async findOne(collection, id, args = {}) {
336
379
  const { initialData, ...queryArgs } = args;
337
- const query = stringifyQuery(queryArgs, { addQueryPrefix: true });
380
+ const query = stringifyQuery(this.applyDefaultDepth(queryArgs), {
381
+ addQueryPrefix: true
382
+ });
338
383
  try {
339
384
  return await this.request(`/api/collections/${collection}/${id}${query}`);
340
385
  } catch (err) {
@@ -343,7 +388,10 @@ var DyrectedClient = class {
343
388
  method: "POST",
344
389
  body: JSON.stringify({ data: [{ id, ...initialData }] })
345
390
  }).catch(
346
- (err2) => console.error(`[dyrected/sdk] Failed to auto-seed document "${id}" in collection "${collection}":`, err2)
391
+ (err2) => console.error(
392
+ `[dyrected/sdk] Failed to auto-seed document "${id}" in collection "${collection}":`,
393
+ err2
394
+ )
347
395
  );
348
396
  return initialData;
349
397
  }
@@ -382,10 +430,13 @@ var DyrectedClient = class {
382
430
  * const updated = await client.transition('posts', postId, 'publish')
383
431
  */
384
432
  async transition(collection, id, transitionName, opts = {}) {
385
- return this.request(`/api/collections/${collection}/${id}/transitions/${encodeURIComponent(transitionName)}`, {
386
- method: "POST",
387
- body: JSON.stringify(opts)
388
- });
433
+ return this.request(
434
+ `/api/collections/${collection}/${id}/transitions/${encodeURIComponent(transitionName)}`,
435
+ {
436
+ method: "POST",
437
+ body: JSON.stringify(opts)
438
+ }
439
+ );
389
440
  }
390
441
  /**
391
442
  * Fetch the workflow history for a document.
@@ -398,7 +449,9 @@ var DyrectedClient = class {
398
449
  */
399
450
  async workflowHistory(collection, id, args = {}) {
400
451
  const query = args.limit ? `?limit=${args.limit}` : "";
401
- return this.request(`/api/collections/${collection}/${id}/workflow-history${query}`);
452
+ return this.request(
453
+ `/api/collections/${collection}/${id}/workflow-history${query}`
454
+ );
402
455
  }
403
456
  /**
404
457
  * Fetch audit entries across every audited collection the current caller can read.
@@ -406,7 +459,9 @@ var DyrectedClient = class {
406
459
  * Sends `GET /api/audit`.
407
460
  */
408
461
  async audit(args = {}) {
409
- const query = stringifyQuery(normalizeQueryArgs(args), { addQueryPrefix: true });
462
+ const query = stringifyQuery(normalizeQueryArgs(args), {
463
+ addQueryPrefix: true
464
+ });
410
465
  return this.request(`/api/audit${query}`);
411
466
  }
412
467
  /**
@@ -415,7 +470,9 @@ var DyrectedClient = class {
415
470
  * Sends `GET /api/collections/:collection/__audit`.
416
471
  */
417
472
  async collectionAudit(collection, args = {}) {
418
- const query = stringifyQuery(normalizeQueryArgs(args), { addQueryPrefix: true });
473
+ const query = stringifyQuery(normalizeQueryArgs(args), {
474
+ addQueryPrefix: true
475
+ });
419
476
  return this.request(`/api/collections/${collection}/__audit${query}`);
420
477
  }
421
478
  async deleteMany(collection, ids) {
@@ -426,7 +483,9 @@ var DyrectedClient = class {
426
483
  }
427
484
  async getGlobal(slug, args = {}) {
428
485
  const { initialData, ...queryArgs } = args;
429
- const query = stringifyQuery(queryArgs, { addQueryPrefix: true });
486
+ const query = stringifyQuery(this.applyDefaultDepth(queryArgs), {
487
+ addQueryPrefix: true
488
+ });
430
489
  try {
431
490
  const res = await this.request(`/api/globals/${slug}${query}`);
432
491
  if ((!res || isFunctionallyEmpty(res)) && !isFunctionallyEmpty(initialData)) {
@@ -434,7 +493,12 @@ var DyrectedClient = class {
434
493
  this.request(`/api/globals/${slug}/seed`, {
435
494
  method: "POST",
436
495
  body: JSON.stringify({ data: initialData })
437
- }).catch((err) => console.error(`[dyrected/sdk] Failed to auto-seed global "${slug}":`, err));
496
+ }).catch(
497
+ (err) => console.error(
498
+ `[dyrected/sdk] Failed to auto-seed global "${slug}":`,
499
+ err
500
+ )
501
+ );
438
502
  return initialData;
439
503
  }
440
504
  return res;
@@ -443,7 +507,12 @@ var DyrectedClient = class {
443
507
  this.request(`/api/globals/${slug}/seed`, {
444
508
  method: "POST",
445
509
  body: JSON.stringify({ data: initialData })
446
- }).catch((err2) => console.error(`[dyrected/sdk] Failed to auto-seed global "${slug}":`, err2));
510
+ }).catch(
511
+ (err2) => console.error(
512
+ `[dyrected/sdk] Failed to auto-seed global "${slug}":`,
513
+ err2
514
+ )
515
+ );
447
516
  return initialData;
448
517
  }
449
518
  throw err;
@@ -456,8 +525,13 @@ var DyrectedClient = class {
456
525
  });
457
526
  }
458
527
  async listMedia(args = {}, collection = "media") {
459
- const query = stringifyQuery(normalizeQueryArgs(args), { addQueryPrefix: true });
460
- return this.request(`/api/collections/${collection}${query}`);
528
+ const query = stringifyQuery(
529
+ this.applyDefaultDepth(normalizeQueryArgs(args)),
530
+ { addQueryPrefix: true }
531
+ );
532
+ return this.request(
533
+ `/api/collections/${collection}${query}`
534
+ );
461
535
  }
462
536
  /** @deprecated Use client.collection('media').upload(file, data) instead */
463
537
  async uploadMedia(file, collection = "media") {
@@ -502,7 +576,9 @@ var DyrectedClient = class {
502
576
  reject(new DyrectedError("Upload aborted", 0));
503
577
  return;
504
578
  }
505
- options.signal.addEventListener("abort", () => xhr.abort(), { once: true });
579
+ options.signal.addEventListener("abort", () => xhr.abort(), {
580
+ once: true
581
+ });
506
582
  }
507
583
  xhr.upload.onprogress = (event) => {
508
584
  if (event.lengthComputable) {
@@ -530,7 +606,13 @@ var DyrectedClient = class {
530
606
  })
531
607
  );
532
608
  }
533
- reject(new DyrectedError(body.message || `Request failed with status ${xhr.status}`, xhr.status, body.code));
609
+ reject(
610
+ new DyrectedError(
611
+ body.message || `Request failed with status ${xhr.status}`,
612
+ xhr.status,
613
+ body.code
614
+ )
615
+ );
534
616
  };
535
617
  xhr.onerror = () => reject(new DyrectedError("Network error during upload", 0));
536
618
  xhr.onabort = () => reject(new DyrectedError("Upload aborted", 0));
@@ -549,7 +631,9 @@ var DyrectedClient = class {
549
631
  });
550
632
  if (res && typeof res.ok === "boolean") {
551
633
  if (!res.ok) {
552
- const body = await res.json().catch(() => ({ message: "Unknown error" }));
634
+ const body = await res.json().catch(() => ({
635
+ message: "Unknown error"
636
+ }));
553
637
  if (res.status === 429 && typeof window !== "undefined") {
554
638
  window.dispatchEvent(
555
639
  new CustomEvent("dyrected:rate-limit", {
@@ -558,7 +642,11 @@ var DyrectedClient = class {
558
642
  );
559
643
  }
560
644
  console.log("[DyrectedError]", body, res.status);
561
- throw new DyrectedError(body.message || `Request failed with status ${res.status}`, res.status, body.code);
645
+ throw new DyrectedError(
646
+ body.message || `Request failed with status ${res.status}`,
647
+ res.status,
648
+ body.code
649
+ );
562
650
  }
563
651
  return res.json();
564
652
  }
@@ -568,6 +656,22 @@ var DyrectedClient = class {
568
656
  function createClient(config) {
569
657
  return new DyrectedClient(config);
570
658
  }
659
+ var PREVIEW_TOKEN_PARAM = "dyPreview";
660
+ function getPreviewToken(search) {
661
+ if (!search) return null;
662
+ let value;
663
+ if (typeof search === "string") {
664
+ value = new URLSearchParams(
665
+ search.startsWith("?") ? search.slice(1) : search
666
+ ).get(PREVIEW_TOKEN_PARAM);
667
+ } else if (search instanceof URLSearchParams) {
668
+ value = search.get(PREVIEW_TOKEN_PARAM);
669
+ } else {
670
+ value = search[PREVIEW_TOKEN_PARAM];
671
+ }
672
+ if (Array.isArray(value)) value = value[0];
673
+ return typeof value === "string" && value.length > 0 ? value : null;
674
+ }
571
675
  function isFunctionallyEmpty(obj) {
572
676
  if (obj === null || obj === void 0 || obj === "") return true;
573
677
  if (Array.isArray(obj)) {
@@ -577,7 +681,9 @@ function isFunctionallyEmpty(obj) {
577
681
  if (typeof obj === "object") {
578
682
  const keys = Object.keys(obj);
579
683
  if (keys.length === 0) return true;
580
- return keys.every((key) => isFunctionallyEmpty(obj[key]));
684
+ return keys.every(
685
+ (key) => isFunctionallyEmpty(obj[key])
686
+ );
581
687
  }
582
688
  return false;
583
689
  }
@@ -610,5 +716,7 @@ function normalizeQueryArgs(args) {
610
716
  export {
611
717
  DyrectedClient,
612
718
  DyrectedError,
613
- createClient
719
+ PREVIEW_TOKEN_PARAM,
720
+ createClient,
721
+ getPreviewToken
614
722
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dyrected/sdk",
3
- "version": "2.5.60",
3
+ "version": "2.5.62",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -30,7 +30,7 @@
30
30
  "tsup": "^8.5.1",
31
31
  "typescript": "^5.4.5",
32
32
  "vitest": "^1.0.0",
33
- "@dyrected/core": "2.5.60"
33
+ "@dyrected/core": "2.5.62"
34
34
  },
35
35
  "license": "BSL-1.1",
36
36
  "author": "Busola Okeowo <busolaokemoney@gmail.com>",