@xleddyl/nuxt-cms 0.1.40 → 0.1.41

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/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xleddyl/nuxt-cms",
3
3
  "configKey": "cms",
4
- "version": "0.1.40",
4
+ "version": "0.1.41",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -9,8 +9,8 @@ import svgLoader from 'vite-svg-loader';
9
9
  import { introspectionFromSchema, buildSchema } from 'graphql';
10
10
  import { minifyIntrospection, outputIntrospectionFile } from 'gql.tada/internal';
11
11
  import { createJiti } from 'jiti';
12
- import { typeName, blockTypeName, blockUnionName, renderGraphqlSdl } from '../dist/runtime/shared/graphql-sdl.js';
13
- import { isMultiSelect, fieldConditions, isTranslatableField, isTranslatableMediaField, isRequiredField, isPrivateField, DEFAULT_MEDIA_MAX_FILE_SIZE } from '../dist/runtime/shared/index.js';
12
+ import { typeName, blockTypeName, blocksFieldTypeName, renderGraphqlSdl } from '../dist/runtime/shared/graphql-sdl.js';
13
+ import { isMultiSelect, fieldConditions, isTranslatableField, isTranslatableMediaField, isRequiredField, isPrivateField, mediaTypeFilter, DEFAULT_MEDIA_MAX_FILE_SIZE } from '../dist/runtime/shared/index.js';
14
14
  import { scanMediaDirectory, readMediaFileMeta } from '../dist/runtime/server/utils/media-sync.js';
15
15
  import { createHash } from 'node:crypto';
16
16
  import { readFile } from 'node:fs/promises';
@@ -450,6 +450,10 @@ ${[mediaTableExpr(dialect), ...tables, ...joins].join(
450
450
  `;
451
451
  }
452
452
 
453
+ function mediaTsType(field) {
454
+ const types = mediaTypeFilter(field.mediaType);
455
+ return types ? `CmsMedia<${types.map((type) => `'${type}'`).join(" | ")}>` : "CmsMedia";
456
+ }
453
457
  function scalarTsType(field) {
454
458
  switch (field.type) {
455
459
  case "number":
@@ -470,13 +474,13 @@ function fieldTsType(config, entryName, key, field) {
470
474
  if (field.cardinality === "many-to-many") return `${target}[]`;
471
475
  return isRequiredField(field) && !config[field.to]?.drafts ? target : `${target} | null`;
472
476
  }
473
- if (field.type === "media") return "CmsMedia | null";
477
+ if (field.type === "media") return `${mediaTsType(field)} | null`;
474
478
  if (field.type === "select" && field.multiple) {
475
479
  const union = field.options.map((o) => JSON.stringify(o)).join(" | ");
476
480
  return `(${union})[]`;
477
481
  }
478
482
  if (field.type === "blocks") {
479
- const union = blockUnionName(entryName, key);
483
+ const union = blocksFieldTypeName(entryName, key);
480
484
  return isRequiredField(field) ? `${union}[]` : `${union}[] | null`;
481
485
  }
482
486
  if (isTranslatableField(field)) return isRequiredField(field) ? "string" : "string | null";
@@ -492,7 +496,7 @@ function blockTypesTs(entryName, key, field) {
492
496
  const lines = [` __typename?: '${name}'`, ` type: '${blockName}'`];
493
497
  for (const [blockFieldKey, blockField] of Object.entries(block.fields)) {
494
498
  if (blockField.type === "media") {
495
- lines.push(` ${blockFieldKey}: CmsMedia | null`);
499
+ lines.push(` ${blockFieldKey}: ${mediaTsType(blockField)} | null`);
496
500
  continue;
497
501
  }
498
502
  const base = scalarTsType(blockField);
@@ -503,7 +507,7 @@ ${lines.join("\n")}
503
507
  }`);
504
508
  }
505
509
  if (members.length)
506
- defs.push(`export type ${blockUnionName(entryName, key)} = ${members.join(" | ")}`);
510
+ defs.push(`export type ${blocksFieldTypeName(entryName, key)} = ${members.join(" | ")}`);
507
511
  return defs;
508
512
  }
509
513
  function entryTs(config, name, entry) {
@@ -520,10 +524,11 @@ ${lines.join("\n")}
520
524
  }
521
525
  function renderTypesFile(config) {
522
526
  const parts = [
523
- `export interface CmsMedia {
527
+ `export type CmsMediaType = 'image' | 'video' | 'file'`,
528
+ `export interface CmsMedia<T extends CmsMediaType = CmsMediaType> {
524
529
  key: string
525
530
  url: string | null
526
- type: 'image' | 'video' | 'file'
531
+ type: T
527
532
  alt: string | null
528
533
  folder: string | null
529
534
  mime: string | null
@@ -1,6 +1,11 @@
1
- import type { AsyncData } from 'nuxt/app';
1
+ import type { AsyncData, AsyncDataOptions } from 'nuxt/app';
2
2
  type CmsDisabledResult = Record<string, any>;
3
3
  type CmsDisabledVariables = Record<string, any>;
4
+ export interface CmsAsyncDataOptions<ResT, DefaultT> extends Pick<AsyncDataOptions<ResT>, 'server' | 'lazy' | 'immediate' | 'deep' | 'dedupe' | 'watch'> {
5
+ key?: string;
6
+ default?: () => DefaultT;
7
+ }
8
+ export declare function cmsQueryKey(query: string, variables?: unknown): string;
4
9
  export declare function $cmsQuery<const Q extends string>(query: Q, variables?: CmsDisabledVariables): Promise<CmsDisabledResult>;
5
- export declare function useCms<const Q extends string>(query: Q, variables?: CmsDisabledVariables): AsyncData<CmsDisabledResult | undefined, Error | undefined>;
10
+ export declare function useCms<const Q extends string, DefaultT = undefined>(query: Q, variables?: CmsDisabledVariables, options?: CmsAsyncDataOptions<CmsDisabledResult, DefaultT>): AsyncData<CmsDisabledResult | DefaultT | undefined, Error | undefined>;
6
11
  export {};
@@ -1,10 +1,14 @@
1
1
  import { useAsyncData } from "#imports";
2
+ export function cmsQueryKey(query, variables) {
3
+ return `cms-gql:${query}:${JSON.stringify(variables ?? {})}`;
4
+ }
2
5
  export async function $cmsQuery(query, variables) {
3
6
  return {};
4
7
  }
5
- export function useCms(query, variables) {
8
+ export function useCms(query, variables, options = {}) {
9
+ const { key, default: defaultValue } = options;
6
10
  return useAsyncData(
7
- `cms-gql:${query}:${JSON.stringify(variables ?? {})}`,
8
- async () => null
11
+ key ?? cmsQueryKey(query, variables),
12
+ async () => defaultValue ? defaultValue() : null
9
13
  );
10
14
  }
@@ -1,4 +1,9 @@
1
1
  import type { CmsResult, CmsVariables } from '#cms-graphql';
2
- import type { AsyncData } from 'nuxt/app';
2
+ import type { AsyncData, AsyncDataOptions } from 'nuxt/app';
3
+ export interface CmsAsyncDataOptions<ResT, DefaultT> extends Pick<AsyncDataOptions<ResT>, 'server' | 'lazy' | 'immediate' | 'deep' | 'dedupe' | 'watch'> {
4
+ key?: string;
5
+ default?: () => DefaultT;
6
+ }
7
+ export declare function cmsQueryKey(query: string, variables?: unknown): string;
3
8
  export declare function $cmsQuery<const Q extends string>(query: Q, variables?: CmsVariables<Q>): Promise<CmsResult<Q>>;
4
- export declare function useCms<const Q extends string>(query: Q, variables?: CmsVariables<Q>): AsyncData<CmsResult<Q> | undefined, Error | undefined>;
9
+ export declare function useCms<const Q extends string, DefaultT = undefined>(query: Q, variables?: CmsVariables<Q>, options?: CmsAsyncDataOptions<CmsResult<Q>, DefaultT>): AsyncData<CmsResult<Q> | DefaultT | undefined, Error | undefined>;
@@ -1,5 +1,8 @@
1
1
  import { useAsyncData } from "#imports";
2
2
  const ENDPOINT = "/api/cms/graphql";
3
+ export function cmsQueryKey(query, variables) {
4
+ return `cms-gql:${query}:${JSON.stringify(variables ?? {})}`;
5
+ }
3
6
  export async function $cmsQuery(query, variables) {
4
7
  const res = await $fetch(ENDPOINT, {
5
8
  method: "POST",
@@ -10,9 +13,11 @@ export async function $cmsQuery(query, variables) {
10
13
  }
11
14
  return res.data;
12
15
  }
13
- export function useCms(query, variables) {
16
+ export function useCms(query, variables, options = {}) {
17
+ const { key, ...asyncDataOptions } = options;
14
18
  return useAsyncData(
15
- `cms-gql:${query}:${JSON.stringify(variables ?? {})}`,
16
- () => $cmsQuery(query, variables)
19
+ key ?? cmsQueryKey(query, variables),
20
+ () => $cmsQuery(query, variables),
21
+ asyncDataOptions
17
22
  );
18
23
  }
@@ -30,7 +30,12 @@ import {
30
30
  pickTranslatedMedia,
31
31
  translatableFieldKeys
32
32
  } from "../../shared/index.js";
33
- import { blockTypeName, blockUnionName, renderGraphqlSdl, typeName } from "../../shared/graphql-sdl.js";
33
+ import {
34
+ blockTypeName,
35
+ blocksFieldTypeName,
36
+ renderGraphqlSdl,
37
+ typeName
38
+ } from "../../shared/graphql-sdl.js";
34
39
  import { useMediaIndex } from "./media-index.js";
35
40
  import { getContentI18n, resolveTable, tableColumns } from "./registry.js";
36
41
  const MAX_LIMIT = 100;
@@ -285,7 +290,7 @@ function entryResolvers(config, name, entry) {
285
290
  }
286
291
  function blockResolvers(name, key, field) {
287
292
  const resolvers = {};
288
- resolvers[blockUnionName(name, key)] = {
293
+ resolvers[blocksFieldTypeName(name, key)] = {
289
294
  __resolveType: (value) => blockTypeName(name, key, String(value.type))
290
295
  };
291
296
  for (const [blockName, block] of Object.entries(field.blocks ?? {})) {
@@ -1,5 +1,4 @@
1
1
  import type { CmsConfig } from './index.js';
2
- export declare function typeName(name: string): string;
3
- export declare function blockUnionName(entryName: string, fieldKey: string): string;
4
- export declare function blockTypeName(entryName: string, fieldKey: string, blockName: string): string;
2
+ import { blockTypeName, blocksFieldTypeName, typeName } from './index.js';
3
+ export { blockTypeName, blocksFieldTypeName, typeName };
5
4
  export declare function renderGraphqlSdl(config: CmsConfig): string;
@@ -1,13 +1,12 @@
1
- import { isPrivateField, isRequiredField, isTranslatableField } from "./index.js";
2
- export function typeName(name) {
3
- return name.replace(/(?:^|_)([a-z0-9])/gi, (_, c) => c.toUpperCase());
4
- }
5
- export function blockUnionName(entryName, fieldKey) {
6
- return `${typeName(entryName)}${typeName(fieldKey)}Block`;
7
- }
8
- export function blockTypeName(entryName, fieldKey, blockName) {
9
- return `${typeName(entryName)}${typeName(fieldKey)}${typeName(blockName)}`;
10
- }
1
+ import {
2
+ blockTypeName,
3
+ blocksFieldTypeName,
4
+ isPrivateField,
5
+ isRequiredField,
6
+ isTranslatableField,
7
+ typeName
8
+ } from "./index.js";
9
+ export { blockTypeName, blocksFieldTypeName, typeName };
11
10
  function scalarFor(field) {
12
11
  switch (field.type) {
13
12
  case "number":
@@ -58,7 +57,7 @@ function fieldSdl(config, entryName, key, field) {
58
57
  if (field.type === "media") return ` ${key}: CmsMedia`;
59
58
  if (field.type === "select" && field.multiple) return ` ${key}: [String!]!`;
60
59
  if (field.type === "blocks")
61
- return ` ${key}: [${blockUnionName(entryName, key)}!]${isRequiredField(field) ? "!" : ""}`;
60
+ return ` ${key}: [${blocksFieldTypeName(entryName, key)}!]${isRequiredField(field) ? "!" : ""}`;
62
61
  return ` ${key}: ${scalarFor(field)}${isRequiredField(field) ? "!" : ""}`;
63
62
  }
64
63
  function entrySdl(config, name, entry) {
@@ -73,25 +72,44 @@ function entrySdl(config, name, entry) {
73
72
  ${lines.join("\n")}
74
73
  }`;
75
74
  }
75
+ function blockFieldSdl(field) {
76
+ if (field.type === "media") return { base: "CmsMedia", nonNull: false };
77
+ return { base: scalarFor(field), nonNull: !!field.required };
78
+ }
79
+ function sharedBlockFieldsSdl(field) {
80
+ const blocks = Object.values(field.blocks ?? {});
81
+ const first = blocks[0];
82
+ if (!first) return [];
83
+ const lines = [];
84
+ for (const key of Object.keys(first.fields)) {
85
+ const rendered = blocks.map((block) => block.fields[key]);
86
+ if (rendered.some((blockField) => !blockField)) continue;
87
+ const types = rendered.map((blockField) => blockFieldSdl(blockField));
88
+ if (types.some((type) => type.base !== types[0].base)) continue;
89
+ const nonNull = types.every((type) => type.nonNull);
90
+ lines.push(` ${key}: ${types[0].base}${nonNull ? "!" : ""}`);
91
+ }
92
+ return lines;
93
+ }
76
94
  function blocksSdl(name, key, field) {
77
- const defs = [];
78
- const members = [];
79
- for (const [blockName, block] of Object.entries(field.blocks ?? {})) {
80
- const gqlType = blockTypeName(name, key, blockName);
81
- members.push(gqlType);
95
+ const blocks = Object.entries(field.blocks ?? {});
96
+ if (!blocks.length) return [];
97
+ const interfaceName = blocksFieldTypeName(name, key);
98
+ const shared = [" type: String!", ...sharedBlockFieldsSdl(field)];
99
+ const defs = [`interface ${interfaceName} {
100
+ ${shared.join("\n")}
101
+ }`];
102
+ for (const [blockName, block] of blocks) {
82
103
  const lines = [" type: String!"];
83
104
  for (const [blockFieldKey, blockField] of Object.entries(block.fields)) {
84
- if (blockField.type === "media") {
85
- lines.push(` ${blockFieldKey}: CmsMedia`);
86
- continue;
87
- }
88
- lines.push(` ${blockFieldKey}: ${scalarFor(blockField)}${blockField.required ? "!" : ""}`);
105
+ const type = blockFieldSdl(blockField);
106
+ lines.push(` ${blockFieldKey}: ${type.base}${type.nonNull ? "!" : ""}`);
89
107
  }
90
- defs.push(`type ${gqlType} {
108
+ const gqlType = blockTypeName(name, key, blockName);
109
+ defs.push(`type ${gqlType} implements ${interfaceName} {
91
110
  ${lines.join("\n")}
92
111
  }`);
93
112
  }
94
- if (members.length) defs.push(`union ${blockUnionName(name, key)} = ${members.join(" | ")}`);
95
113
  return defs;
96
114
  }
97
115
  function filterSdl(name, entry) {
@@ -119,7 +137,8 @@ const COMMON_SDL = [
119
137
  "input FloatFilter {\n eq: Float\n neq: Float\n gt: Float\n gte: Float\n lt: Float\n lte: Float\n in: [Float!]\n isNull: Boolean\n}",
120
138
  "input StringFilter {\n eq: String\n neq: String\n gt: String\n gte: String\n lt: String\n lte: String\n like: String\n in: [String!]\n isNull: Boolean\n}",
121
139
  "input BooleanFilter {\n eq: Boolean\n neq: Boolean\n isNull: Boolean\n}",
122
- "type CmsMedia {\n key: String!\n url: String\n type: String!\n alt: String\n folder: String\n mime: String\n size: Int\n width: Int\n height: Int\n}"
140
+ "enum CmsMediaType {\n image\n video\n file\n}",
141
+ "type CmsMedia {\n key: String!\n url: String\n type: CmsMediaType!\n alt: String\n folder: String\n mime: String\n size: Int\n width: Int\n height: Int\n}"
123
142
  ];
124
143
  export function renderGraphqlSdl(config) {
125
144
  const queryLines = [];
@@ -98,6 +98,9 @@ export interface CmsEntry {
98
98
  table?: CmsTable;
99
99
  }
100
100
  export type CmsConfig = Record<string, CmsEntry>;
101
+ export declare function typeName(name: string): string;
102
+ export declare function blocksFieldTypeName(entryName: string, fieldKey: string): string;
103
+ export declare function blockTypeName(entryName: string, fieldKey: string, blockName: string): string;
101
104
  interface FieldInputBase {
102
105
  label: string;
103
106
  required?: boolean;
@@ -184,6 +184,15 @@ export function localizeBlocks(field, value, locale, defaultLocale) {
184
184
  if (!Array.isArray(value)) return value;
185
185
  return value.map((item) => localizeBlock(field, item, locale, defaultLocale));
186
186
  }
187
+ export function typeName(name) {
188
+ return name.replace(/(?:^|_)([a-z0-9])/gi, (_, c) => c.toUpperCase());
189
+ }
190
+ export function blocksFieldTypeName(entryName, fieldKey) {
191
+ return `${typeName(entryName)}${typeName(fieldKey)}Block`;
192
+ }
193
+ export function blockTypeName(entryName, fieldKey, blockName) {
194
+ return `${typeName(entryName)}${typeName(fieldKey)}${typeName(blockName)}`;
195
+ }
187
196
  export function defineCmsConfig(config) {
188
197
  return config;
189
198
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xleddyl/nuxt-cms",
3
- "version": "0.1.40",
3
+ "version": "0.1.41",
4
4
  "description": "Lightweight CMS that ships with your Nuxt app: runs on the Nitro server, content types defined in code, /cms admin panel, GraphQL API, SQLite or Postgres. No external CMS needed!",
5
5
  "license": "MIT",
6
6
  "author": "Edoardo Alberti (https://github.com/xleddyl)",