@isardsat/editorial-server 6.13.2 → 6.14.0

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.
@@ -2,12 +2,15 @@ export declare function createSchema(configDirectory: string): Promise<Record<st
2
2
  displayName: string;
3
3
  fields: Record<string, {
4
4
  [x: string]: unknown;
5
- type: "string" | "number" | "boolean" | "url" | "date" | "datetime" | "markdown" | "color" | "select";
5
+ type: "string" | "number" | "boolean" | "url" | "date" | "datetime" | "markdown" | "color" | "select" | "multiselect";
6
6
  displayName: string;
7
7
  optional: boolean;
8
8
  displayExtra?: string | undefined;
9
9
  placeholder?: string | undefined;
10
10
  showInSummary?: boolean | undefined;
11
+ options?: string[] | undefined;
12
+ maxSelectedOptions?: number | undefined;
13
+ minSelectedOptions?: number | undefined;
11
14
  }>;
12
15
  filterBy?: string | undefined;
13
16
  singleton?: boolean | undefined;
@@ -4,12 +4,15 @@ export declare function createStorage(dataDirectory: string): {
4
4
  displayName: string;
5
5
  fields: Record<string, {
6
6
  [x: string]: unknown;
7
- type: "string" | "number" | "boolean" | "url" | "date" | "datetime" | "markdown" | "color" | "select";
7
+ type: "string" | "number" | "boolean" | "url" | "date" | "datetime" | "markdown" | "color" | "select" | "multiselect";
8
8
  displayName: string;
9
9
  optional: boolean;
10
10
  displayExtra?: string | undefined;
11
11
  placeholder?: string | undefined;
12
12
  showInSummary?: boolean | undefined;
13
+ options?: string[] | undefined;
14
+ maxSelectedOptions?: number | undefined;
15
+ minSelectedOptions?: number | undefined;
13
16
  }>;
14
17
  filterBy?: string | undefined;
15
18
  singleton?: boolean | undefined;
@@ -1,5 +1,5 @@
1
1
  import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi";
2
- import { EditorialDataItemSchema, EditorialDataSchema, EditorialSchemaSchema, } from "@isardsat/editorial-common";
2
+ import { EditorialDataItemSchema, EditorialDataSchema, EditorialSchemaSchema, getOptionsReference, } from "@isardsat/editorial-common";
3
3
  function createCache() {
4
4
  let schemaCache = null;
5
5
  const contentCache = new Map();
@@ -36,6 +36,86 @@ function createCache() {
36
36
  },
37
37
  };
38
38
  }
39
+ /**
40
+ * Resolves uploaded file paths to full URLs for an item.
41
+ */
42
+ function resolveFileUrls(item, schema, itemType, origin) {
43
+ const resolvedItem = { ...item };
44
+ const itemSchema = schema[itemType];
45
+ if (!itemSchema)
46
+ return resolvedItem;
47
+ for (const [key, value] of Object.entries(resolvedItem)) {
48
+ if (!itemSchema.fields[key]?.isUploadedFile)
49
+ continue;
50
+ if (typeof value !== "string")
51
+ continue;
52
+ if (value.startsWith("http"))
53
+ continue;
54
+ resolvedItem[key] = `${origin}/${value}`;
55
+ }
56
+ return resolvedItem;
57
+ }
58
+ /**
59
+ * Resolves referenced fields in an item, replacing IDs with full objects.
60
+ * Recursively resolves nested references.
61
+ */
62
+ function resolveReferences(item, schema, itemType, content, origin, resolvedIds = new Set()) {
63
+ const itemIdentifier = `${itemType}:${item.id}`;
64
+ // Prevent circular references
65
+ if (resolvedIds.has(itemIdentifier)) {
66
+ return resolveFileUrls(item, schema, itemType, origin);
67
+ }
68
+ resolvedIds.add(itemIdentifier);
69
+ // First resolve file URLs for the current item
70
+ let resolvedItem = resolveFileUrls(item, schema, itemType, origin);
71
+ const itemSchema = schema[itemType];
72
+ if (!itemSchema)
73
+ return resolvedItem;
74
+ for (const [fieldKey, fieldConfig] of Object.entries(itemSchema.fields)) {
75
+ if (fieldConfig.type !== "select" && fieldConfig.type !== "multiselect") {
76
+ continue;
77
+ }
78
+ const referencedType = getOptionsReference(fieldConfig.options);
79
+ if (!referencedType)
80
+ continue;
81
+ const referencedCollection = content[referencedType];
82
+ if (!referencedCollection)
83
+ continue;
84
+ const fieldValue = item[fieldKey];
85
+ if (fieldConfig.type === "select" && typeof fieldValue === "string") {
86
+ // Single reference - replace ID with full object
87
+ const referencedItem = referencedCollection[fieldValue];
88
+ if (referencedItem) {
89
+ // Recursively resolve nested references
90
+ resolvedItem[fieldKey] = resolveReferences(referencedItem, schema, referencedType, content, origin, new Set(resolvedIds));
91
+ }
92
+ }
93
+ else if (fieldConfig.type === "multiselect" &&
94
+ Array.isArray(fieldValue)) {
95
+ // Multiple references - replace IDs with full objects
96
+ resolvedItem[fieldKey] = fieldValue
97
+ .map((id) => {
98
+ const referencedItem = referencedCollection[id];
99
+ if (!referencedItem)
100
+ return null;
101
+ // Recursively resolve nested references
102
+ return resolveReferences(referencedItem, schema, referencedType, content, origin, new Set(resolvedIds));
103
+ })
104
+ .filter(Boolean);
105
+ }
106
+ }
107
+ return resolvedItem;
108
+ }
109
+ /**
110
+ * Resolves all references in a collection.
111
+ */
112
+ function resolveCollectionReferences(collection, schema, itemType, content, origin) {
113
+ const resolvedCollection = {};
114
+ for (const [itemKey, item] of Object.entries(collection)) {
115
+ resolvedCollection[itemKey] = resolveReferences(item, schema, itemType, content, origin);
116
+ }
117
+ return resolvedCollection;
118
+ }
39
119
  export function createDataRoutes(config, storage) {
40
120
  const app = new OpenAPIHono();
41
121
  const cache = createCache();
@@ -70,6 +150,13 @@ export function createDataRoutes(config, storage) {
70
150
  example: "es_ES",
71
151
  }),
72
152
  preview: z.string().optional(),
153
+ resolve: z
154
+ .string()
155
+ .optional()
156
+ .openapi({
157
+ param: { name: "resolve", in: "query" },
158
+ description: "Resolve referenced fields to full objects",
159
+ }),
73
160
  }),
74
161
  },
75
162
  responses: {
@@ -83,9 +170,19 @@ export function createDataRoutes(config, storage) {
83
170
  },
84
171
  },
85
172
  }), async (c) => {
86
- const { preview } = c.req.valid("query");
173
+ const { preview, resolve } = c.req.valid("query");
87
174
  const content = await cache.getContent(storage, { production: !preview });
88
- return c.json(content);
175
+ if (!resolve) {
176
+ return c.json(content);
177
+ }
178
+ const origin = preview ? new URL(c.req.url).origin : publicFilesUrl;
179
+ // Resolve references for all collections
180
+ const schema = await cache.getSchema(storage);
181
+ const resolvedContent = {};
182
+ for (const [itemType, collection] of Object.entries(content)) {
183
+ resolvedContent[itemType] = resolveCollectionReferences(collection, schema, itemType, content, origin);
184
+ }
185
+ return c.json(resolvedContent);
89
186
  });
90
187
  app.openapi(createRoute({
91
188
  method: "get",
@@ -106,6 +203,13 @@ export function createDataRoutes(config, storage) {
106
203
  example: "es_ES",
107
204
  }),
108
205
  preview: z.string().optional(),
206
+ resolve: z
207
+ .string()
208
+ .optional()
209
+ .openapi({
210
+ param: { name: "resolve", in: "query" },
211
+ description: "Resolve referenced fields to full objects",
212
+ }),
109
213
  }),
110
214
  },
111
215
  responses: {
@@ -123,7 +227,7 @@ export function createDataRoutes(config, storage) {
123
227
  },
124
228
  }), async (c) => {
125
229
  const { itemType } = c.req.valid("param");
126
- const { lang, preview } = c.req.valid("query");
230
+ const { lang, preview, resolve } = c.req.valid("query");
127
231
  const origin = preview ? new URL(c.req.url).origin : publicFilesUrl;
128
232
  const content = await cache.getContent(storage, {
129
233
  production: !preview,
@@ -149,6 +253,11 @@ export function createDataRoutes(config, storage) {
149
253
  }
150
254
  }
151
255
  }
256
+ // Resolve references if requested (includes file URL resolution)
257
+ if (resolve) {
258
+ return c.json(resolveCollectionReferences(collection, schema, itemType, content, origin));
259
+ }
260
+ // Apply file URL resolution for non-resolved requests
152
261
  for (const [itemKey, itemValue] of Object.entries(collection)) {
153
262
  for (const [key, value] of Object.entries(itemValue)) {
154
263
  if (!schema[itemType].fields[key]?.isUploadedFile)
@@ -219,6 +328,13 @@ export function createDataRoutes(config, storage) {
219
328
  example: "es_ES",
220
329
  }),
221
330
  preview: z.string().optional(),
331
+ resolve: z
332
+ .string()
333
+ .optional()
334
+ .openapi({
335
+ param: { name: "resolve", in: "query" },
336
+ description: "Resolve referenced fields to full objects",
337
+ }),
222
338
  }),
223
339
  },
224
340
  responses: {
@@ -236,7 +352,7 @@ export function createDataRoutes(config, storage) {
236
352
  },
237
353
  }), async (c) => {
238
354
  const { itemType, id } = c.req.valid("param");
239
- const { lang, preview } = c.req.valid("query");
355
+ const { lang, preview, resolve } = c.req.valid("query");
240
356
  const origin = preview ? new URL(c.req.url).origin : publicFilesUrl;
241
357
  const content = await cache.getContent(storage, {
242
358
  production: !preview,
@@ -247,7 +363,7 @@ export function createDataRoutes(config, storage) {
247
363
  if (!collection) {
248
364
  return c.notFound();
249
365
  }
250
- const item = collection[id];
366
+ let item = collection[id];
251
367
  if (!item) {
252
368
  return c.notFound();
253
369
  }
@@ -261,6 +377,12 @@ export function createDataRoutes(config, storage) {
261
377
  }
262
378
  }
263
379
  }
380
+ // Resolve references if requested (includes file URL resolution)
381
+ if (resolve) {
382
+ item = resolveReferences(item, schema, itemType, content, origin);
383
+ return c.json(item);
384
+ }
385
+ // Apply file URL resolution for non-resolved requests
264
386
  for (const [key, value] of Object.entries(item)) {
265
387
  if (!schema[itemType].fields[key]?.isUploadedFile)
266
388
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isardsat/editorial-server",
3
- "version": "6.13.2",
3
+ "version": "6.14.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -14,8 +14,8 @@
14
14
  "hono": "^4.9.8",
15
15
  "yaml": "^2.8.1",
16
16
  "zod": "^4.1.11",
17
- "@isardsat/editorial-admin": "^6.13.2",
18
- "@isardsat/editorial-common": "^6.13.2"
17
+ "@isardsat/editorial-common": "^6.14.0",
18
+ "@isardsat/editorial-admin": "^6.14.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@tsconfig/node22": "^22.0.0",