@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/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,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
|
+
};
|