@asteroidcms/core-utils 0.1.0 → 0.1.2

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.d.cts CHANGED
@@ -2,7 +2,7 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as react from 'react';
3
3
  import { PropsWithChildren } from 'react';
4
4
  import * as _apollo_client from '@apollo/client';
5
- import { InMemoryCacheConfig, ApolloClientOptions, ApolloClient } from '@apollo/client';
5
+ import { InMemoryCacheConfig, ApolloClientOptions, ApolloClient, DocumentNode } from '@apollo/client';
6
6
  import { useMutation } from '@apollo/client/react';
7
7
 
8
8
  type AsteroidCMSConfig = {
@@ -65,32 +65,32 @@ declare module "@apollo/client" {
65
65
  }
66
66
  declare function createApolloClient(config: AsteroidCMSConfig): ApolloClient;
67
67
 
68
- type FieldSelector$1 = string | {
68
+ type FieldSelector$2 = string | {
69
69
  field: string;
70
70
  as?: string;
71
71
  };
72
- type ReferenceExpansion$1 = {
72
+ type ReferenceExpansion$2 = {
73
73
  field: string;
74
74
  as?: string;
75
75
  single?: boolean;
76
- select?: readonly (FieldSelector$1 | ReferenceExpansion$1)[];
76
+ select?: readonly (FieldSelector$2 | ReferenceExpansion$2)[];
77
77
  };
78
- type ContentStatus = "DRAFT" | "PUBLISHED" | "ARCHIVED";
79
- type CmsSearchCondition = {
78
+ type ContentStatus$1 = "DRAFT" | "PUBLISHED" | "ARCHIVED";
79
+ type CmsSearchCondition$1 = {
80
80
  field: string;
81
81
  value: string;
82
82
  mode?: string;
83
83
  };
84
- type UseCmsContentOptions = {
84
+ type UseCmsContentOptions$1 = {
85
85
  schema_slug: string;
86
86
  entrySlug?: string;
87
- select?: readonly (FieldSelector$1 | ReferenceExpansion$1)[];
87
+ select?: readonly (FieldSelector$2 | ReferenceExpansion$2)[];
88
88
  fullData?: boolean;
89
89
  limit?: number;
90
90
  offset?: number;
91
- status?: ContentStatus;
91
+ status?: ContentStatus$1;
92
92
  filter?: Record<string, string | number | boolean | null>;
93
- search?: CmsSearchCondition[];
93
+ search?: CmsSearchCondition$1[];
94
94
  variables?: Record<string, any>;
95
95
  };
96
96
  /**
@@ -99,7 +99,7 @@ type UseCmsContentOptions = {
99
99
  * Supports single-entry fetches (`entrySlug`) and paginated/filtered lists,
100
100
  * with arbitrarily nested reference expansion and field aliasing.
101
101
  */
102
- declare function useCmsContent<T = unknown>({ schema_slug, entrySlug, select, fullData, limit, offset, status, filter, search, variables, }: UseCmsContentOptions): {
102
+ declare function useCmsContent<T = unknown>({ schema_slug, entrySlug, select, fullData, limit, offset, status, filter, search, variables, }: UseCmsContentOptions$1): {
103
103
  client: _apollo_client.ApolloClient;
104
104
  observable: _apollo_client.ObservableQuery<unknown, _apollo_client.OperationVariables>;
105
105
  previousData?: unknown;
@@ -167,23 +167,23 @@ declare function useCmsContent<T = unknown>({ schema_slug, entrySlug, select, fu
167
167
  data: T | undefined;
168
168
  };
169
169
 
170
- type FieldSelector = string | {
170
+ type FieldSelector$1 = string | {
171
171
  field: string;
172
172
  as?: string;
173
173
  single?: boolean;
174
- select?: readonly (FieldSelector | ReferenceExpansion)[];
174
+ select?: readonly (FieldSelector$1 | ReferenceExpansion$1)[];
175
175
  };
176
- type ReferenceExpansion = {
176
+ type ReferenceExpansion$1 = {
177
177
  field: string;
178
178
  as?: string;
179
179
  single?: boolean;
180
- select?: readonly (FieldSelector | ReferenceExpansion)[];
180
+ select?: readonly (FieldSelector$1 | ReferenceExpansion$1)[];
181
181
  };
182
182
  type MutationType = "create" | "update" | "delete";
183
183
  type UseCmsMutateOptions = {
184
184
  schema_slug: string;
185
185
  entrySlug?: string;
186
- select?: readonly (FieldSelector | ReferenceExpansion)[];
186
+ select?: readonly (FieldSelector$1 | ReferenceExpansion$1)[];
187
187
  fullData?: boolean;
188
188
  mutationType?: MutationType;
189
189
  entryId?: string;
@@ -205,6 +205,42 @@ declare function useCmsMutate<TData = unknown>({ schema_slug, select, fullData,
205
205
  }, _apollo_client.ApolloCache, "none">;
206
206
  };
207
207
 
208
+ type FieldSelector = string | {
209
+ field: string;
210
+ as?: string;
211
+ };
212
+ type ReferenceExpansion = {
213
+ field: string;
214
+ as?: string;
215
+ single?: boolean;
216
+ select?: readonly (FieldSelector | ReferenceExpansion)[];
217
+ };
218
+ type ContentStatus = "DRAFT" | "PUBLISHED" | "ARCHIVED";
219
+ type CmsSearchCondition = {
220
+ field: string;
221
+ value: string;
222
+ mode?: string;
223
+ };
224
+ type UseCmsContentOptions = {
225
+ schema_slug: string;
226
+ entrySlug?: string;
227
+ select?: readonly (FieldSelector | ReferenceExpansion)[];
228
+ fullData?: boolean;
229
+ limit?: number;
230
+ offset?: number;
231
+ status?: ContentStatus;
232
+ filter?: Record<string, string | number | boolean | null>;
233
+ search?: CmsSearchCondition[];
234
+ variables?: Record<string, unknown>;
235
+ };
236
+ declare function buildCmsQuery({ schema_slug, entrySlug, select, fullData, limit, offset, status, filter, search, variables, }: UseCmsContentOptions): {
237
+ query: DocumentNode;
238
+ variables: Record<string, unknown>;
239
+ isSingle: boolean;
240
+ };
241
+
242
+ declare function fetchCmsContent<T>(getClient: () => ApolloClient, opts: UseCmsContentOptions): Promise<T>;
243
+
208
244
  /**
209
245
  * Build a canonical media URL from an asset id. Pass `cmsUrl` explicitly when
210
246
  * calling outside of React (e.g. SSR loaders, scripts). Inside components,
@@ -217,6 +253,38 @@ declare function cmsImage(id: string | undefined, options: {
217
253
  /** Hook variant that pulls `cmsUrl`/`mediaPath` from the provider. */
218
254
  declare function useCmsImage(): (id?: string) => string;
219
255
 
256
+ type ReadTimeUnit = "short" | "long";
257
+ interface GetContentReadTimeOptions {
258
+ /**
259
+ * Average words read per minute
260
+ * @default 200
261
+ */
262
+ wordsPerMinute?: number;
263
+ /**
264
+ * Output style
265
+ * short => "3 min read"
266
+ * long => "3 minutes read"
267
+ *
268
+ * @default "short"
269
+ */
270
+ format?: ReadTimeUnit;
271
+ /**
272
+ * Round mode for minutes
273
+ * ceil => always round up
274
+ * round => normal rounding
275
+ * floor => round down
276
+ *
277
+ * @default "ceil"
278
+ */
279
+ round?: "ceil" | "round" | "floor";
280
+ /**
281
+ * Minimum minutes returned
282
+ * @default 1
283
+ */
284
+ minMinutes?: number;
285
+ }
286
+ declare function getContentReadTime(content: string, options?: GetContentReadTimeOptions): string;
287
+
220
288
  /**
221
289
  * Portable rich-text parser for CMS content.
222
290
  *
@@ -263,4 +331,4 @@ declare function RichTextContent({ html, classMap, as, className, }: RichTextCon
263
331
  };
264
332
  }, HTMLElement>;
265
333
 
266
- export { type AsteroidCMSConfig, AsteroidCMSProvider, type AsteroidCMSProviderProps, type ParseRichTextOptions, type ResolvedAsteroidCMSConfig, type RichTextClassKey, type RichTextClassMap, RichTextContent, type UseCmsContentOptions, type UseCmsMutateOptions, cmsImage, createApolloClient, parseRichText, removeEmptyParagraphs, useAsteroidCMSConfig, useCmsContent, useCmsImage, useCmsMutate };
334
+ export { type AsteroidCMSConfig, AsteroidCMSProvider, type AsteroidCMSProviderProps, type CmsSearchCondition, type ContentStatus, type FieldSelector, type ParseRichTextOptions, type ReferenceExpansion, type ResolvedAsteroidCMSConfig, type RichTextClassKey, type RichTextClassMap, RichTextContent, type UseCmsContentOptions$1 as UseCmsContentOptions, type UseCmsMutateOptions, buildCmsQuery, cmsImage, createApolloClient, fetchCmsContent, getContentReadTime, parseRichText, removeEmptyParagraphs, useAsteroidCMSConfig, useCmsContent, useCmsImage, useCmsMutate };
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as react from 'react';
3
3
  import { PropsWithChildren } from 'react';
4
4
  import * as _apollo_client from '@apollo/client';
5
- import { InMemoryCacheConfig, ApolloClientOptions, ApolloClient } from '@apollo/client';
5
+ import { InMemoryCacheConfig, ApolloClientOptions, ApolloClient, DocumentNode } from '@apollo/client';
6
6
  import { useMutation } from '@apollo/client/react';
7
7
 
8
8
  type AsteroidCMSConfig = {
@@ -65,32 +65,32 @@ declare module "@apollo/client" {
65
65
  }
66
66
  declare function createApolloClient(config: AsteroidCMSConfig): ApolloClient;
67
67
 
68
- type FieldSelector$1 = string | {
68
+ type FieldSelector$2 = string | {
69
69
  field: string;
70
70
  as?: string;
71
71
  };
72
- type ReferenceExpansion$1 = {
72
+ type ReferenceExpansion$2 = {
73
73
  field: string;
74
74
  as?: string;
75
75
  single?: boolean;
76
- select?: readonly (FieldSelector$1 | ReferenceExpansion$1)[];
76
+ select?: readonly (FieldSelector$2 | ReferenceExpansion$2)[];
77
77
  };
78
- type ContentStatus = "DRAFT" | "PUBLISHED" | "ARCHIVED";
79
- type CmsSearchCondition = {
78
+ type ContentStatus$1 = "DRAFT" | "PUBLISHED" | "ARCHIVED";
79
+ type CmsSearchCondition$1 = {
80
80
  field: string;
81
81
  value: string;
82
82
  mode?: string;
83
83
  };
84
- type UseCmsContentOptions = {
84
+ type UseCmsContentOptions$1 = {
85
85
  schema_slug: string;
86
86
  entrySlug?: string;
87
- select?: readonly (FieldSelector$1 | ReferenceExpansion$1)[];
87
+ select?: readonly (FieldSelector$2 | ReferenceExpansion$2)[];
88
88
  fullData?: boolean;
89
89
  limit?: number;
90
90
  offset?: number;
91
- status?: ContentStatus;
91
+ status?: ContentStatus$1;
92
92
  filter?: Record<string, string | number | boolean | null>;
93
- search?: CmsSearchCondition[];
93
+ search?: CmsSearchCondition$1[];
94
94
  variables?: Record<string, any>;
95
95
  };
96
96
  /**
@@ -99,7 +99,7 @@ type UseCmsContentOptions = {
99
99
  * Supports single-entry fetches (`entrySlug`) and paginated/filtered lists,
100
100
  * with arbitrarily nested reference expansion and field aliasing.
101
101
  */
102
- declare function useCmsContent<T = unknown>({ schema_slug, entrySlug, select, fullData, limit, offset, status, filter, search, variables, }: UseCmsContentOptions): {
102
+ declare function useCmsContent<T = unknown>({ schema_slug, entrySlug, select, fullData, limit, offset, status, filter, search, variables, }: UseCmsContentOptions$1): {
103
103
  client: _apollo_client.ApolloClient;
104
104
  observable: _apollo_client.ObservableQuery<unknown, _apollo_client.OperationVariables>;
105
105
  previousData?: unknown;
@@ -167,23 +167,23 @@ declare function useCmsContent<T = unknown>({ schema_slug, entrySlug, select, fu
167
167
  data: T | undefined;
168
168
  };
169
169
 
170
- type FieldSelector = string | {
170
+ type FieldSelector$1 = string | {
171
171
  field: string;
172
172
  as?: string;
173
173
  single?: boolean;
174
- select?: readonly (FieldSelector | ReferenceExpansion)[];
174
+ select?: readonly (FieldSelector$1 | ReferenceExpansion$1)[];
175
175
  };
176
- type ReferenceExpansion = {
176
+ type ReferenceExpansion$1 = {
177
177
  field: string;
178
178
  as?: string;
179
179
  single?: boolean;
180
- select?: readonly (FieldSelector | ReferenceExpansion)[];
180
+ select?: readonly (FieldSelector$1 | ReferenceExpansion$1)[];
181
181
  };
182
182
  type MutationType = "create" | "update" | "delete";
183
183
  type UseCmsMutateOptions = {
184
184
  schema_slug: string;
185
185
  entrySlug?: string;
186
- select?: readonly (FieldSelector | ReferenceExpansion)[];
186
+ select?: readonly (FieldSelector$1 | ReferenceExpansion$1)[];
187
187
  fullData?: boolean;
188
188
  mutationType?: MutationType;
189
189
  entryId?: string;
@@ -205,6 +205,42 @@ declare function useCmsMutate<TData = unknown>({ schema_slug, select, fullData,
205
205
  }, _apollo_client.ApolloCache, "none">;
206
206
  };
207
207
 
208
+ type FieldSelector = string | {
209
+ field: string;
210
+ as?: string;
211
+ };
212
+ type ReferenceExpansion = {
213
+ field: string;
214
+ as?: string;
215
+ single?: boolean;
216
+ select?: readonly (FieldSelector | ReferenceExpansion)[];
217
+ };
218
+ type ContentStatus = "DRAFT" | "PUBLISHED" | "ARCHIVED";
219
+ type CmsSearchCondition = {
220
+ field: string;
221
+ value: string;
222
+ mode?: string;
223
+ };
224
+ type UseCmsContentOptions = {
225
+ schema_slug: string;
226
+ entrySlug?: string;
227
+ select?: readonly (FieldSelector | ReferenceExpansion)[];
228
+ fullData?: boolean;
229
+ limit?: number;
230
+ offset?: number;
231
+ status?: ContentStatus;
232
+ filter?: Record<string, string | number | boolean | null>;
233
+ search?: CmsSearchCondition[];
234
+ variables?: Record<string, unknown>;
235
+ };
236
+ declare function buildCmsQuery({ schema_slug, entrySlug, select, fullData, limit, offset, status, filter, search, variables, }: UseCmsContentOptions): {
237
+ query: DocumentNode;
238
+ variables: Record<string, unknown>;
239
+ isSingle: boolean;
240
+ };
241
+
242
+ declare function fetchCmsContent<T>(getClient: () => ApolloClient, opts: UseCmsContentOptions): Promise<T>;
243
+
208
244
  /**
209
245
  * Build a canonical media URL from an asset id. Pass `cmsUrl` explicitly when
210
246
  * calling outside of React (e.g. SSR loaders, scripts). Inside components,
@@ -217,6 +253,38 @@ declare function cmsImage(id: string | undefined, options: {
217
253
  /** Hook variant that pulls `cmsUrl`/`mediaPath` from the provider. */
218
254
  declare function useCmsImage(): (id?: string) => string;
219
255
 
256
+ type ReadTimeUnit = "short" | "long";
257
+ interface GetContentReadTimeOptions {
258
+ /**
259
+ * Average words read per minute
260
+ * @default 200
261
+ */
262
+ wordsPerMinute?: number;
263
+ /**
264
+ * Output style
265
+ * short => "3 min read"
266
+ * long => "3 minutes read"
267
+ *
268
+ * @default "short"
269
+ */
270
+ format?: ReadTimeUnit;
271
+ /**
272
+ * Round mode for minutes
273
+ * ceil => always round up
274
+ * round => normal rounding
275
+ * floor => round down
276
+ *
277
+ * @default "ceil"
278
+ */
279
+ round?: "ceil" | "round" | "floor";
280
+ /**
281
+ * Minimum minutes returned
282
+ * @default 1
283
+ */
284
+ minMinutes?: number;
285
+ }
286
+ declare function getContentReadTime(content: string, options?: GetContentReadTimeOptions): string;
287
+
220
288
  /**
221
289
  * Portable rich-text parser for CMS content.
222
290
  *
@@ -263,4 +331,4 @@ declare function RichTextContent({ html, classMap, as, className, }: RichTextCon
263
331
  };
264
332
  }, HTMLElement>;
265
333
 
266
- export { type AsteroidCMSConfig, AsteroidCMSProvider, type AsteroidCMSProviderProps, type ParseRichTextOptions, type ResolvedAsteroidCMSConfig, type RichTextClassKey, type RichTextClassMap, RichTextContent, type UseCmsContentOptions, type UseCmsMutateOptions, cmsImage, createApolloClient, parseRichText, removeEmptyParagraphs, useAsteroidCMSConfig, useCmsContent, useCmsImage, useCmsMutate };
334
+ export { type AsteroidCMSConfig, AsteroidCMSProvider, type AsteroidCMSProviderProps, type CmsSearchCondition, type ContentStatus, type FieldSelector, type ParseRichTextOptions, type ReferenceExpansion, type ResolvedAsteroidCMSConfig, type RichTextClassKey, type RichTextClassMap, RichTextContent, type UseCmsContentOptions$1 as UseCmsContentOptions, type UseCmsMutateOptions, buildCmsQuery, cmsImage, createApolloClient, fetchCmsContent, getContentReadTime, parseRichText, removeEmptyParagraphs, useAsteroidCMSConfig, useCmsContent, useCmsImage, useCmsMutate };
package/dist/index.js CHANGED
@@ -113,7 +113,7 @@ function useCmsContent({
113
113
  variables = {}
114
114
  }) {
115
115
  const isSingle = !!entrySlug;
116
- const buildSelection = (items = []) => {
116
+ const buildSelection2 = (items = []) => {
117
117
  const lines = [];
118
118
  items.forEach((item) => {
119
119
  if (typeof item === "string") {
@@ -126,7 +126,7 @@ function useCmsContent({
126
126
  }
127
127
  const alias = item.as || item.field;
128
128
  const resolver = item.single ? "expandedReferenceObject" : "expandedReference";
129
- const subSelection = item.select?.length ? buildSelection(item.select) : "data { ... }";
129
+ const subSelection = item.select?.length ? buildSelection2(item.select) : "data { ... }";
130
130
  lines.push(`
131
131
  ${alias}: ${resolver}(slug: "${item.field}") {
132
132
  ${subSelection}
@@ -139,7 +139,7 @@ function useCmsContent({
139
139
  const selectionParts = [];
140
140
  if (fullData) selectionParts.push("data");
141
141
  if (select.length > 0) {
142
- const userSelection = buildSelection(select);
142
+ const userSelection = buildSelection2(select);
143
143
  if (userSelection) selectionParts.push(userSelection);
144
144
  }
145
145
  const selection = selectionParts.join("\n ").trim() || "id";
@@ -223,7 +223,7 @@ function useCmsMutate({
223
223
  entryId,
224
224
  variables: inputVariables = {}
225
225
  }) {
226
- const buildSelection = (items = []) => {
226
+ const buildSelection2 = (items = []) => {
227
227
  const lines = [];
228
228
  for (const item of items) {
229
229
  if (typeof item === "string") {
@@ -237,7 +237,7 @@ function useCmsMutate({
237
237
  continue;
238
238
  }
239
239
  const resolver = single ? "expandedReferenceObject" : "expandedReference";
240
- const sub = buildSelection(subSelect) || "id slug";
240
+ const sub = buildSelection2(subSelect) || "id slug";
241
241
  lines.push(`
242
242
  ${alias}: ${resolver}(slug: "${field}") {
243
243
  id
@@ -248,7 +248,7 @@ function useCmsMutate({
248
248
  }
249
249
  return lines.join("\n ").trim();
250
250
  };
251
- const userSelection = buildSelection(select);
251
+ const userSelection = buildSelection2(select);
252
252
  let selection = fullData ? "data" : "";
253
253
  if (userSelection) {
254
254
  selection = selection ? `${selection}
@@ -305,6 +305,129 @@ function useCmsMutate({
305
305
  data: resultData
306
306
  };
307
307
  }
308
+ function buildSelection(items = []) {
309
+ const lines = [];
310
+ items.forEach((item) => {
311
+ if (typeof item === "string") {
312
+ lines.push(`${item}: dataField(slug: "${item}")`);
313
+ return;
314
+ }
315
+ if ("field" in item && typeof item.field === "string") {
316
+ if (!("select" in item)) {
317
+ const alias2 = item.as || item.field;
318
+ lines.push(`${alias2}: dataField(slug: "${item.field}")`);
319
+ return;
320
+ }
321
+ const alias = item.as || item.field;
322
+ const resolver = item.single ? "expandedReferenceObject" : "expandedReference";
323
+ const subSelection = item.select?.length ? buildSelection(item.select) : "data { ... }";
324
+ lines.push(`
325
+ ${alias}: ${resolver}(slug: "${item.field}") {
326
+ ${subSelection}
327
+ }
328
+ `);
329
+ }
330
+ });
331
+ return lines.join("\n ").trim();
332
+ }
333
+ function buildCmsQuery({
334
+ schema_slug,
335
+ entrySlug,
336
+ select = [],
337
+ fullData = false,
338
+ limit,
339
+ offset,
340
+ status = "PUBLISHED",
341
+ filter,
342
+ search,
343
+ variables = {}
344
+ }) {
345
+ const isSingle = Boolean(entrySlug);
346
+ const selectionParts = [];
347
+ if (fullData) {
348
+ selectionParts.push("data");
349
+ }
350
+ if (select.length > 0) {
351
+ const userSelection = buildSelection(select);
352
+ if (userSelection) {
353
+ selectionParts.push(userSelection);
354
+ }
355
+ }
356
+ const selection = selectionParts.join("\n ").trim() || "id";
357
+ const queryVariables = {
358
+ schema_slug,
359
+ ...entrySlug && { slug: entrySlug },
360
+ ...variables
361
+ };
362
+ const fieldArgParts = ["schema_slug: $schema_slug"];
363
+ if (!isSingle) {
364
+ if (typeof limit === "number" && limit >= 0) {
365
+ fieldArgParts.push("limit: $limit");
366
+ queryVariables.limit = limit;
367
+ }
368
+ if (typeof offset === "number" && offset >= 0) {
369
+ fieldArgParts.push("offset: $offset");
370
+ queryVariables.offset = offset;
371
+ }
372
+ if (status) {
373
+ const statuses = status;
374
+ if (statuses.length > 0) {
375
+ fieldArgParts.push("status: $status");
376
+ queryVariables.status = statuses;
377
+ }
378
+ }
379
+ const hasSearch = Array.isArray(search) && search.length > 0;
380
+ const hasFilter = filter && typeof filter === "object" && Object.keys(filter).length > 0;
381
+ if (hasSearch || hasFilter) {
382
+ const mergedFilter = hasFilter ? { ...filter } : {};
383
+ if (hasSearch) {
384
+ for (const condition of search) {
385
+ mergedFilter[condition.field] = {
386
+ regex: true,
387
+ value: condition.value,
388
+ mode: condition.mode ?? "i"
389
+ };
390
+ }
391
+ }
392
+ fieldArgParts.push("data: $filter");
393
+ queryVariables.filter = mergedFilter;
394
+ }
395
+ }
396
+ const varDecls = ["$schema_slug: String!"];
397
+ if ("limit" in queryVariables) varDecls.push("$limit: Float");
398
+ if ("offset" in queryVariables) varDecls.push("$offset: Float");
399
+ if ("status" in queryVariables) varDecls.push("$status: ContentStatus");
400
+ if ("filter" in queryVariables) varDecls.push("$filter: JSONObject");
401
+ const varBlock = varDecls.length > 0 ? `(
402
+ ${varDecls.join("\n ")}
403
+ )` : "";
404
+ const contentFieldArgs = fieldArgParts.join(", ");
405
+ const queryStr = isSingle ? `
406
+ query Get${schema_slug}Entry($schema_slug: String!, $slug: String!) {
407
+ entry: contentEntry(schema_slug: $schema_slug, slug: $slug) {
408
+ ${selection}
409
+ }
410
+ }
411
+ ` : `
412
+ query Get${schema_slug}Entries${varBlock} {
413
+ entries: contentEntries(${contentFieldArgs}) {
414
+ ${selection}
415
+ }
416
+ }
417
+ `;
418
+ return {
419
+ query: gql(queryStr),
420
+ variables: queryVariables,
421
+ isSingle
422
+ };
423
+ }
424
+
425
+ // src/fetchCmsContent.ts
426
+ async function fetchCmsContent(getClient, opts) {
427
+ const { query, variables, isSingle } = buildCmsQuery(opts);
428
+ const { data } = await getClient().query({ query, variables });
429
+ return isSingle ? data.entry : data.entries;
430
+ }
308
431
 
309
432
  // src/utils/cmsImage.ts
310
433
  function cmsImage(id, options) {
@@ -318,6 +441,35 @@ function useCmsImage() {
318
441
  return (id) => cmsImage(id, { cmsUrl, mediaPath });
319
442
  }
320
443
 
444
+ // src/utils/getContentReadTime.ts
445
+ function getContentReadTime(content, options = {}) {
446
+ const {
447
+ wordsPerMinute = 200,
448
+ format = "short",
449
+ round = "ceil",
450
+ minMinutes = 1
451
+ } = options;
452
+ if (!content || typeof content !== "string") {
453
+ return format === "short" ? `${minMinutes} min read` : `${minMinutes} minute read`;
454
+ }
455
+ let text = content.replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<style[\s\S]*?<\/style>/gi, " ");
456
+ text = text.replace(/<\/(p|div|h[1-6]|li|blockquote|pre|code|br|tr)>/gi, " ").replace(/<[^>]+>/g, " ");
457
+ text = text.replace(/&nbsp;/gi, " ").replace(/&amp;/gi, "&").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&#39;/gi, "'").replace(/&quot;/gi, '"');
458
+ text = text.replace(/\s+/g, " ").trim();
459
+ const words = text.split(" ").filter(Boolean).length;
460
+ const rawMinutes = words / wordsPerMinute;
461
+ const roundMap = {
462
+ ceil: Math.ceil,
463
+ round: Math.round,
464
+ floor: Math.floor
465
+ };
466
+ const minutes = Math.max(minMinutes, roundMap[round](rawMinutes));
467
+ if (format === "long") {
468
+ return `${minutes} ${minutes === 1 ? "minute" : "minutes"} read`;
469
+ }
470
+ return `${minutes} min read`;
471
+ }
472
+
321
473
  // src/components/richTextParser.ts
322
474
  var DEFAULT_ALLOWLIST = [
323
475
  "p",
@@ -1450,6 +1602,6 @@ function RichTextContent({
1450
1602
  });
1451
1603
  }
1452
1604
 
1453
- export { AsteroidCMSProvider, RichTextContent, cmsImage, createApolloClient, parseRichText, removeEmptyParagraphs, useAsteroidCMSConfig, useCmsContent, useCmsImage, useCmsMutate };
1605
+ export { AsteroidCMSProvider, RichTextContent, buildCmsQuery, cmsImage, createApolloClient, fetchCmsContent, getContentReadTime, parseRichText, removeEmptyParagraphs, useAsteroidCMSConfig, useCmsContent, useCmsImage, useCmsMutate };
1454
1606
  //# sourceMappingURL=index.js.map
1455
1607
  //# sourceMappingURL=index.js.map