@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 ADDED
@@ -0,0 +1,151 @@
1
+ # @jay-framework/wix-data
2
+
3
+ Wix Data API client for the Jay Framework. This package provides a convenient wrapper around `@wix/data` that uses the configured Wix client from `@jay-framework/wix-server-client`.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ yarn add @jay-framework/wix-data
9
+ ```
10
+
11
+ ## Prerequisites
12
+
13
+ Make sure you have configured `@jay-framework/wix-server-client` with a valid `./config/.wix.yaml` file. See the [wix-server-client documentation](../wix-server-client/README.md) for configuration details.
14
+
15
+ ## Usage
16
+
17
+ ### Quick Start
18
+
19
+ ```typescript
20
+ import { getItemsClient, getCollectionsClient } from '@jay-framework/wix-data';
21
+
22
+ // Get pre-configured clients
23
+ const itemsClient = getItemsClient();
24
+ const collectionsClient = getCollectionsClient();
25
+
26
+ // Query data items
27
+ const results = await itemsClient
28
+ .queryDataItems({
29
+ dataCollectionId: 'myCollection',
30
+ })
31
+ .find();
32
+
33
+ // Get collections
34
+ const allCollections = await collectionsClient.listDataCollections();
35
+ ```
36
+
37
+ ### Advanced Usage
38
+
39
+ You can also use the Wix client directly:
40
+
41
+ ```typescript
42
+ import { getClient } from '@jay-framework/wix-server-client';
43
+ import { items, collections } from '@jay-framework/wix-data';
44
+
45
+ const wixClient = getClient();
46
+ const itemsClient = wixClient.use(items);
47
+ const collectionsClient = wixClient.use(collections);
48
+
49
+ // Use the clients...
50
+ ```
51
+
52
+ ### Working with Data Items
53
+
54
+ ```typescript
55
+ import { getItemsClient } from '@jay-framework/wix-data';
56
+
57
+ const itemsClient = getItemsClient();
58
+
59
+ // Query items
60
+ const items = await itemsClient
61
+ .queryDataItems({
62
+ dataCollectionId: 'products',
63
+ })
64
+ .eq('status', 'active')
65
+ .find();
66
+
67
+ // Get a single item
68
+ const item = await itemsClient.getDataItem('item-id', { dataCollectionId: 'products' });
69
+
70
+ // Insert an item
71
+ const newItem = await itemsClient.insertDataItem({
72
+ dataCollectionId: 'products',
73
+ dataItem: {
74
+ data: {
75
+ name: 'New Product',
76
+ price: 29.99,
77
+ },
78
+ },
79
+ });
80
+
81
+ // Update an item
82
+ const updatedItem = await itemsClient.updateDataItem('item-id', {
83
+ dataCollectionId: 'products',
84
+ dataItem: {
85
+ data: {
86
+ price: 24.99,
87
+ },
88
+ },
89
+ });
90
+
91
+ // Remove an item
92
+ await itemsClient.removeDataItem('item-id', { dataCollectionId: 'products' });
93
+ ```
94
+
95
+ ### Working with Collections
96
+
97
+ ```typescript
98
+ import { getCollectionsClient } from '@jay-framework/wix-data';
99
+
100
+ const collectionsClient = getCollectionsClient();
101
+
102
+ // List all collections
103
+ const collections = await collectionsClient.listDataCollections();
104
+
105
+ // Get a specific collection
106
+ const collection = await collectionsClient.getDataCollection('myCollection');
107
+ ```
108
+
109
+ ## API Reference
110
+
111
+ ### `getItemsClient()`
112
+
113
+ Returns a configured Wix Data Items client (singleton).
114
+
115
+ The Items API allows you to access and manage items in a Wix site's data collections.
116
+
117
+ **Returns:** Items client instance from `@wix/data`
118
+
119
+ **Documentation:** [Wix Data Items API](https://dev.wix.com/docs/sdk/backend-modules/data/items/introduction)
120
+
121
+ ### `getCollectionsClient()`
122
+
123
+ Returns a configured Wix Data Collections client (singleton).
124
+
125
+ The Collections API allows you to manage a site's data collections.
126
+
127
+ **Returns:** Collections client instance from `@wix/data`
128
+
129
+ **Documentation:** [Wix Data Collections API](https://dev.wix.com/docs/sdk/backend-modules/data/collections/introduction)
130
+
131
+ ## Re-exported Types
132
+
133
+ This package re-exports all types and functions from `@wix/data`, so you can import them directly:
134
+
135
+ ```typescript
136
+ import {
137
+ items,
138
+ collections,
139
+ type DataItem,
140
+ type DataCollection,
141
+ // ... all other exports from @wix/data
142
+ } from '@jay-framework/wix-data';
143
+ ```
144
+
145
+ ## Configuration
146
+
147
+ This package uses the configuration from `@jay-framework/wix-server-client`. Make sure you have a `./config/.wix.yaml` file configured. See the [wix-server-client README](../wix-server-client/README.md) for details.
148
+
149
+ ## License
150
+
151
+ Apache-2.0
@@ -0,0 +1,12 @@
1
+ name: getCategories
2
+ description: Get categories for a Wix Data collection. Returns category names, slugs, and item counts.
3
+
4
+ inputSchema:
5
+ collectionId: string
6
+
7
+ outputSchema:
8
+ categories:
9
+ - _id: string
10
+ slug: string
11
+ title: string
12
+ itemCount: number
@@ -0,0 +1,9 @@
1
+ name: getItemBySlug
2
+ description: Get a single item from a Wix Data collection by its URL slug. Returns the item data or null if not found.
3
+
4
+ inputSchema:
5
+ collectionId: string
6
+ slug: string
7
+
8
+ outputSchema:
9
+ item: {}
@@ -0,0 +1,19 @@
1
+ name: queryItems
2
+ description: Query items from a Wix Data collection with pagination, sorting, and filtering. Returns matching items with total count.
3
+
4
+ inputSchema:
5
+ collectionId: string
6
+ limit?: number
7
+ offset?: number
8
+ sortField?: string
9
+ sortDirection?: enum(ASC | DESC)
10
+ filter?: {}
11
+ categoryId?: string
12
+ categoryField?: string
13
+
14
+ outputSchema:
15
+ items:
16
+ - {}
17
+ totalCount: number
18
+ offset: number
19
+ hasMore: boolean
@@ -0,0 +1,244 @@
1
+ import { makeJayStackComponent, makeJayInit } from "@jay-framework/fullstack-component";
2
+ import { createActionCaller } from "@jay-framework/stack-client-runtime";
3
+ import { registerReactiveGlobalContext, createSignal } from "@jay-framework/component";
4
+ import { createJayContext, useGlobalContext } from "@jay-framework/runtime";
5
+ import { WIX_CLIENT_CONTEXT } from "@jay-framework/wix-server-client/client";
6
+ import { items } from "@wix/data";
7
+ const collectionItem = makeJayStackComponent().withProps();
8
+ function parseWixMediaUrl(url) {
9
+ if (!url) return null;
10
+ const match = url.match(
11
+ /^wix:(image|video|document|audio):\/\/v1\/([^/]+)\/([^#]+)(?:#(.*))?$/
12
+ );
13
+ if (!match) return null;
14
+ const [, type, mediaId, fileName, hashParams] = match;
15
+ const result = {
16
+ type,
17
+ mediaId,
18
+ fileName: decodeURIComponent(fileName)
19
+ };
20
+ if (hashParams) {
21
+ const params = new URLSearchParams(hashParams);
22
+ const originWidth = params.get("originWidth");
23
+ const originHeight = params.get("originHeight");
24
+ if (originWidth) result.originWidth = parseInt(originWidth, 10);
25
+ if (originHeight) result.originHeight = parseInt(originHeight, 10);
26
+ const posterUri = params.get("posterUri");
27
+ const posterWidth = params.get("posterWidth");
28
+ const posterHeight = params.get("posterHeight");
29
+ if (posterUri) result.posterUri = decodeURIComponent(posterUri);
30
+ if (posterWidth) result.posterWidth = parseInt(posterWidth, 10);
31
+ if (posterHeight) result.posterHeight = parseInt(posterHeight, 10);
32
+ }
33
+ return result;
34
+ }
35
+ function formatWixMediaUrl(_id, url, resize) {
36
+ const resizeFragment = "";
37
+ if (url == null ? void 0 : url.startsWith("wix:")) {
38
+ const parsed = parseWixMediaUrl(url);
39
+ if (parsed) {
40
+ return `https://static.wixstatic.com/media/${parsed.mediaId}${resizeFragment}`;
41
+ }
42
+ }
43
+ if ((url == null ? void 0 : url.startsWith("http://")) || (url == null ? void 0 : url.startsWith("https://"))) {
44
+ return url;
45
+ }
46
+ return "";
47
+ }
48
+ const queryItems = createActionCaller("wixData.queryItems", "GET");
49
+ createActionCaller("wixData.getItemBySlug", "GET");
50
+ createActionCaller("wixData.getCategories", "GET");
51
+ const PAGE_SIZE = 20;
52
+ function ListInteractive(_props, refs, viewStateSignals, fastCarryForward) {
53
+ const { loadedItems: [loadedItems, setLoadedItems], hasMore: [hasMore, setHasMore], isLoading: [isLoading, setIsLoading], loadedCount: [loadedCount, setLoadedCount] } = viewStateSignals;
54
+ let currentOffset = fastCarryForward.offset;
55
+ const { collectionId, categoryId, categoryField, totalCount, pathPrefix, slugField, fieldWhitelist } = fastCarryForward;
56
+ refs.loadMoreButton?.onclick(async () => {
57
+ if (currentOffset >= totalCount || isLoading())
58
+ return;
59
+ setIsLoading(true);
60
+ try {
61
+ const result = await queryItems({
62
+ collectionId,
63
+ limit: PAGE_SIZE,
64
+ offset: currentOffset,
65
+ categoryId,
66
+ categoryField
67
+ });
68
+ const newItems = result.items.map((item) => mapItemToViewState(item, pathPrefix, slugField, fieldWhitelist));
69
+ currentOffset += newItems.length;
70
+ setLoadedItems([...loadedItems(), ...newItems]);
71
+ setLoadedCount(loadedCount() + newItems.length);
72
+ setHasMore(result.hasMore);
73
+ } catch (error) {
74
+ console.error("[wix-data] Failed to load more items:", error);
75
+ } finally {
76
+ setIsLoading(false);
77
+ }
78
+ });
79
+ return {
80
+ render: () => ({
81
+ loadedItems: loadedItems(),
82
+ hasMore: hasMore(),
83
+ isLoading: isLoading(),
84
+ loadedCount: loadedCount()
85
+ })
86
+ };
87
+ }
88
+ function isImageValue(value) {
89
+ if (typeof value === "string" && value.startsWith("wix:image://"))
90
+ return true;
91
+ if (typeof value === "object" && value !== null && ("src" in value || "url" in value))
92
+ return true;
93
+ return false;
94
+ }
95
+ function mapImageField(imgValue, altText) {
96
+ if (!imgValue)
97
+ return void 0;
98
+ if (typeof imgValue === "string") {
99
+ const parsed = parseWixMediaUrl(imgValue);
100
+ return {
101
+ url: formatWixMediaUrl("", imgValue),
102
+ altText: altText || "",
103
+ width: parsed?.originWidth,
104
+ height: parsed?.originHeight
105
+ };
106
+ }
107
+ if (typeof imgValue === "object" && imgValue !== null) {
108
+ const img = imgValue;
109
+ const srcUrl = img.src || img.url || "";
110
+ const parsed = parseWixMediaUrl(srcUrl);
111
+ return {
112
+ url: formatWixMediaUrl("", srcUrl),
113
+ altText: img.alt || altText || "",
114
+ width: parsed?.originWidth ?? img.width,
115
+ height: parsed?.originHeight ?? img.height
116
+ };
117
+ }
118
+ return void 0;
119
+ }
120
+ function mapItemToViewState(item, pathPrefix, slugField, whitelist) {
121
+ const mapped = {
122
+ _id: item._id,
123
+ url: `${pathPrefix}/${item[slugField] || item._id}`
124
+ };
125
+ const keysToInclude = whitelist || Object.keys(item).filter((k) => !k.startsWith("_"));
126
+ const titleValue = item.title || item.name;
127
+ const altText = typeof titleValue === "string" ? titleValue : "";
128
+ keysToInclude.forEach((key) => {
129
+ const value = item[key];
130
+ if (isImageValue(value)) {
131
+ mapped[key] = mapImageField(value, altText);
132
+ } else {
133
+ mapped[key] = value || "";
134
+ }
135
+ });
136
+ return mapped;
137
+ }
138
+ const collectionList = makeJayStackComponent().withProps().withInteractive(ListInteractive);
139
+ const collectionCard = makeJayStackComponent().withProps();
140
+ const WIX_DATA_CONTEXT = createJayContext();
141
+ function provideWixDataContext() {
142
+ const wixClientContext = useGlobalContext(WIX_CLIENT_CONTEXT);
143
+ const wixClient = wixClientContext.client;
144
+ const itemsClient = wixClient.use(items);
145
+ const context = registerReactiveGlobalContext(WIX_DATA_CONTEXT, () => ({
146
+ items: itemsClient
147
+ }));
148
+ console.log("[wix-data] Client data context initialized");
149
+ return context;
150
+ }
151
+ function formatCellValue(value) {
152
+ if (value === null || value === void 0)
153
+ return "";
154
+ if (typeof value === "string")
155
+ return value;
156
+ if (typeof value === "number")
157
+ return String(value);
158
+ if (typeof value === "boolean")
159
+ return value ? "Yes" : "No";
160
+ if (value instanceof Date)
161
+ return value.toLocaleDateString();
162
+ if (typeof value === "object")
163
+ return JSON.stringify(value);
164
+ return String(value);
165
+ }
166
+ function TableInteractive(_props, refs, viewStateSignals, fastCarryForward, wixDataContext) {
167
+ const { currentPage: [currentPage, setCurrentPage], hasPrev: [hasPrev, setHasPrev], hasNext: [hasNext, setHasNext], sortField: [sortField, setSortField], sortDirection: [sortDirection, setSortDirection] } = viewStateSignals;
168
+ const { collectionId, pageSize, totalCount } = fastCarryForward;
169
+ const totalPages = Math.ceil(totalCount / pageSize);
170
+ const [rows, setRows] = createSignal([]);
171
+ const [isLoading, setIsLoading] = createSignal(false);
172
+ async function fetchPage(page) {
173
+ setIsLoading(true);
174
+ try {
175
+ const result = await wixDataContext.items.queryDataItems({
176
+ dataCollectionId: collectionId
177
+ }).limit(pageSize).skip((page - 1) * pageSize).find();
178
+ const newRows = result.items.map((item) => ({
179
+ _id: item._id,
180
+ url: `/${collectionId.toLowerCase()}/${item._id}`,
181
+ cells: fastCarryForward.columns.map((fieldName) => ({
182
+ fieldName,
183
+ value: formatCellValue(item[fieldName])
184
+ }))
185
+ }));
186
+ setRows(newRows);
187
+ setCurrentPage(page);
188
+ setHasPrev(page > 1);
189
+ setHasNext(page < totalPages);
190
+ } catch (error) {
191
+ console.error("[wix-data] Failed to fetch page:", error);
192
+ } finally {
193
+ setIsLoading(false);
194
+ }
195
+ }
196
+ refs.prevButton?.onclick(() => {
197
+ if (hasPrev() && !isLoading()) {
198
+ fetchPage(currentPage() - 1);
199
+ }
200
+ });
201
+ refs.nextButton?.onclick(() => {
202
+ if (hasNext() && !isLoading()) {
203
+ fetchPage(currentPage() + 1);
204
+ }
205
+ });
206
+ refs.columns?.headerButton?.onclick(({ coordinate }) => {
207
+ const [fieldName] = coordinate;
208
+ if (sortField() === fieldName) {
209
+ const newDirection = sortDirection() === "ASC" ? "DESC" : sortDirection() === "DESC" ? "NONE" : "ASC";
210
+ setSortDirection(newDirection);
211
+ if (newDirection === "NONE") {
212
+ setSortField(null);
213
+ }
214
+ } else {
215
+ setSortField(fieldName);
216
+ setSortDirection("ASC");
217
+ }
218
+ fetchPage(1);
219
+ });
220
+ return {
221
+ render: () => ({
222
+ currentPage: currentPage(),
223
+ hasPrev: hasPrev(),
224
+ hasNext: hasNext(),
225
+ sortField: sortField(),
226
+ sortDirection: sortDirection()
227
+ })
228
+ };
229
+ }
230
+ const collectionTable = makeJayStackComponent().withProps().withContexts(WIX_DATA_CONTEXT).withInteractive(TableInteractive);
231
+ const init = makeJayInit().withClient(async (data) => {
232
+ console.log("[wix-data] Initializing client-side data context...");
233
+ provideWixDataContext();
234
+ console.log(`[wix-data] Client initialization complete. Collections: ${data.collections.join(", ")}`);
235
+ });
236
+ export {
237
+ WIX_DATA_CONTEXT,
238
+ collectionCard,
239
+ collectionItem,
240
+ collectionList,
241
+ collectionTable,
242
+ init,
243
+ provideWixDataContext
244
+ };