@jay-framework/wix-data 0.15.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.
- package/README.md +151 -0
- package/dist/actions/get-categories.jay-action +12 -0
- package/dist/actions/get-item-by-slug.jay-action +9 -0
- package/dist/actions/query-items.jay-action +19 -0
- package/dist/index.client.js +244 -0
- package/dist/index.d.ts +633 -0
- package/dist/index.js +1561 -0
- package/package.json +63 -0
- package/plugin.yaml +39 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1561 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { collections, items } from "@wix/data";
|
|
4
|
+
import * as yaml from "js-yaml";
|
|
5
|
+
import { createJayService, makeJayStackComponent, RenderPipeline, makeJayQuery, ActionError, makeJayInit, makeContractGenerator } from "@jay-framework/fullstack-component";
|
|
6
|
+
import { registerService, getService } from "@jay-framework/stack-server-runtime";
|
|
7
|
+
import { createJayContext } from "@jay-framework/runtime";
|
|
8
|
+
import "@jay-framework/component";
|
|
9
|
+
import { WIX_CLIENT_SERVICE } from "@jay-framework/wix-server-client";
|
|
10
|
+
const CONFIG_DIR = "config";
|
|
11
|
+
const CONFIG_FILE_NAME$1 = "wix-data.yaml";
|
|
12
|
+
const DOCS_FILE_NAME = "wix-data-collections.md";
|
|
13
|
+
function getConfigPath() {
|
|
14
|
+
return path.join(process.cwd(), CONFIG_DIR, CONFIG_FILE_NAME$1);
|
|
15
|
+
}
|
|
16
|
+
async function loadConfig(wixClient) {
|
|
17
|
+
const configPath = getConfigPath();
|
|
18
|
+
if (fs.existsSync(configPath)) {
|
|
19
|
+
console.log(`[wix-data] Loading config from ${configPath}`);
|
|
20
|
+
return loadConfigFromFile(configPath);
|
|
21
|
+
}
|
|
22
|
+
console.log(`[wix-data] No config found at ${configPath}, generating default...`);
|
|
23
|
+
const { config: defaultConfig, collectionData } = await generateDefaultConfig(wixClient);
|
|
24
|
+
await saveConfig(defaultConfig, configPath);
|
|
25
|
+
const docsPath = path.join(process.cwd(), CONFIG_DIR, DOCS_FILE_NAME);
|
|
26
|
+
await saveDocumentation(collectionData, docsPath);
|
|
27
|
+
return defaultConfig;
|
|
28
|
+
}
|
|
29
|
+
function loadConfigFromFile(configPath) {
|
|
30
|
+
try {
|
|
31
|
+
const fileContent = fs.readFileSync(configPath, "utf-8");
|
|
32
|
+
const parsed = yaml.load(fileContent);
|
|
33
|
+
if (!parsed || !parsed.collections) {
|
|
34
|
+
console.warn("[wix-data] Invalid config file, using empty config");
|
|
35
|
+
return { collections: [] };
|
|
36
|
+
}
|
|
37
|
+
console.log(`[wix-data] Loaded ${parsed.collections.length} collections from config`);
|
|
38
|
+
return parsed;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error("[wix-data] Failed to load config file:", error);
|
|
41
|
+
return { collections: [] };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function saveConfig(config, configPath) {
|
|
45
|
+
try {
|
|
46
|
+
const configDir = path.dirname(configPath);
|
|
47
|
+
if (!fs.existsSync(configDir)) {
|
|
48
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
49
|
+
}
|
|
50
|
+
const yamlContent = generateConfigYaml(config);
|
|
51
|
+
fs.writeFileSync(configPath, yamlContent, "utf-8");
|
|
52
|
+
console.log(`[wix-data] Saved default config to ${configPath}`);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error("[wix-data] Failed to save config file:", error);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function generateConfigYaml(config) {
|
|
58
|
+
const header = `# Wix Data Plugin Configuration
|
|
59
|
+
#
|
|
60
|
+
# This file was auto-generated from your Wix Data collections.
|
|
61
|
+
# Edit this file to configure which collections are visible and how they behave.
|
|
62
|
+
#
|
|
63
|
+
# Collection visibility:
|
|
64
|
+
# visible: false (default) - Collection is hidden, no contracts generated
|
|
65
|
+
# visible: true - Collection is active, contracts will be generated
|
|
66
|
+
#
|
|
67
|
+
# Components (only used when visible: true):
|
|
68
|
+
# itemPage: true - Generate item page component
|
|
69
|
+
# indexPage: true - Generate index/list page component
|
|
70
|
+
# categoryPage: true - Generate category page component
|
|
71
|
+
# tableWidget: true - Generate table widget component
|
|
72
|
+
# cardWidget: true - Generate card widget component
|
|
73
|
+
#
|
|
74
|
+
# References:
|
|
75
|
+
# mode: link - Include only the reference ID (default)
|
|
76
|
+
# mode: embed - Fetch and include full referenced item data
|
|
77
|
+
#
|
|
78
|
+
|
|
79
|
+
`;
|
|
80
|
+
const yamlBody = yaml.dump(config, {
|
|
81
|
+
indent: 2,
|
|
82
|
+
lineWidth: 120,
|
|
83
|
+
noRefs: true,
|
|
84
|
+
sortKeys: false
|
|
85
|
+
});
|
|
86
|
+
return header + yamlBody;
|
|
87
|
+
}
|
|
88
|
+
async function generateDefaultConfig(wixClient) {
|
|
89
|
+
const collectionsClient = wixClient.use(collections);
|
|
90
|
+
try {
|
|
91
|
+
const result = await collectionsClient.listDataCollections({});
|
|
92
|
+
const dataCollections = result.collections || [];
|
|
93
|
+
console.log(`[wix-data] Found ${dataCollections.length} collections from Wix API`);
|
|
94
|
+
const userCollections = dataCollections.filter((c) => c._id && !c._id.startsWith("_"));
|
|
95
|
+
const collectionData = [];
|
|
96
|
+
const collectionConfigs = [];
|
|
97
|
+
userCollections.forEach((collection) => {
|
|
98
|
+
const collectionId = collection._id;
|
|
99
|
+
const fields = collection.fields || [];
|
|
100
|
+
const referenceFields = fields.filter(
|
|
101
|
+
(f) => f.type === "REFERENCE" || f.type === "MULTI_REFERENCE"
|
|
102
|
+
);
|
|
103
|
+
const references = referenceFields.map((f) => ({
|
|
104
|
+
fieldName: f.key || "",
|
|
105
|
+
mode: "link"
|
|
106
|
+
}));
|
|
107
|
+
const docReferences = referenceFields.map((f) => {
|
|
108
|
+
const meta = f;
|
|
109
|
+
const targetCollection = meta.typeMetadata?.reference?.referencedCollectionId || meta.typeMetadata?.multiReference?.referencedCollectionId || "unknown";
|
|
110
|
+
return {
|
|
111
|
+
fieldName: f.key || "",
|
|
112
|
+
fieldDisplayName: f.displayName || f.key || "",
|
|
113
|
+
targetCollection,
|
|
114
|
+
isMulti: f.type === "MULTI_REFERENCE"
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
collectionData.push({
|
|
118
|
+
id: collectionId,
|
|
119
|
+
displayName: collection.displayName || collectionId,
|
|
120
|
+
fields: fields.map((f) => ({
|
|
121
|
+
key: f.key || "",
|
|
122
|
+
displayName: f.displayName || f.key || "",
|
|
123
|
+
type: f.type || "unknown"
|
|
124
|
+
})),
|
|
125
|
+
references: docReferences
|
|
126
|
+
});
|
|
127
|
+
const slugField = fields.find((f) => f.key === "slug")?.key || fields.find((f) => f.key === "title")?.key || "_id";
|
|
128
|
+
const config = {
|
|
129
|
+
collectionId,
|
|
130
|
+
visible: false,
|
|
131
|
+
pathPrefix: `/${collectionId.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`,
|
|
132
|
+
slugField,
|
|
133
|
+
components: {}
|
|
134
|
+
};
|
|
135
|
+
if (references.length > 0) {
|
|
136
|
+
config.references = references;
|
|
137
|
+
}
|
|
138
|
+
collectionConfigs.push(config);
|
|
139
|
+
});
|
|
140
|
+
console.log(
|
|
141
|
+
`[wix-data] Generated config for ${collectionConfigs.length} collections (all hidden by default)`
|
|
142
|
+
);
|
|
143
|
+
return {
|
|
144
|
+
config: { collections: collectionConfigs },
|
|
145
|
+
collectionData
|
|
146
|
+
};
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error("[wix-data] Failed to fetch collections from Wix Data API:", error);
|
|
149
|
+
return {
|
|
150
|
+
config: { collections: [] },
|
|
151
|
+
collectionData: []
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async function saveDocumentation(collectionData, docsPath) {
|
|
156
|
+
if (collectionData.length === 0) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const markdown = generateDocumentationMarkdown(collectionData);
|
|
161
|
+
fs.writeFileSync(docsPath, markdown, "utf-8");
|
|
162
|
+
console.log(`[wix-data] Saved collection documentation to ${docsPath}`);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error("[wix-data] Failed to save documentation file:", error);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function generateDocumentationMarkdown(collectionData) {
|
|
168
|
+
const sections = [];
|
|
169
|
+
sections.push(`# Wix Data Collections
|
|
170
|
+
|
|
171
|
+
This document was auto-generated from your Wix Data collections.
|
|
172
|
+
It provides an overview of your data model and relationships.
|
|
173
|
+
|
|
174
|
+
Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
`);
|
|
178
|
+
sections.push(`## Collections Overview
|
|
179
|
+
|
|
180
|
+
| Collection | Display Name | Fields | References |
|
|
181
|
+
|------------|--------------|--------|------------|
|
|
182
|
+
${collectionData.map((c) => `| ${c.id} | ${c.displayName} | ${c.fields.length} | ${c.references.length} |`).join("\n")}
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
`);
|
|
186
|
+
sections.push(`## Relationships Diagram
|
|
187
|
+
|
|
188
|
+
\`\`\`mermaid
|
|
189
|
+
flowchart LR
|
|
190
|
+
${generateMermaidDiagram(collectionData)}
|
|
191
|
+
\`\`\`
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
`);
|
|
195
|
+
sections.push(`## Collection Details
|
|
196
|
+
`);
|
|
197
|
+
collectionData.forEach((collection) => {
|
|
198
|
+
sections.push(`### ${collection.displayName} (\`${collection.id}\`)
|
|
199
|
+
|
|
200
|
+
**Fields:**
|
|
201
|
+
|
|
202
|
+
| Field | Display Name | Type |
|
|
203
|
+
|-------|--------------|------|
|
|
204
|
+
${collection.fields.map((f) => `| ${f.key} | ${f.displayName} | ${f.type} |`).join("\n")}
|
|
205
|
+
`);
|
|
206
|
+
if (collection.references.length > 0) {
|
|
207
|
+
sections.push(`
|
|
208
|
+
**References:**
|
|
209
|
+
|
|
210
|
+
| Field | Target Collection | Type |
|
|
211
|
+
|-------|-------------------|------|
|
|
212
|
+
${collection.references.map(
|
|
213
|
+
(r) => `| ${r.fieldName} (${r.fieldDisplayName}) | ${r.targetCollection} | ${r.isMulti ? "Multi-Reference" : "Reference"} |`
|
|
214
|
+
).join("\n")}
|
|
215
|
+
`);
|
|
216
|
+
}
|
|
217
|
+
sections.push(`
|
|
218
|
+
---
|
|
219
|
+
`);
|
|
220
|
+
});
|
|
221
|
+
return sections.join("\n");
|
|
222
|
+
}
|
|
223
|
+
function generateMermaidDiagram(collectionData) {
|
|
224
|
+
const lines = [];
|
|
225
|
+
const collectionIds = new Set(collectionData.map((c) => c.id));
|
|
226
|
+
collectionData.forEach((collection) => {
|
|
227
|
+
const nodeId = sanitizeMermaidId(collection.id);
|
|
228
|
+
const label = sanitizeMermaidLabel(collection.displayName || collection.id);
|
|
229
|
+
lines.push(` ${nodeId}["${label}"]`);
|
|
230
|
+
});
|
|
231
|
+
lines.push("");
|
|
232
|
+
collectionData.forEach((collection) => {
|
|
233
|
+
const sourceId = sanitizeMermaidId(collection.id);
|
|
234
|
+
collection.references.forEach((ref) => {
|
|
235
|
+
if (collectionIds.has(ref.targetCollection)) {
|
|
236
|
+
const targetId = sanitizeMermaidId(ref.targetCollection);
|
|
237
|
+
const arrow = ref.isMulti ? "-->|*|" : "-->|1|";
|
|
238
|
+
ref.fieldDisplayName || ref.fieldName;
|
|
239
|
+
lines.push(` ${sourceId} ${arrow} ${targetId}`);
|
|
240
|
+
lines.push(
|
|
241
|
+
` linkStyle ${lines.filter((l) => l.includes("-->")).length - 1} stroke:#666`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
const filteredLines = lines.filter((l) => !l.includes("linkStyle"));
|
|
247
|
+
return filteredLines.join("\n");
|
|
248
|
+
}
|
|
249
|
+
function sanitizeMermaidId(id) {
|
|
250
|
+
return id.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
251
|
+
}
|
|
252
|
+
function sanitizeMermaidLabel(label) {
|
|
253
|
+
return label.replace(/"/g, "'").replace(/\[/g, "(").replace(/\]/g, ")").replace(/[{}]/g, "").replace(/[<>]/g, "");
|
|
254
|
+
}
|
|
255
|
+
function validateCollectionConfig(config) {
|
|
256
|
+
const errors = [];
|
|
257
|
+
if (!config.collectionId) {
|
|
258
|
+
errors.push("collectionId is required");
|
|
259
|
+
}
|
|
260
|
+
if (!config.pathPrefix) {
|
|
261
|
+
errors.push("pathPrefix is required");
|
|
262
|
+
} else if (!config.pathPrefix.startsWith("/")) {
|
|
263
|
+
errors.push("pathPrefix must start with /");
|
|
264
|
+
}
|
|
265
|
+
if (!config.slugField) {
|
|
266
|
+
errors.push("slugField is required");
|
|
267
|
+
}
|
|
268
|
+
if (!config.components) {
|
|
269
|
+
errors.push("components configuration is required");
|
|
270
|
+
}
|
|
271
|
+
if (config.category) {
|
|
272
|
+
if (!config.category.referenceField) {
|
|
273
|
+
errors.push("category.referenceField is required when category is configured");
|
|
274
|
+
}
|
|
275
|
+
if (!config.category.categorySlugField) {
|
|
276
|
+
errors.push("category.categorySlugField is required when category is configured");
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return errors;
|
|
280
|
+
}
|
|
281
|
+
function validateConfig(config) {
|
|
282
|
+
const errors = [];
|
|
283
|
+
if (!config.collections || config.collections.length === 0) {
|
|
284
|
+
errors.push("At least one collection must be configured");
|
|
285
|
+
return errors;
|
|
286
|
+
}
|
|
287
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
288
|
+
config.collections.forEach((collection) => {
|
|
289
|
+
if (seenIds.has(collection.collectionId)) {
|
|
290
|
+
errors.push(`Duplicate collectionId: ${collection.collectionId}`);
|
|
291
|
+
}
|
|
292
|
+
seenIds.add(collection.collectionId);
|
|
293
|
+
const collectionErrors = validateCollectionConfig(collection);
|
|
294
|
+
errors.push(...collectionErrors.map((e) => `${collection.collectionId}: ${e}`));
|
|
295
|
+
});
|
|
296
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
297
|
+
config.collections.filter((c) => c.visible).forEach((collection) => {
|
|
298
|
+
if (seenPaths.has(collection.pathPrefix)) {
|
|
299
|
+
errors.push(`Duplicate pathPrefix: ${collection.pathPrefix}`);
|
|
300
|
+
}
|
|
301
|
+
seenPaths.add(collection.pathPrefix);
|
|
302
|
+
});
|
|
303
|
+
return errors;
|
|
304
|
+
}
|
|
305
|
+
function getVisibleCollections(config) {
|
|
306
|
+
return config.collections.filter((c) => c.visible === true);
|
|
307
|
+
}
|
|
308
|
+
function mapWixTypeToJayType(wixType) {
|
|
309
|
+
const typeMap = {
|
|
310
|
+
TEXT: "string",
|
|
311
|
+
NUMBER: "number",
|
|
312
|
+
BOOLEAN: "boolean",
|
|
313
|
+
DATE: "string",
|
|
314
|
+
DATETIME: "string",
|
|
315
|
+
TIME: "string",
|
|
316
|
+
RICH_TEXT: "string",
|
|
317
|
+
RICH_CONTENT: "string",
|
|
318
|
+
URL: "string",
|
|
319
|
+
DOCUMENT: "string",
|
|
320
|
+
ARRAY: "string",
|
|
321
|
+
OBJECT: "string",
|
|
322
|
+
TAGS: "string"
|
|
323
|
+
};
|
|
324
|
+
return typeMap[wixType] || "string";
|
|
325
|
+
}
|
|
326
|
+
function getFieldCategory(key, wixType) {
|
|
327
|
+
if (key.startsWith("_")) return "system";
|
|
328
|
+
switch (wixType) {
|
|
329
|
+
case "IMAGE":
|
|
330
|
+
return "image";
|
|
331
|
+
case "VIDEO":
|
|
332
|
+
case "AUDIO":
|
|
333
|
+
return "media";
|
|
334
|
+
case "ADDRESS":
|
|
335
|
+
return "address";
|
|
336
|
+
case "REFERENCE":
|
|
337
|
+
return "reference";
|
|
338
|
+
case "MULTI_REFERENCE":
|
|
339
|
+
return "multiReference";
|
|
340
|
+
case "RICH_TEXT":
|
|
341
|
+
case "RICH_CONTENT":
|
|
342
|
+
return "richContent";
|
|
343
|
+
default:
|
|
344
|
+
return "simple";
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function getReferencedCollectionId(field) {
|
|
348
|
+
const metadata = field.typeMetadata;
|
|
349
|
+
if (!metadata) return void 0;
|
|
350
|
+
if (field.type === "REFERENCE") {
|
|
351
|
+
return metadata.reference?.referencedCollectionId;
|
|
352
|
+
}
|
|
353
|
+
if (field.type === "MULTI_REFERENCE") {
|
|
354
|
+
return metadata.multiReference?.referencedCollectionId;
|
|
355
|
+
}
|
|
356
|
+
return void 0;
|
|
357
|
+
}
|
|
358
|
+
async function processSchema(collection, config, collectionFetcher) {
|
|
359
|
+
const refConfigMap = new Map((config.references || []).map((r) => [r.fieldName, r]));
|
|
360
|
+
const fields = await Promise.all(
|
|
361
|
+
(collection.fields || []).map(async (f) => {
|
|
362
|
+
const key = f.key || "";
|
|
363
|
+
const wixType = f.type || "TEXT";
|
|
364
|
+
const category = getFieldCategory(key, wixType);
|
|
365
|
+
const refConfig = refConfigMap.get(key);
|
|
366
|
+
const isEmbedded = (category === "reference" || category === "multiReference") && refConfig?.mode === "embed";
|
|
367
|
+
const field = {
|
|
368
|
+
key,
|
|
369
|
+
displayName: f.displayName,
|
|
370
|
+
jayType: mapWixTypeToJayType(wixType),
|
|
371
|
+
wixType,
|
|
372
|
+
category,
|
|
373
|
+
embedded: isEmbedded,
|
|
374
|
+
referenceConfig: isEmbedded ? refConfig : void 0
|
|
375
|
+
};
|
|
376
|
+
if (isEmbedded && collectionFetcher && refConfig) {
|
|
377
|
+
const referencedCollectionId = getReferencedCollectionId(f);
|
|
378
|
+
if (referencedCollectionId) {
|
|
379
|
+
const referencedCollection = await collectionFetcher(referencedCollectionId);
|
|
380
|
+
if (referencedCollection) {
|
|
381
|
+
const nestedConfig = {
|
|
382
|
+
collectionId: referencedCollection._id || referencedCollectionId,
|
|
383
|
+
pathPrefix: "",
|
|
384
|
+
slugField: "",
|
|
385
|
+
references: refConfig.references,
|
|
386
|
+
// Pass nested references
|
|
387
|
+
components: {}
|
|
388
|
+
};
|
|
389
|
+
field.embeddedSchema = await processSchema(
|
|
390
|
+
referencedCollection,
|
|
391
|
+
nestedConfig,
|
|
392
|
+
collectionFetcher
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return field;
|
|
398
|
+
})
|
|
399
|
+
);
|
|
400
|
+
return {
|
|
401
|
+
collectionId: collection._id || config.collectionId,
|
|
402
|
+
displayName: collection.displayName,
|
|
403
|
+
config,
|
|
404
|
+
fields,
|
|
405
|
+
hasCategory: !!config.category,
|
|
406
|
+
category: config.category
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
const WIX_DATA_SERVICE_MARKER = createJayService("Wix Data Service");
|
|
410
|
+
let itemsClientInstance;
|
|
411
|
+
let collectionsClientInstance;
|
|
412
|
+
function getItemsClient(wixClient) {
|
|
413
|
+
if (!itemsClientInstance) {
|
|
414
|
+
itemsClientInstance = wixClient.use(items);
|
|
415
|
+
}
|
|
416
|
+
return itemsClientInstance;
|
|
417
|
+
}
|
|
418
|
+
function getCollectionsClient(wixClient) {
|
|
419
|
+
if (!collectionsClientInstance) {
|
|
420
|
+
collectionsClientInstance = wixClient.use(collections);
|
|
421
|
+
}
|
|
422
|
+
return collectionsClientInstance;
|
|
423
|
+
}
|
|
424
|
+
function provideWixDataService(wixClient, config) {
|
|
425
|
+
const itemsClient = getItemsClient(wixClient);
|
|
426
|
+
const collectionsClient = getCollectionsClient(wixClient);
|
|
427
|
+
const collectionCache = /* @__PURE__ */ new Map();
|
|
428
|
+
const processedSchemaCache = /* @__PURE__ */ new Map();
|
|
429
|
+
async function fetchCollection(collectionId) {
|
|
430
|
+
if (collectionCache.has(collectionId)) {
|
|
431
|
+
return collectionCache.get(collectionId) || null;
|
|
432
|
+
}
|
|
433
|
+
try {
|
|
434
|
+
const collection = await collectionsClient.getDataCollection(collectionId);
|
|
435
|
+
if (!collection) {
|
|
436
|
+
collectionCache.set(collectionId, null);
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
collectionCache.set(collectionId, collection);
|
|
440
|
+
return collection;
|
|
441
|
+
} catch (error) {
|
|
442
|
+
console.error(`[wix-data] Failed to fetch collection ${collectionId}:`, error);
|
|
443
|
+
collectionCache.set(collectionId, null);
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
const service = {
|
|
448
|
+
items: itemsClient,
|
|
449
|
+
collections: collectionsClient,
|
|
450
|
+
config,
|
|
451
|
+
getCollectionConfig(collectionId) {
|
|
452
|
+
return config.collections.find((c) => c.collectionId === collectionId);
|
|
453
|
+
},
|
|
454
|
+
async getCollection(collectionId) {
|
|
455
|
+
return fetchCollection(collectionId);
|
|
456
|
+
},
|
|
457
|
+
async getProcessedSchema(collectionId) {
|
|
458
|
+
if (processedSchemaCache.has(collectionId)) {
|
|
459
|
+
return processedSchemaCache.get(collectionId) || null;
|
|
460
|
+
}
|
|
461
|
+
const collectionConfig = config.collections.find(
|
|
462
|
+
(c) => c.collectionId === collectionId
|
|
463
|
+
);
|
|
464
|
+
if (!collectionConfig) {
|
|
465
|
+
processedSchemaCache.set(collectionId, null);
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
const collection = await fetchCollection(collectionId);
|
|
469
|
+
if (!collection) {
|
|
470
|
+
console.warn(`[wix-data] Collection not found: ${collectionId}`);
|
|
471
|
+
processedSchemaCache.set(collectionId, null);
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
const processed = await processSchema(collection, collectionConfig, fetchCollection);
|
|
475
|
+
processedSchemaCache.set(collectionId, processed);
|
|
476
|
+
return processed;
|
|
477
|
+
},
|
|
478
|
+
async getProcessedSchemas(filter) {
|
|
479
|
+
const matchingConfigs = config.collections.filter(filter);
|
|
480
|
+
const results = await Promise.all(
|
|
481
|
+
matchingConfigs.map((c) => service.getProcessedSchema(c.collectionId))
|
|
482
|
+
);
|
|
483
|
+
return results.filter((s) => s !== null);
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
registerService(WIX_DATA_SERVICE_MARKER, service);
|
|
487
|
+
return service;
|
|
488
|
+
}
|
|
489
|
+
const WIX_DATA_CONTEXT = createJayContext();
|
|
490
|
+
async function* loadItemParams(services) {
|
|
491
|
+
const [wixData, contractInfo] = services;
|
|
492
|
+
if (!contractInfo?.metadata) {
|
|
493
|
+
console.warn("[wix-data] loadItemParams called without contract metadata");
|
|
494
|
+
yield [];
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
const { collectionId } = contractInfo.metadata;
|
|
498
|
+
const config = wixData.getCollectionConfig(collectionId);
|
|
499
|
+
if (!config) {
|
|
500
|
+
console.error(`[wix-data] No config found for collection: ${collectionId}`);
|
|
501
|
+
yield [];
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
try {
|
|
505
|
+
const result = await wixData.items.query(collectionId).find();
|
|
506
|
+
yield result.items.map((item) => ({
|
|
507
|
+
slug: item[config.slugField] || item._id
|
|
508
|
+
}));
|
|
509
|
+
} catch (error) {
|
|
510
|
+
console.error(`[wix-data] Failed to load slugs for ${collectionId}:`, error);
|
|
511
|
+
yield [];
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
async function renderSlowlyChanging$3(props, wixData) {
|
|
515
|
+
const { collectionId } = props.metadata;
|
|
516
|
+
const Pipeline = RenderPipeline.for();
|
|
517
|
+
return Pipeline.try(async () => {
|
|
518
|
+
const config = wixData.getCollectionConfig(collectionId);
|
|
519
|
+
if (!config) {
|
|
520
|
+
throw new Error(`Collection not configured: ${collectionId}`);
|
|
521
|
+
}
|
|
522
|
+
const result = await wixData.items.query(collectionId).eq(config.slugField, props.slug).find();
|
|
523
|
+
if (!result.items.length) {
|
|
524
|
+
throw new Error("Item not found");
|
|
525
|
+
}
|
|
526
|
+
const item = result.items[0];
|
|
527
|
+
const viewState = await mapItemToViewState$1(item, config, wixData);
|
|
528
|
+
return { item, viewState, collectionId };
|
|
529
|
+
}).recover((error) => {
|
|
530
|
+
console.error(`[wix-data] Item not found: ${props.slug}`, error);
|
|
531
|
+
return Pipeline.clientError(404, "Item not found");
|
|
532
|
+
}).toPhaseOutput(({ item, viewState, collectionId: colId }) => ({
|
|
533
|
+
viewState,
|
|
534
|
+
carryForward: {
|
|
535
|
+
itemId: item._id,
|
|
536
|
+
collectionId: colId
|
|
537
|
+
}
|
|
538
|
+
}));
|
|
539
|
+
}
|
|
540
|
+
async function mapItemToViewState$1(item, config, wixData) {
|
|
541
|
+
const processableEntries = Object.entries(item).filter(([key]) => !key.startsWith("_") || key === "_id");
|
|
542
|
+
const mappedEntries = await Promise.all(processableEntries.map(async ([key, value]) => {
|
|
543
|
+
const refConfig = config.references?.find((r) => r.fieldName === key);
|
|
544
|
+
if (refConfig?.mode === "embed" && value) {
|
|
545
|
+
return [key, await fetchReference(value, wixData)];
|
|
546
|
+
} else if (isImageValue$2(value)) {
|
|
547
|
+
return [key, mapImageValue(value)];
|
|
548
|
+
} else {
|
|
549
|
+
return [key, value];
|
|
550
|
+
}
|
|
551
|
+
}));
|
|
552
|
+
return {
|
|
553
|
+
_id: item._id,
|
|
554
|
+
...Object.fromEntries(mappedEntries)
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
function isImageValue$2(value) {
|
|
558
|
+
return typeof value === "object" && value !== null && ("src" in value || "url" in value);
|
|
559
|
+
}
|
|
560
|
+
function mapImageValue(value) {
|
|
561
|
+
return {
|
|
562
|
+
url: value.src || value.url || "",
|
|
563
|
+
altText: value.alt || "",
|
|
564
|
+
width: value.width,
|
|
565
|
+
height: value.height
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
async function fetchReference(refValue, wixData) {
|
|
569
|
+
try {
|
|
570
|
+
if (Array.isArray(refValue)) {
|
|
571
|
+
const items2 = await Promise.all(refValue.map(async (id) => {
|
|
572
|
+
try {
|
|
573
|
+
const result = await wixData.items.get("", id);
|
|
574
|
+
return result.dataItem ? {
|
|
575
|
+
_id: result.dataItem._id,
|
|
576
|
+
...result.dataItem.data
|
|
577
|
+
} : null;
|
|
578
|
+
} catch {
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
}));
|
|
582
|
+
return items2.filter(Boolean);
|
|
583
|
+
} else if (typeof refValue === "string") {
|
|
584
|
+
const result = await wixData.items.get("", refValue);
|
|
585
|
+
return result.dataItem ? {
|
|
586
|
+
_id: result.dataItem._id,
|
|
587
|
+
...result.dataItem.data
|
|
588
|
+
} : null;
|
|
589
|
+
}
|
|
590
|
+
} catch (error) {
|
|
591
|
+
console.error("[wix-data] Failed to fetch reference:", error);
|
|
592
|
+
}
|
|
593
|
+
return null;
|
|
594
|
+
}
|
|
595
|
+
const collectionItem = makeJayStackComponent().withProps().withServices(WIX_DATA_SERVICE_MARKER).withLoadParams(loadItemParams).withSlowlyRender(renderSlowlyChanging$3);
|
|
596
|
+
function parseWixMediaUrl(url) {
|
|
597
|
+
if (!url) return null;
|
|
598
|
+
const match = url.match(
|
|
599
|
+
/^wix:(image|video|document|audio):\/\/v1\/([^/]+)\/([^#]+)(?:#(.*))?$/
|
|
600
|
+
);
|
|
601
|
+
if (!match) return null;
|
|
602
|
+
const [, type, mediaId, fileName, hashParams] = match;
|
|
603
|
+
const result = {
|
|
604
|
+
type,
|
|
605
|
+
mediaId,
|
|
606
|
+
fileName: decodeURIComponent(fileName)
|
|
607
|
+
};
|
|
608
|
+
if (hashParams) {
|
|
609
|
+
const params = new URLSearchParams(hashParams);
|
|
610
|
+
const originWidth = params.get("originWidth");
|
|
611
|
+
const originHeight = params.get("originHeight");
|
|
612
|
+
if (originWidth) result.originWidth = parseInt(originWidth, 10);
|
|
613
|
+
if (originHeight) result.originHeight = parseInt(originHeight, 10);
|
|
614
|
+
const posterUri = params.get("posterUri");
|
|
615
|
+
const posterWidth = params.get("posterWidth");
|
|
616
|
+
const posterHeight = params.get("posterHeight");
|
|
617
|
+
if (posterUri) result.posterUri = decodeURIComponent(posterUri);
|
|
618
|
+
if (posterWidth) result.posterWidth = parseInt(posterWidth, 10);
|
|
619
|
+
if (posterHeight) result.posterHeight = parseInt(posterHeight, 10);
|
|
620
|
+
}
|
|
621
|
+
return result;
|
|
622
|
+
}
|
|
623
|
+
function formatWixMediaUrl(_id, url, resize) {
|
|
624
|
+
const resizeFragment = "";
|
|
625
|
+
if (url == null ? void 0 : url.startsWith("wix:")) {
|
|
626
|
+
const parsed = parseWixMediaUrl(url);
|
|
627
|
+
if (parsed) {
|
|
628
|
+
return `https://static.wixstatic.com/media/${parsed.mediaId}${resizeFragment}`;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
if ((url == null ? void 0 : url.startsWith("http://")) || (url == null ? void 0 : url.startsWith("https://"))) {
|
|
632
|
+
return url;
|
|
633
|
+
}
|
|
634
|
+
return "";
|
|
635
|
+
}
|
|
636
|
+
function getComponentFields(config) {
|
|
637
|
+
if (config === void 0 || config === false) return void 0;
|
|
638
|
+
if (config === true) return void 0;
|
|
639
|
+
return config.fields;
|
|
640
|
+
}
|
|
641
|
+
function isComponentEnabled(config) {
|
|
642
|
+
if (config === void 0 || config === false) return false;
|
|
643
|
+
return true;
|
|
644
|
+
}
|
|
645
|
+
const queryItems = makeJayQuery("wixData.queryItems").withServices(WIX_DATA_SERVICE_MARKER).withHandler(
|
|
646
|
+
async (input, wixData) => {
|
|
647
|
+
const {
|
|
648
|
+
collectionId,
|
|
649
|
+
limit = 20,
|
|
650
|
+
offset = 0,
|
|
651
|
+
sortField,
|
|
652
|
+
sortDirection = "ASC",
|
|
653
|
+
filter = {},
|
|
654
|
+
categoryId,
|
|
655
|
+
categoryField
|
|
656
|
+
} = input;
|
|
657
|
+
try {
|
|
658
|
+
let query = wixData.items.query(collectionId).limit(limit).skip(offset);
|
|
659
|
+
if (sortField) {
|
|
660
|
+
query = sortDirection === "DESC" ? query.descending(sortField) : query.ascending(sortField);
|
|
661
|
+
}
|
|
662
|
+
if (categoryId && categoryField) {
|
|
663
|
+
query = query.hasSome(categoryField, [categoryId]);
|
|
664
|
+
}
|
|
665
|
+
query = Object.entries(filter).filter(([, value]) => value !== void 0 && value !== null).reduce((q, [field, value]) => q.eq(field, value), query);
|
|
666
|
+
const result = await query.find();
|
|
667
|
+
const totalCount = result.totalCount ?? result.items.length;
|
|
668
|
+
return {
|
|
669
|
+
items: result.items,
|
|
670
|
+
totalCount,
|
|
671
|
+
offset,
|
|
672
|
+
hasMore: offset + result.items.length < totalCount
|
|
673
|
+
};
|
|
674
|
+
} catch (error) {
|
|
675
|
+
console.error("[wixData.queryItems] Query failed:", error);
|
|
676
|
+
throw new ActionError("QUERY_FAILED", "Failed to query items");
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
);
|
|
680
|
+
const getItemBySlug = makeJayQuery("wixData.getItemBySlug").withServices(WIX_DATA_SERVICE_MARKER).withCaching({ maxAge: 300, staleWhileRevalidate: 600 }).withHandler(
|
|
681
|
+
async (input, wixData) => {
|
|
682
|
+
const { collectionId, slug } = input;
|
|
683
|
+
if (!slug) {
|
|
684
|
+
throw new ActionError("INVALID_INPUT", "Slug is required");
|
|
685
|
+
}
|
|
686
|
+
const config = wixData.getCollectionConfig(collectionId);
|
|
687
|
+
if (!config) {
|
|
688
|
+
throw new ActionError(
|
|
689
|
+
"INVALID_CONFIG",
|
|
690
|
+
`Collection not configured: ${collectionId}`
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
try {
|
|
694
|
+
const result = await wixData.items.query(collectionId).eq(config.slugField, slug).find();
|
|
695
|
+
if (!result.items.length) {
|
|
696
|
+
return { item: null };
|
|
697
|
+
}
|
|
698
|
+
return { item: result.items[0] };
|
|
699
|
+
} catch (error) {
|
|
700
|
+
console.error("[wixData.getItemBySlug] Failed to get item:", error);
|
|
701
|
+
return { item: null };
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
);
|
|
705
|
+
const getCategories = makeJayQuery("wixData.getCategories").withServices(WIX_DATA_SERVICE_MARKER).withCaching({ maxAge: 3600 }).withHandler(
|
|
706
|
+
async (input, wixData) => {
|
|
707
|
+
const { collectionId } = input;
|
|
708
|
+
const config = wixData.getCollectionConfig(collectionId);
|
|
709
|
+
if (!config) {
|
|
710
|
+
throw new ActionError(
|
|
711
|
+
"INVALID_CONFIG",
|
|
712
|
+
`Collection not configured: ${collectionId}`
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
if (!config.category) {
|
|
716
|
+
return { categories: [] };
|
|
717
|
+
}
|
|
718
|
+
try {
|
|
719
|
+
const result = await wixData.items.query(collectionId).limit(1e3).find();
|
|
720
|
+
const categoryCounts = /* @__PURE__ */ new Map();
|
|
721
|
+
const categoryIds = /* @__PURE__ */ new Set();
|
|
722
|
+
result.items.forEach((item) => {
|
|
723
|
+
const catValue = item[config.category.referenceField];
|
|
724
|
+
const catIds = Array.isArray(catValue) ? catValue : typeof catValue === "string" ? [catValue] : [];
|
|
725
|
+
catIds.forEach((catId) => {
|
|
726
|
+
categoryIds.add(catId);
|
|
727
|
+
categoryCounts.set(catId, (categoryCounts.get(catId) || 0) + 1);
|
|
728
|
+
});
|
|
729
|
+
});
|
|
730
|
+
const categoryResults = await Promise.all(
|
|
731
|
+
Array.from(categoryIds).map(async (catId) => {
|
|
732
|
+
try {
|
|
733
|
+
const catResult = await wixData.items.get("", catId);
|
|
734
|
+
const catData = catResult.dataItem?.data;
|
|
735
|
+
if (!catData) return null;
|
|
736
|
+
return {
|
|
737
|
+
_id: catId,
|
|
738
|
+
slug: catData[config.category.categorySlugField] || catId,
|
|
739
|
+
title: catData.title || catData.name || catId,
|
|
740
|
+
itemCount: categoryCounts.get(catId) || 0
|
|
741
|
+
};
|
|
742
|
+
} catch {
|
|
743
|
+
return null;
|
|
744
|
+
}
|
|
745
|
+
})
|
|
746
|
+
);
|
|
747
|
+
const categories = categoryResults.filter((c) => c !== null);
|
|
748
|
+
categories.sort((a, b) => b.itemCount - a.itemCount);
|
|
749
|
+
return { categories };
|
|
750
|
+
} catch (error) {
|
|
751
|
+
console.error("[wixData.getCategories] Failed to load categories:", error);
|
|
752
|
+
throw new ActionError("LOAD_FAILED", "Failed to load categories");
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
);
|
|
756
|
+
const PAGE_SIZE = 20;
|
|
757
|
+
async function* loadListParams(services) {
|
|
758
|
+
const [wixData, contractInfo] = services;
|
|
759
|
+
if (!contractInfo?.metadata) {
|
|
760
|
+
console.warn("[wix-data] loadListParams called without contract metadata");
|
|
761
|
+
yield [];
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
const { collectionId } = contractInfo.metadata;
|
|
765
|
+
const config = wixData.getCollectionConfig(collectionId);
|
|
766
|
+
if (!config) {
|
|
767
|
+
console.error(`[wix-data] No config found for collection: ${collectionId}`);
|
|
768
|
+
yield [];
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
const params = [];
|
|
772
|
+
if (isComponentEnabled(config.components.indexPage)) {
|
|
773
|
+
params.push({});
|
|
774
|
+
}
|
|
775
|
+
if (isComponentEnabled(config.components.categoryPage) && config.category) {
|
|
776
|
+
try {
|
|
777
|
+
const result = await wixData.items.query(collectionId).limit(1e3).find();
|
|
778
|
+
const categoryIds = /* @__PURE__ */ new Set();
|
|
779
|
+
result.items.forEach((item) => {
|
|
780
|
+
const catValue = item[config.category.referenceField];
|
|
781
|
+
const catIds = Array.isArray(catValue) ? catValue : typeof catValue === "string" ? [catValue] : [];
|
|
782
|
+
catIds.forEach((id) => categoryIds.add(id));
|
|
783
|
+
});
|
|
784
|
+
const categoryResults = await Promise.all(Array.from(categoryIds).map(async (catId) => {
|
|
785
|
+
try {
|
|
786
|
+
const catResult = await wixData.items.get("", catId);
|
|
787
|
+
return catResult.dataItem?.data?.[config.category.categorySlugField];
|
|
788
|
+
} catch {
|
|
789
|
+
return void 0;
|
|
790
|
+
}
|
|
791
|
+
}));
|
|
792
|
+
categoryResults.filter((slug) => !!slug).forEach((slug) => params.push({ category: slug }));
|
|
793
|
+
} catch (error) {
|
|
794
|
+
console.error(`[wix-data] Failed to load category params:`, error);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
yield params;
|
|
798
|
+
}
|
|
799
|
+
async function renderSlowlyChanging$2(props, wixData) {
|
|
800
|
+
const { collectionId } = props.metadata;
|
|
801
|
+
const Pipeline = RenderPipeline.for();
|
|
802
|
+
return Pipeline.try(async () => {
|
|
803
|
+
const config = wixData.getCollectionConfig(collectionId);
|
|
804
|
+
if (!config) {
|
|
805
|
+
throw new Error(`Collection not configured: ${collectionId}`);
|
|
806
|
+
}
|
|
807
|
+
const componentConfig = config.components.indexPage || config.components.categoryPage;
|
|
808
|
+
const fieldWhitelist = getComponentFields(componentConfig);
|
|
809
|
+
let categoryData;
|
|
810
|
+
let categoryId;
|
|
811
|
+
const categoryField = config.category?.referenceField;
|
|
812
|
+
if (props.category && config.category) {
|
|
813
|
+
const catQuery = wixData.items.query(config.category.referenceField.split(".")[0] || collectionId).eq(config.category.categorySlugField, props.category);
|
|
814
|
+
const catResult = await catQuery.find();
|
|
815
|
+
if (catResult.items.length > 0) {
|
|
816
|
+
const cat = catResult.items[0];
|
|
817
|
+
categoryId = cat._id;
|
|
818
|
+
categoryData = {
|
|
819
|
+
_id: cat._id,
|
|
820
|
+
slug: props.category,
|
|
821
|
+
title: cat.data?.title || cat.data?.name || props.category,
|
|
822
|
+
description: cat.data?.description || ""
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
const result = await queryItems({
|
|
827
|
+
collectionId,
|
|
828
|
+
limit: PAGE_SIZE,
|
|
829
|
+
offset: 0,
|
|
830
|
+
categoryId,
|
|
831
|
+
categoryField
|
|
832
|
+
});
|
|
833
|
+
const items2 = result.items.map((item) => mapItemToViewState(item, config.pathPrefix, config.slugField, fieldWhitelist));
|
|
834
|
+
const breadcrumbs = [
|
|
835
|
+
{ slug: "", title: "Home", url: "/" },
|
|
836
|
+
{
|
|
837
|
+
slug: collectionId.toLowerCase(),
|
|
838
|
+
title: config.collectionId,
|
|
839
|
+
url: config.pathPrefix
|
|
840
|
+
}
|
|
841
|
+
];
|
|
842
|
+
if (categoryData) {
|
|
843
|
+
breadcrumbs.push({
|
|
844
|
+
slug: categoryData.slug,
|
|
845
|
+
title: categoryData.title,
|
|
846
|
+
url: `${config.pathPrefix}/category/${categoryData.slug}`
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
return {
|
|
850
|
+
items: items2,
|
|
851
|
+
totalCount: result.totalCount,
|
|
852
|
+
category: categoryData,
|
|
853
|
+
breadcrumbs,
|
|
854
|
+
offset: items2.length,
|
|
855
|
+
// Initial offset is the number of items loaded
|
|
856
|
+
categoryId,
|
|
857
|
+
categoryField,
|
|
858
|
+
pathPrefix: config.pathPrefix,
|
|
859
|
+
slugField: config.slugField,
|
|
860
|
+
fieldWhitelist
|
|
861
|
+
};
|
|
862
|
+
}).recover((error) => {
|
|
863
|
+
console.error(`[wix-data] Failed to load list:`, error);
|
|
864
|
+
return Pipeline.clientError(500, "Failed to load items");
|
|
865
|
+
}).toPhaseOutput((data) => ({
|
|
866
|
+
viewState: {
|
|
867
|
+
items: data.items,
|
|
868
|
+
totalCount: data.totalCount,
|
|
869
|
+
category: data.category,
|
|
870
|
+
breadcrumbs: data.breadcrumbs
|
|
871
|
+
},
|
|
872
|
+
carryForward: {
|
|
873
|
+
collectionId,
|
|
874
|
+
categoryId: data.categoryId,
|
|
875
|
+
categoryField: data.categoryField,
|
|
876
|
+
offset: data.offset,
|
|
877
|
+
totalCount: data.totalCount,
|
|
878
|
+
pathPrefix: data.pathPrefix,
|
|
879
|
+
slugField: data.slugField,
|
|
880
|
+
fieldWhitelist: data.fieldWhitelist
|
|
881
|
+
}
|
|
882
|
+
}));
|
|
883
|
+
}
|
|
884
|
+
async function renderFastChanging$1(props, slowCarryForward, wixData) {
|
|
885
|
+
const Pipeline = RenderPipeline.for();
|
|
886
|
+
const hasMore = slowCarryForward.offset < slowCarryForward.totalCount;
|
|
887
|
+
return Pipeline.ok({
|
|
888
|
+
loadedItems: [],
|
|
889
|
+
// Empty initially - items loaded via "load more"
|
|
890
|
+
hasMore,
|
|
891
|
+
isLoading: false,
|
|
892
|
+
loadedCount: 0
|
|
893
|
+
}).toPhaseOutput((viewState) => ({
|
|
894
|
+
viewState,
|
|
895
|
+
carryForward: {
|
|
896
|
+
collectionId: slowCarryForward.collectionId,
|
|
897
|
+
categoryId: slowCarryForward.categoryId,
|
|
898
|
+
categoryField: slowCarryForward.categoryField,
|
|
899
|
+
offset: slowCarryForward.offset,
|
|
900
|
+
totalCount: slowCarryForward.totalCount,
|
|
901
|
+
pathPrefix: slowCarryForward.pathPrefix,
|
|
902
|
+
slugField: slowCarryForward.slugField,
|
|
903
|
+
fieldWhitelist: slowCarryForward.fieldWhitelist
|
|
904
|
+
}
|
|
905
|
+
}));
|
|
906
|
+
}
|
|
907
|
+
function isImageValue$1(value) {
|
|
908
|
+
if (typeof value === "string" && value.startsWith("wix:image://"))
|
|
909
|
+
return true;
|
|
910
|
+
if (typeof value === "object" && value !== null && ("src" in value || "url" in value))
|
|
911
|
+
return true;
|
|
912
|
+
return false;
|
|
913
|
+
}
|
|
914
|
+
function mapImageField(imgValue, altText) {
|
|
915
|
+
if (!imgValue)
|
|
916
|
+
return void 0;
|
|
917
|
+
if (typeof imgValue === "string") {
|
|
918
|
+
const parsed = parseWixMediaUrl(imgValue);
|
|
919
|
+
return {
|
|
920
|
+
url: formatWixMediaUrl("", imgValue),
|
|
921
|
+
altText: altText || "",
|
|
922
|
+
width: parsed?.originWidth,
|
|
923
|
+
height: parsed?.originHeight
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
if (typeof imgValue === "object" && imgValue !== null) {
|
|
927
|
+
const img = imgValue;
|
|
928
|
+
const srcUrl = img.src || img.url || "";
|
|
929
|
+
const parsed = parseWixMediaUrl(srcUrl);
|
|
930
|
+
return {
|
|
931
|
+
url: formatWixMediaUrl("", srcUrl),
|
|
932
|
+
altText: img.alt || altText || "",
|
|
933
|
+
width: parsed?.originWidth ?? img.width,
|
|
934
|
+
height: parsed?.originHeight ?? img.height
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
return void 0;
|
|
938
|
+
}
|
|
939
|
+
function mapItemToViewState(item, pathPrefix, slugField, whitelist) {
|
|
940
|
+
const mapped = {
|
|
941
|
+
_id: item._id,
|
|
942
|
+
url: `${pathPrefix}/${item[slugField] || item._id}`
|
|
943
|
+
};
|
|
944
|
+
const keysToInclude = whitelist || Object.keys(item).filter((k) => !k.startsWith("_"));
|
|
945
|
+
const titleValue = item.title || item.name;
|
|
946
|
+
const altText = typeof titleValue === "string" ? titleValue : "";
|
|
947
|
+
keysToInclude.forEach((key) => {
|
|
948
|
+
const value = item[key];
|
|
949
|
+
if (isImageValue$1(value)) {
|
|
950
|
+
mapped[key] = mapImageField(value, altText);
|
|
951
|
+
} else {
|
|
952
|
+
mapped[key] = value || "";
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
return mapped;
|
|
956
|
+
}
|
|
957
|
+
const collectionList = makeJayStackComponent().withProps().withServices(WIX_DATA_SERVICE_MARKER).withLoadParams(loadListParams).withSlowlyRender(renderSlowlyChanging$2).withFastRender(renderFastChanging$1);
|
|
958
|
+
function dataTag(key, type, description, indent = 2) {
|
|
959
|
+
const prefix = " ".repeat(indent);
|
|
960
|
+
const desc = description ? `, description: ${description}` : "";
|
|
961
|
+
return `${prefix}- {tag: ${key}, type: data, dataType: ${type}${desc}}`;
|
|
962
|
+
}
|
|
963
|
+
function dataTagWithPhase(key, type, phase, description, indent = 2) {
|
|
964
|
+
const prefix = " ".repeat(indent);
|
|
965
|
+
const desc = `, description: ${description}`;
|
|
966
|
+
return `${prefix}- {tag: ${key}, type: data, dataType: ${type}, phase: ${phase}${desc}}`;
|
|
967
|
+
}
|
|
968
|
+
function interactiveTag(key, elementType, description, indent = 2) {
|
|
969
|
+
const prefix = " ".repeat(indent);
|
|
970
|
+
const desc = description ? `, description: ${description}` : "";
|
|
971
|
+
return `${prefix}- {tag: ${key}, type: interactive, elementType: ${elementType}${desc}}`;
|
|
972
|
+
}
|
|
973
|
+
function variantTag(key, type, phase, description, indent = 2) {
|
|
974
|
+
const prefix = " ".repeat(indent);
|
|
975
|
+
const desc = description ? `, description: ${description}` : "";
|
|
976
|
+
return `${prefix}- {tag: ${key}, type: variant, dataType: ${type}, phase: ${phase}${desc}}`;
|
|
977
|
+
}
|
|
978
|
+
function imageSubContract(key, description, indent = 2) {
|
|
979
|
+
const prefix = " ".repeat(indent);
|
|
980
|
+
const innerPrefix = " ".repeat(indent + 2);
|
|
981
|
+
return `${prefix}- tag: ${key}
|
|
982
|
+
${prefix} type: sub-contract
|
|
983
|
+
${prefix} description: ${description || key}
|
|
984
|
+
${prefix} tags:
|
|
985
|
+
${innerPrefix}- {tag: url, type: data, dataType: string}
|
|
986
|
+
${innerPrefix}- {tag: altText, type: data, dataType: string}
|
|
987
|
+
${innerPrefix}- {tag: width, type: data, dataType: number}
|
|
988
|
+
${innerPrefix}- {tag: height, type: data, dataType: number}`;
|
|
989
|
+
}
|
|
990
|
+
function mediaSubContract(key, description, indent = 2) {
|
|
991
|
+
const prefix = " ".repeat(indent);
|
|
992
|
+
const innerPrefix = " ".repeat(indent + 2);
|
|
993
|
+
return `${prefix}- tag: ${key}
|
|
994
|
+
${prefix} type: sub-contract
|
|
995
|
+
${prefix} description: ${description || key}
|
|
996
|
+
${prefix} tags:
|
|
997
|
+
${innerPrefix}- {tag: url, type: data, dataType: string}
|
|
998
|
+
${innerPrefix}- {tag: title, type: data, dataType: string}`;
|
|
999
|
+
}
|
|
1000
|
+
function addressSubContract(key, description, indent = 2) {
|
|
1001
|
+
const prefix = " ".repeat(indent);
|
|
1002
|
+
const innerPrefix = " ".repeat(indent + 2);
|
|
1003
|
+
return `${prefix}- tag: ${key}
|
|
1004
|
+
${prefix} type: sub-contract
|
|
1005
|
+
${prefix} description: ${description || key}
|
|
1006
|
+
${prefix} tags:
|
|
1007
|
+
${innerPrefix}- {tag: formatted, type: data, dataType: string}
|
|
1008
|
+
${innerPrefix}- {tag: city, type: data, dataType: string}
|
|
1009
|
+
${innerPrefix}- {tag: country, type: data, dataType: string}`;
|
|
1010
|
+
}
|
|
1011
|
+
function embeddedReferenceSubContract(field, indent = 2) {
|
|
1012
|
+
const prefix = " ".repeat(indent);
|
|
1013
|
+
const isMulti = field.category === "multiReference";
|
|
1014
|
+
const repeated = isMulti ? `
|
|
1015
|
+
${prefix} repeated: true
|
|
1016
|
+
${prefix} trackBy: _id` : "";
|
|
1017
|
+
let innerTags;
|
|
1018
|
+
if (field.embeddedSchema) {
|
|
1019
|
+
innerTags = schemaToTags(field.embeddedSchema, indent + 2);
|
|
1020
|
+
} else {
|
|
1021
|
+
const innerPrefix = " ".repeat(indent + 2);
|
|
1022
|
+
innerTags = [
|
|
1023
|
+
`${innerPrefix}- {tag: _id, type: data, dataType: string}`,
|
|
1024
|
+
`${innerPrefix}- {tag: title, type: data, dataType: string}`,
|
|
1025
|
+
`${innerPrefix}- {tag: slug, type: data, dataType: string}`
|
|
1026
|
+
];
|
|
1027
|
+
}
|
|
1028
|
+
return `${prefix}- tag: ${field.key}
|
|
1029
|
+
${prefix} type: sub-contract${repeated}
|
|
1030
|
+
${prefix} description: ${field.displayName || field.key}
|
|
1031
|
+
${prefix} tags:
|
|
1032
|
+
${innerTags.join("\n")}`;
|
|
1033
|
+
}
|
|
1034
|
+
function categorySubContract(indent = 2) {
|
|
1035
|
+
const prefix = " ".repeat(indent);
|
|
1036
|
+
const innerPrefix = " ".repeat(indent + 2);
|
|
1037
|
+
return `${prefix}- tag: category
|
|
1038
|
+
${prefix} type: sub-contract
|
|
1039
|
+
${prefix} description: Current category
|
|
1040
|
+
${prefix} tags:
|
|
1041
|
+
${innerPrefix}- {tag: _id, type: data, dataType: string}
|
|
1042
|
+
${innerPrefix}- {tag: slug, type: data, dataType: string}
|
|
1043
|
+
${innerPrefix}- {tag: title, type: data, dataType: string}
|
|
1044
|
+
${innerPrefix}- {tag: description, type: data, dataType: string}
|
|
1045
|
+
${innerPrefix}- {tag: categoryLink, type: interactive, elementType: HTMLAnchorElement}`;
|
|
1046
|
+
}
|
|
1047
|
+
function breadcrumbsSubContract(indent = 2) {
|
|
1048
|
+
const prefix = " ".repeat(indent);
|
|
1049
|
+
const innerPrefix = " ".repeat(indent + 2);
|
|
1050
|
+
return `${prefix}- tag: breadcrumbs
|
|
1051
|
+
${prefix} type: sub-contract
|
|
1052
|
+
${prefix} repeated: true
|
|
1053
|
+
${prefix} trackBy: slug
|
|
1054
|
+
${prefix} description: Navigation breadcrumbs
|
|
1055
|
+
${prefix} tags:
|
|
1056
|
+
${innerPrefix}- {tag: slug, type: data, dataType: string}
|
|
1057
|
+
${innerPrefix}- {tag: title, type: data, dataType: string}
|
|
1058
|
+
${innerPrefix}- {tag: url, type: data, dataType: string}
|
|
1059
|
+
${innerPrefix}- {tag: link, type: interactive, elementType: HTMLAnchorElement}`;
|
|
1060
|
+
}
|
|
1061
|
+
function fieldToTag(field, indent = 2) {
|
|
1062
|
+
switch (field.category) {
|
|
1063
|
+
case "system":
|
|
1064
|
+
return field.key === "_id" ? dataTag("_id", "string", "Item ID", indent) : "";
|
|
1065
|
+
case "image":
|
|
1066
|
+
return imageSubContract(field.key, field.displayName, indent);
|
|
1067
|
+
case "media":
|
|
1068
|
+
return mediaSubContract(field.key, field.displayName, indent);
|
|
1069
|
+
case "address":
|
|
1070
|
+
return addressSubContract(field.key, field.displayName, indent);
|
|
1071
|
+
case "reference":
|
|
1072
|
+
case "multiReference":
|
|
1073
|
+
return field.embedded ? embeddedReferenceSubContract(field, indent) : dataTag(
|
|
1074
|
+
field.key,
|
|
1075
|
+
"string",
|
|
1076
|
+
`Reference ID${field.category === "multiReference" ? "s" : ""}`,
|
|
1077
|
+
indent
|
|
1078
|
+
);
|
|
1079
|
+
case "richContent":
|
|
1080
|
+
return dataTag(field.key, "string", field.displayName, indent);
|
|
1081
|
+
default:
|
|
1082
|
+
return dataTag(field.key, field.jayType, field.displayName, indent);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
function schemaToTags(schema, indent = 2) {
|
|
1086
|
+
const tags = [];
|
|
1087
|
+
tags.push(dataTag("_id", "string", "Item ID", indent));
|
|
1088
|
+
schema.fields.filter((f) => f.category !== "system").forEach((f) => {
|
|
1089
|
+
const tag = fieldToTag(f, indent);
|
|
1090
|
+
if (tag) tags.push(tag);
|
|
1091
|
+
});
|
|
1092
|
+
return tags;
|
|
1093
|
+
}
|
|
1094
|
+
function isCardField(f) {
|
|
1095
|
+
return f.category !== "system" && f.category !== "reference" && f.category !== "multiReference" && f.category !== "richContent";
|
|
1096
|
+
}
|
|
1097
|
+
function isTableField(f) {
|
|
1098
|
+
return f.category === "simple";
|
|
1099
|
+
}
|
|
1100
|
+
function isContentField(f) {
|
|
1101
|
+
return f.category !== "system";
|
|
1102
|
+
}
|
|
1103
|
+
function toPascalCase(str) {
|
|
1104
|
+
return str.split(/[-_\s]+/).map((s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase()).join("");
|
|
1105
|
+
}
|
|
1106
|
+
const DEFAULT_PAGE_SIZE = 20;
|
|
1107
|
+
async function renderSlowlyChanging$1(props, wixData) {
|
|
1108
|
+
const { collectionId } = props.metadata;
|
|
1109
|
+
const pageSize = props.pageSize || DEFAULT_PAGE_SIZE;
|
|
1110
|
+
const Pipeline = RenderPipeline.for();
|
|
1111
|
+
return Pipeline.try(async () => {
|
|
1112
|
+
const schema = await wixData.getProcessedSchema(collectionId);
|
|
1113
|
+
if (!schema) {
|
|
1114
|
+
throw new Error(`Collection not configured: ${collectionId}`);
|
|
1115
|
+
}
|
|
1116
|
+
const config = schema.config;
|
|
1117
|
+
const tableFields = schema.fields.filter(isTableField);
|
|
1118
|
+
const columns = tableFields.map((field) => ({
|
|
1119
|
+
fieldName: field.key,
|
|
1120
|
+
label: field.displayName || field.key,
|
|
1121
|
+
sortable: ["TEXT", "NUMBER", "DATE", "DATETIME"].includes(field.wixType)
|
|
1122
|
+
}));
|
|
1123
|
+
const columnNames = tableFields.map((field) => field.key);
|
|
1124
|
+
const result = await wixData.items.query(collectionId).limit(pageSize).find();
|
|
1125
|
+
const rows = result.items.map((item) => ({
|
|
1126
|
+
_id: item._id,
|
|
1127
|
+
url: `${config.pathPrefix}/${item[config.slugField] || item._id}`,
|
|
1128
|
+
cells: columnNames.map((fieldName) => ({
|
|
1129
|
+
fieldName,
|
|
1130
|
+
value: formatCellValue(item[fieldName])
|
|
1131
|
+
}))
|
|
1132
|
+
}));
|
|
1133
|
+
const totalCount = result.totalCount || rows.length;
|
|
1134
|
+
const totalPages = Math.ceil(totalCount / pageSize);
|
|
1135
|
+
return {
|
|
1136
|
+
columns,
|
|
1137
|
+
rows,
|
|
1138
|
+
totalCount,
|
|
1139
|
+
pageSize,
|
|
1140
|
+
totalPages,
|
|
1141
|
+
columnNames
|
|
1142
|
+
};
|
|
1143
|
+
}).recover((error) => {
|
|
1144
|
+
console.error(`[wix-data] Failed to load table data:`, error);
|
|
1145
|
+
return Pipeline.clientError(500, "Failed to load table data");
|
|
1146
|
+
}).toPhaseOutput((data) => ({
|
|
1147
|
+
viewState: {
|
|
1148
|
+
columns: data.columns,
|
|
1149
|
+
rows: data.rows,
|
|
1150
|
+
totalCount: data.totalCount,
|
|
1151
|
+
pageSize: data.pageSize,
|
|
1152
|
+
totalPages: data.totalPages
|
|
1153
|
+
},
|
|
1154
|
+
carryForward: {
|
|
1155
|
+
collectionId,
|
|
1156
|
+
columns: data.columnNames,
|
|
1157
|
+
pageSize: data.pageSize,
|
|
1158
|
+
totalCount: data.totalCount
|
|
1159
|
+
}
|
|
1160
|
+
}));
|
|
1161
|
+
}
|
|
1162
|
+
function formatCellValue(value) {
|
|
1163
|
+
if (value === null || value === void 0)
|
|
1164
|
+
return "";
|
|
1165
|
+
if (typeof value === "string")
|
|
1166
|
+
return value;
|
|
1167
|
+
if (typeof value === "number")
|
|
1168
|
+
return String(value);
|
|
1169
|
+
if (typeof value === "boolean")
|
|
1170
|
+
return value ? "Yes" : "No";
|
|
1171
|
+
if (value instanceof Date)
|
|
1172
|
+
return value.toLocaleDateString();
|
|
1173
|
+
if (typeof value === "object")
|
|
1174
|
+
return JSON.stringify(value);
|
|
1175
|
+
return String(value);
|
|
1176
|
+
}
|
|
1177
|
+
async function renderFastChanging(props, slowCarryForward, _wixData) {
|
|
1178
|
+
const Pipeline = RenderPipeline.for();
|
|
1179
|
+
return Pipeline.ok({
|
|
1180
|
+
currentPage: 1,
|
|
1181
|
+
hasPrev: false,
|
|
1182
|
+
hasNext: slowCarryForward.totalCount > slowCarryForward.pageSize,
|
|
1183
|
+
sortField: null,
|
|
1184
|
+
sortDirection: "NONE"
|
|
1185
|
+
}).toPhaseOutput((viewState) => ({
|
|
1186
|
+
viewState,
|
|
1187
|
+
carryForward: slowCarryForward
|
|
1188
|
+
}));
|
|
1189
|
+
}
|
|
1190
|
+
const collectionTable = makeJayStackComponent().withProps().withServices(WIX_DATA_SERVICE_MARKER).withSlowlyRender(renderSlowlyChanging$1).withFastRender(renderFastChanging);
|
|
1191
|
+
async function renderSlowlyChanging(props, wixData) {
|
|
1192
|
+
const { collectionId } = props.metadata;
|
|
1193
|
+
const Pipeline = RenderPipeline.for();
|
|
1194
|
+
return Pipeline.try(async () => {
|
|
1195
|
+
const config = wixData.getCollectionConfig(collectionId);
|
|
1196
|
+
if (!config) {
|
|
1197
|
+
throw new Error(`Collection not configured: ${collectionId}`);
|
|
1198
|
+
}
|
|
1199
|
+
let item;
|
|
1200
|
+
if (props.itemId) {
|
|
1201
|
+
item = await wixData.items.get(collectionId, props.itemId);
|
|
1202
|
+
} else if (props.slug) {
|
|
1203
|
+
const result = await wixData.items.query(collectionId).eq(config.slugField, props.slug).find();
|
|
1204
|
+
item = result.items[0] || null;
|
|
1205
|
+
}
|
|
1206
|
+
if (!item) {
|
|
1207
|
+
throw new Error("Item not found");
|
|
1208
|
+
}
|
|
1209
|
+
const dataFields = Object.entries(item).filter(([key]) => !key.startsWith("_")).map(([key, value]) => {
|
|
1210
|
+
if (isImageValue(value)) {
|
|
1211
|
+
return [key, { url: value.src || value.url || "", altText: value.alt || "" }];
|
|
1212
|
+
}
|
|
1213
|
+
return [key, value];
|
|
1214
|
+
});
|
|
1215
|
+
const viewState = {
|
|
1216
|
+
_id: item._id,
|
|
1217
|
+
url: `${config.pathPrefix}/${item[config.slugField] || item._id}`,
|
|
1218
|
+
...Object.fromEntries(dataFields)
|
|
1219
|
+
};
|
|
1220
|
+
return { viewState, itemId: item._id };
|
|
1221
|
+
}).recover((error) => {
|
|
1222
|
+
console.error(`[wix-data] Failed to load card item:`, error);
|
|
1223
|
+
return Pipeline.clientError(404, "Item not found");
|
|
1224
|
+
}).toPhaseOutput(({ viewState, itemId }) => ({
|
|
1225
|
+
viewState,
|
|
1226
|
+
carryForward: { itemId }
|
|
1227
|
+
}));
|
|
1228
|
+
}
|
|
1229
|
+
function isImageValue(value) {
|
|
1230
|
+
return typeof value === "object" && value !== null && ("src" in value || "url" in value);
|
|
1231
|
+
}
|
|
1232
|
+
const collectionCard = makeJayStackComponent().withProps().withServices(WIX_DATA_SERVICE_MARKER).withSlowlyRender(renderSlowlyChanging);
|
|
1233
|
+
const init = makeJayInit().withServer(async () => {
|
|
1234
|
+
console.log("[wix-data] Initializing Wix Data service...");
|
|
1235
|
+
const wixClient = getService(WIX_CLIENT_SERVICE);
|
|
1236
|
+
const config = await loadConfig(wixClient);
|
|
1237
|
+
provideWixDataService(wixClient, config);
|
|
1238
|
+
const visibleCount = config.collections.filter((c) => c.visible).length;
|
|
1239
|
+
console.log(`[wix-data] Server initialization complete. ${visibleCount}/${config.collections.length} collections visible.`);
|
|
1240
|
+
return {
|
|
1241
|
+
collections: config.collections.filter((c) => c.visible).map((c) => c.collectionId)
|
|
1242
|
+
};
|
|
1243
|
+
});
|
|
1244
|
+
const CONFIG_FILE_NAME = "wix-data.yaml";
|
|
1245
|
+
async function setupWixData(ctx) {
|
|
1246
|
+
const configPath = path.join(ctx.configDir, CONFIG_FILE_NAME);
|
|
1247
|
+
if (ctx.initError) {
|
|
1248
|
+
if (!fs.existsSync(configPath)) {
|
|
1249
|
+
return {
|
|
1250
|
+
status: "needs-config",
|
|
1251
|
+
message: "wix-data requires wix-server-client to be configured first. Run: jay-stack setup wix-server-client"
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
return {
|
|
1255
|
+
status: "error",
|
|
1256
|
+
message: `Service init failed: ${ctx.initError.message}`
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
try {
|
|
1260
|
+
getService(WIX_DATA_SERVICE_MARKER);
|
|
1261
|
+
} catch {
|
|
1262
|
+
return {
|
|
1263
|
+
status: "error",
|
|
1264
|
+
message: "WixDataService not available. Check initialization."
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
const dataService = getService(WIX_DATA_SERVICE_MARKER);
|
|
1268
|
+
const visibleCount = dataService.config.collections.filter((c) => c.visible).length;
|
|
1269
|
+
const totalCount = dataService.config.collections.length;
|
|
1270
|
+
return {
|
|
1271
|
+
status: "configured",
|
|
1272
|
+
message: `${totalCount} collections found (${visibleCount} visible)`
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
async function generateWixDataReferences(ctx) {
|
|
1276
|
+
if (ctx.initError) {
|
|
1277
|
+
throw new Error(`init failed: ${ctx.initError.message}`);
|
|
1278
|
+
}
|
|
1279
|
+
let dataService;
|
|
1280
|
+
try {
|
|
1281
|
+
dataService = getService(WIX_DATA_SERVICE_MARKER);
|
|
1282
|
+
} catch {
|
|
1283
|
+
throw new Error("WixDataService not available. Run jay-stack setup first.");
|
|
1284
|
+
}
|
|
1285
|
+
fs.mkdirSync(ctx.referencesDir, { recursive: true });
|
|
1286
|
+
const config = dataService.config;
|
|
1287
|
+
const collectionSummaries = [];
|
|
1288
|
+
for (const collConfig of config.collections) {
|
|
1289
|
+
const collection = await dataService.getCollection(collConfig.collectionId);
|
|
1290
|
+
if (!collection) continue;
|
|
1291
|
+
const fields = (collection.fields || []).map((f) => ({
|
|
1292
|
+
key: f.key || "",
|
|
1293
|
+
displayName: f.displayName || f.key || "",
|
|
1294
|
+
type: f.type || "unknown"
|
|
1295
|
+
}));
|
|
1296
|
+
const referenceFields = (collection.fields || []).filter(
|
|
1297
|
+
(f) => f.type === "REFERENCE" || f.type === "MULTI_REFERENCE"
|
|
1298
|
+
);
|
|
1299
|
+
const references = referenceFields.map((f) => {
|
|
1300
|
+
const meta = f;
|
|
1301
|
+
return {
|
|
1302
|
+
fieldName: f.key || "",
|
|
1303
|
+
targetCollection: meta.typeMetadata?.reference?.referencedCollectionId || meta.typeMetadata?.multiReference?.referencedCollectionId || "unknown",
|
|
1304
|
+
isMulti: f.type === "MULTI_REFERENCE"
|
|
1305
|
+
};
|
|
1306
|
+
});
|
|
1307
|
+
const components = {};
|
|
1308
|
+
if (collConfig.components) {
|
|
1309
|
+
if (collConfig.components.itemPage) components.itemPage = true;
|
|
1310
|
+
if (collConfig.components.indexPage) components.indexPage = true;
|
|
1311
|
+
if (collConfig.components.categoryPage) components.categoryPage = true;
|
|
1312
|
+
if (collConfig.components.tableWidget) components.tableWidget = true;
|
|
1313
|
+
if (collConfig.components.cardWidget) components.cardWidget = true;
|
|
1314
|
+
}
|
|
1315
|
+
collectionSummaries.push({
|
|
1316
|
+
collectionId: collConfig.collectionId,
|
|
1317
|
+
visible: collConfig.visible ?? false,
|
|
1318
|
+
pathPrefix: collConfig.pathPrefix,
|
|
1319
|
+
slugField: collConfig.slugField,
|
|
1320
|
+
fields,
|
|
1321
|
+
references,
|
|
1322
|
+
components
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
const collectionsPath = path.join(ctx.referencesDir, "collections.yaml");
|
|
1326
|
+
fs.writeFileSync(
|
|
1327
|
+
collectionsPath,
|
|
1328
|
+
yaml.dump(
|
|
1329
|
+
{
|
|
1330
|
+
_generated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1331
|
+
_description: "CMS collection schemas for agent discovery. Shows field types, references, and visibility.",
|
|
1332
|
+
totalCollections: collectionSummaries.length,
|
|
1333
|
+
visibleCollections: collectionSummaries.filter((c) => c.visible).length,
|
|
1334
|
+
collections: collectionSummaries
|
|
1335
|
+
},
|
|
1336
|
+
{ indent: 2, lineWidth: 120, noRefs: true }
|
|
1337
|
+
),
|
|
1338
|
+
"utf-8"
|
|
1339
|
+
);
|
|
1340
|
+
return {
|
|
1341
|
+
referencesCreated: [`agent-kit/references/${ctx.pluginName}/collections.yaml`],
|
|
1342
|
+
message: `${collectionSummaries.length} collections (${collectionSummaries.filter((c) => c.visible).length} visible)`
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
function buildContract$3(schema) {
|
|
1346
|
+
const tags = [
|
|
1347
|
+
dataTag("_id", "string", "Item ID"),
|
|
1348
|
+
interactiveTag("itemLink", "HTMLAnchorElement", "Link to item")
|
|
1349
|
+
];
|
|
1350
|
+
schema.fields.filter(isContentField).forEach((f) => {
|
|
1351
|
+
const tag = fieldToTag(f);
|
|
1352
|
+
if (tag) tags.push(tag);
|
|
1353
|
+
});
|
|
1354
|
+
if (schema.hasCategory) {
|
|
1355
|
+
tags.push(categorySubContract());
|
|
1356
|
+
}
|
|
1357
|
+
tags.push(breadcrumbsSubContract());
|
|
1358
|
+
return `name: ${toPascalCase(schema.collectionId)}Item
|
|
1359
|
+
description: Item page for ${schema.displayName || schema.collectionId}
|
|
1360
|
+
tags:
|
|
1361
|
+
${tags.join("\n")}`;
|
|
1362
|
+
}
|
|
1363
|
+
const generator$3 = makeContractGenerator().withServices(WIX_DATA_SERVICE_MARKER).generateWith(async (wixDataService) => {
|
|
1364
|
+
const schemas = await wixDataService.getProcessedSchemas(
|
|
1365
|
+
(c) => c.visible === true && !!c.components.itemPage
|
|
1366
|
+
);
|
|
1367
|
+
return schemas.map((schema) => {
|
|
1368
|
+
const name = toPascalCase(schema.collectionId) + "Item";
|
|
1369
|
+
console.log(`[wix-data] Generated item contract: ${name}`);
|
|
1370
|
+
return {
|
|
1371
|
+
name,
|
|
1372
|
+
yaml: buildContract$3(schema),
|
|
1373
|
+
description: `Item page for ${schema.displayName || schema.collectionId}`,
|
|
1374
|
+
metadata: { collectionId: schema.collectionId }
|
|
1375
|
+
};
|
|
1376
|
+
});
|
|
1377
|
+
});
|
|
1378
|
+
function getListComponentConfig(schema) {
|
|
1379
|
+
return schema.config.components.indexPage || schema.config.components.categoryPage;
|
|
1380
|
+
}
|
|
1381
|
+
function buildCardTags(schema, indent = 6) {
|
|
1382
|
+
const componentConfig = getListComponentConfig(schema);
|
|
1383
|
+
const whitelist = getComponentFields(componentConfig);
|
|
1384
|
+
const cardTags = [
|
|
1385
|
+
dataTag("_id", "string", void 0, indent),
|
|
1386
|
+
dataTag("url", "string", "Full URL to item page", indent),
|
|
1387
|
+
interactiveTag("itemLink", "HTMLAnchorElement", void 0, indent)
|
|
1388
|
+
];
|
|
1389
|
+
const fieldsToInclude = whitelist ? schema.fields.filter((f) => whitelist.includes(f.key)) : schema.fields.filter(isCardField);
|
|
1390
|
+
fieldsToInclude.forEach((field) => {
|
|
1391
|
+
const tag = fieldToTag(field, indent);
|
|
1392
|
+
if (tag) cardTags.push(tag);
|
|
1393
|
+
});
|
|
1394
|
+
return cardTags;
|
|
1395
|
+
}
|
|
1396
|
+
function buildItemsSubContract(schema, tagName, phase, description) {
|
|
1397
|
+
const cardTags = buildCardTags(schema);
|
|
1398
|
+
const phaseAttr = phase ? `
|
|
1399
|
+
phase: ${phase}` : "";
|
|
1400
|
+
const desc = phase ? "Additional items loaded on the client" : "Initial items (rendered server-side)";
|
|
1401
|
+
return ` - tag: ${tagName}
|
|
1402
|
+
type: sub-contract
|
|
1403
|
+
repeated: true
|
|
1404
|
+
trackBy: _id${phaseAttr}
|
|
1405
|
+
description: ${desc}
|
|
1406
|
+
tags:
|
|
1407
|
+
${cardTags.join("\n")}`;
|
|
1408
|
+
}
|
|
1409
|
+
function buildContract$2(schema) {
|
|
1410
|
+
const tags = [];
|
|
1411
|
+
tags.push(buildItemsSubContract(schema, "items"));
|
|
1412
|
+
tags.push(buildItemsSubContract(schema, "loadedItems", "fast+interactive"));
|
|
1413
|
+
tags.push(dataTag("totalCount", "number", "Total items"));
|
|
1414
|
+
tags.push(variantTag("hasMore", "boolean", "fast+interactive", "More items available"));
|
|
1415
|
+
tags.push(variantTag("isLoading", "boolean", "fast+interactive", "Loading state"));
|
|
1416
|
+
tags.push(
|
|
1417
|
+
dataTagWithPhase("loadedCount", "number", "fast+interactive", "Items currently loaded")
|
|
1418
|
+
);
|
|
1419
|
+
tags.push(interactiveTag("loadMoreButton", "HTMLButtonElement", "Load more trigger"));
|
|
1420
|
+
if (schema.hasCategory) {
|
|
1421
|
+
tags.push(categorySubContract());
|
|
1422
|
+
}
|
|
1423
|
+
tags.push(breadcrumbsSubContract());
|
|
1424
|
+
return `name: ${toPascalCase(schema.collectionId)}List
|
|
1425
|
+
description: List page for ${schema.displayName || schema.collectionId}
|
|
1426
|
+
tags:
|
|
1427
|
+
${tags.join("\n")}`;
|
|
1428
|
+
}
|
|
1429
|
+
const generator$2 = makeContractGenerator().withServices(WIX_DATA_SERVICE_MARKER).generateWith(async (wixDataService) => {
|
|
1430
|
+
const schemas = await wixDataService.getProcessedSchemas(
|
|
1431
|
+
(c) => c.visible === true && (isComponentEnabled(c.components.indexPage) || isComponentEnabled(c.components.categoryPage))
|
|
1432
|
+
);
|
|
1433
|
+
return schemas.map((schema) => {
|
|
1434
|
+
const name = toPascalCase(schema.collectionId) + "List";
|
|
1435
|
+
console.log(`[wix-data] Generated list contract: ${name}`);
|
|
1436
|
+
return {
|
|
1437
|
+
name,
|
|
1438
|
+
yaml: buildContract$2(schema),
|
|
1439
|
+
description: `List page for ${schema.displayName || schema.collectionId}`,
|
|
1440
|
+
metadata: { collectionId: schema.collectionId }
|
|
1441
|
+
};
|
|
1442
|
+
});
|
|
1443
|
+
});
|
|
1444
|
+
function buildContract$1(schema) {
|
|
1445
|
+
const tags = [];
|
|
1446
|
+
tags.push(` - tag: columns
|
|
1447
|
+
type: sub-contract
|
|
1448
|
+
repeated: true
|
|
1449
|
+
trackBy: fieldName
|
|
1450
|
+
description: Table column definitions
|
|
1451
|
+
tags:
|
|
1452
|
+
- {tag: fieldName, type: data, dataType: string}
|
|
1453
|
+
- {tag: label, type: data, dataType: string}
|
|
1454
|
+
- {tag: sortable, type: variant, dataType: boolean}
|
|
1455
|
+
- {tag: sortDirection, type: variant, dataType: "enum (NONE | ASC | DESC)", phase: fast+interactive}
|
|
1456
|
+
- {tag: headerButton, type: interactive, elementType: HTMLButtonElement}`);
|
|
1457
|
+
tags.push(` - tag: rows
|
|
1458
|
+
type: sub-contract
|
|
1459
|
+
repeated: true
|
|
1460
|
+
trackBy: _id
|
|
1461
|
+
description: Table rows
|
|
1462
|
+
tags:
|
|
1463
|
+
- {tag: _id, type: data, dataType: string}
|
|
1464
|
+
- {tag: url, type: data, dataType: string}
|
|
1465
|
+
- {tag: rowLink, type: interactive, elementType: HTMLAnchorElement}
|
|
1466
|
+
- tag: cells
|
|
1467
|
+
type: sub-contract
|
|
1468
|
+
repeated: true
|
|
1469
|
+
trackBy: fieldName
|
|
1470
|
+
tags:
|
|
1471
|
+
- {tag: fieldName, type: data, dataType: string}
|
|
1472
|
+
- {tag: value, type: data, dataType: string}`);
|
|
1473
|
+
tags.push(dataTag("totalCount", "number"));
|
|
1474
|
+
tags.push(dataTag("pageSize", "number"));
|
|
1475
|
+
tags.push(dataTag("totalPages", "number"));
|
|
1476
|
+
tags.push(variantTag("currentPage", "number", "fast+interactive"));
|
|
1477
|
+
tags.push(variantTag("hasPrev", "boolean", "fast+interactive"));
|
|
1478
|
+
tags.push(variantTag("hasNext", "boolean", "fast+interactive"));
|
|
1479
|
+
tags.push(interactiveTag("prevButton", "HTMLButtonElement"));
|
|
1480
|
+
tags.push(interactiveTag("nextButton", "HTMLButtonElement"));
|
|
1481
|
+
if (schema.hasCategory) {
|
|
1482
|
+
tags.push(categorySubContract());
|
|
1483
|
+
}
|
|
1484
|
+
return `name: ${toPascalCase(schema.collectionId)}Table
|
|
1485
|
+
description: Table widget for ${schema.displayName || schema.collectionId}
|
|
1486
|
+
tags:
|
|
1487
|
+
${tags.join("\n")}`;
|
|
1488
|
+
}
|
|
1489
|
+
const generator$1 = makeContractGenerator().withServices(WIX_DATA_SERVICE_MARKER).generateWith(async (wixDataService) => {
|
|
1490
|
+
const schemas = await wixDataService.getProcessedSchemas(
|
|
1491
|
+
(c) => c.visible === true && !!c.components.tableWidget
|
|
1492
|
+
);
|
|
1493
|
+
return schemas.map((schema) => {
|
|
1494
|
+
const name = toPascalCase(schema.collectionId) + "Table";
|
|
1495
|
+
console.log(`[wix-data] Generated table contract: ${name}`);
|
|
1496
|
+
return {
|
|
1497
|
+
name,
|
|
1498
|
+
yaml: buildContract$1(schema),
|
|
1499
|
+
description: `Table widget for ${schema.displayName || schema.collectionId}`,
|
|
1500
|
+
metadata: { collectionId: schema.collectionId }
|
|
1501
|
+
};
|
|
1502
|
+
});
|
|
1503
|
+
});
|
|
1504
|
+
function buildContract(schema) {
|
|
1505
|
+
const tags = [
|
|
1506
|
+
dataTag("_id", "string"),
|
|
1507
|
+
dataTag("url", "string", "Full URL to item page"),
|
|
1508
|
+
interactiveTag("itemLink", "HTMLAnchorElement")
|
|
1509
|
+
];
|
|
1510
|
+
schema.fields.filter(isCardField).forEach((f) => {
|
|
1511
|
+
const tag = fieldToTag(f);
|
|
1512
|
+
if (tag) tags.push(tag);
|
|
1513
|
+
});
|
|
1514
|
+
return `name: ${toPascalCase(schema.collectionId)}Card
|
|
1515
|
+
description: Card widget for ${schema.displayName || schema.collectionId}
|
|
1516
|
+
tags:
|
|
1517
|
+
${tags.join("\n")}`;
|
|
1518
|
+
}
|
|
1519
|
+
const generator = makeContractGenerator().withServices(WIX_DATA_SERVICE_MARKER).generateWith(async (wixDataService) => {
|
|
1520
|
+
const schemas = await wixDataService.getProcessedSchemas(
|
|
1521
|
+
(c) => c.visible === true && !!c.components.cardWidget
|
|
1522
|
+
);
|
|
1523
|
+
return schemas.map((schema) => {
|
|
1524
|
+
const name = toPascalCase(schema.collectionId) + "Card";
|
|
1525
|
+
console.log(`[wix-data] Generated card contract: ${name}`);
|
|
1526
|
+
return {
|
|
1527
|
+
name,
|
|
1528
|
+
yaml: buildContract(schema),
|
|
1529
|
+
description: `Card widget for ${schema.displayName || schema.collectionId}`,
|
|
1530
|
+
metadata: { collectionId: schema.collectionId }
|
|
1531
|
+
};
|
|
1532
|
+
});
|
|
1533
|
+
});
|
|
1534
|
+
export {
|
|
1535
|
+
WIX_DATA_CONTEXT,
|
|
1536
|
+
WIX_DATA_SERVICE_MARKER,
|
|
1537
|
+
generator as cardContractGenerator,
|
|
1538
|
+
collectionCard,
|
|
1539
|
+
collectionItem,
|
|
1540
|
+
collectionList,
|
|
1541
|
+
collectionTable,
|
|
1542
|
+
generateWixDataReferences,
|
|
1543
|
+
getCategories,
|
|
1544
|
+
getItemBySlug,
|
|
1545
|
+
getVisibleCollections,
|
|
1546
|
+
init,
|
|
1547
|
+
isCardField,
|
|
1548
|
+
isContentField,
|
|
1549
|
+
isTableField,
|
|
1550
|
+
generator$3 as itemContractGenerator,
|
|
1551
|
+
generator$2 as listContractGenerator,
|
|
1552
|
+
loadConfig,
|
|
1553
|
+
processSchema,
|
|
1554
|
+
provideWixDataService,
|
|
1555
|
+
queryItems,
|
|
1556
|
+
setupWixData,
|
|
1557
|
+
generator$1 as tableContractGenerator,
|
|
1558
|
+
toPascalCase,
|
|
1559
|
+
validateCollectionConfig,
|
|
1560
|
+
validateConfig
|
|
1561
|
+
};
|