saasufy-components 8.3.0 → 8.4.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 +71 -1
- package/package.json +2 -2
- package/utils.js +129 -1
package/README.md
CHANGED
|
@@ -83,7 +83,7 @@ This project does not impose any specific approach to styling components, howeve
|
|
|
83
83
|
<link href="https://saasufy.com/node_modules/saasufy-components/styles.css" rel="stylesheet" />
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
-
###
|
|
86
|
+
### Template utility functions
|
|
87
87
|
|
|
88
88
|
The following utility functions can be used anywhere inside template `{{expression}}` placeholders:
|
|
89
89
|
|
|
@@ -99,6 +99,65 @@ The following utility functions can be used anywhere inside template `{{expressi
|
|
|
99
99
|
- Make a string safe by escaping HTML characters: `{{safeString(Product.name)}}`
|
|
100
100
|
- Given an array of objects, extract the values from the specified field and join them together into a single comma-separated string: `{{joinFields(Product, 'name')}}`
|
|
101
101
|
|
|
102
|
+
### General utility functions
|
|
103
|
+
|
|
104
|
+
The following utility functions are available for advanced use cases and can be imported from the `saasufy-components` module:
|
|
105
|
+
|
|
106
|
+
**Import from CDN:**
|
|
107
|
+
```javascript
|
|
108
|
+
import {
|
|
109
|
+
getRecordIds,
|
|
110
|
+
getRecord,
|
|
111
|
+
getRecordCount,
|
|
112
|
+
generateRecords,
|
|
113
|
+
getRecords,
|
|
114
|
+
logAttributeChanges,
|
|
115
|
+
wait
|
|
116
|
+
} from 'https://saasufy.com/node_modules/saasufy-components/utils.js';
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Import from npm:**
|
|
120
|
+
```javascript
|
|
121
|
+
import {
|
|
122
|
+
getRecordIds,
|
|
123
|
+
getRecord,
|
|
124
|
+
getRecordCount,
|
|
125
|
+
generateRecords,
|
|
126
|
+
getRecords,
|
|
127
|
+
logAttributeChanges,
|
|
128
|
+
wait
|
|
129
|
+
} from 'saasufy-components/utils.js';
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Available functions:**
|
|
133
|
+
|
|
134
|
+
- `getRecordIds({ socket, type, viewName, viewParams, startPage, maxPages, pageSize })`: Retrieves record IDs from a collection view across multiple pages. Returns an array of IDs.
|
|
135
|
+
- `getRecord({ socket, type, id, fields })`: Fetches a single record by ID. If fields are specified, only those fields are retrieved; otherwise the entire record is returned.
|
|
136
|
+
- `getRecordCount({ socket, type, viewName, viewParams, offset })`: Gets the total count of records in a view. Returns a number representing the count.
|
|
137
|
+
- `generateRecords({ socket, type, viewName, viewParams, fields, startPage, pageSize, maxAttempts })`: An async generator function that yields records one by one from a collection view. Useful for processing large datasets efficiently.
|
|
138
|
+
|
|
139
|
+
**Example usage:**
|
|
140
|
+
```javascript
|
|
141
|
+
// Process all products one by one without loading them all into memory at once
|
|
142
|
+
const productGenerator = generateRecords({
|
|
143
|
+
socket: mySocket,
|
|
144
|
+
type: 'Product',
|
|
145
|
+
viewName: 'alphabeticalView',
|
|
146
|
+
viewParams: { category: 'electronics' },
|
|
147
|
+
fields: [ 'name', 'price', 'qty' ],
|
|
148
|
+
pageSize: 50
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
for await (let product of productGenerator) {
|
|
152
|
+
console.log(`Processing product: ${product.name}`);
|
|
153
|
+
// Process each product individually
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
- `getRecords({ socket, type, viewName, viewParams, fields, startPage, maxPages, pageSize })`: Retrieves multiple records from a collection view. Returns an array of record objects.
|
|
157
|
+
- `logAttributeChanges(...elementSelectors)`: Logs attribute changes for DOM elements to the console. Useful for debugging. Returns a function to stop observing.
|
|
158
|
+
- `wait(duration)`: Creates a promise that resolves after the specified duration in milliseconds. Useful for adding delays in async operations.
|
|
159
|
+
|
|
160
|
+
|
|
102
161
|
### Special fields
|
|
103
162
|
|
|
104
163
|
Saasufy supports a number of special metadata fields which are automatically created and updated on each model record.
|
|
@@ -120,6 +179,17 @@ You will need to deploy your Saasufy service from the dashboard for the changes
|
|
|
120
179
|
The `id` field in Saasufy is extra special; you can optionally specify it when you create a new resource, however, if it is not specified, Saasufy will create one for you.
|
|
121
180
|
Saasufy offers utility functions for creating IDs (random and deterministic). See the previous `Utility functions` section.
|
|
122
181
|
|
|
182
|
+
### Events
|
|
183
|
+
|
|
184
|
+
The Saasufy `collection-viewer` component dispatches the following events when records are created, updated or removed from an active collection as a result of user interactions with that component:
|
|
185
|
+
|
|
186
|
+
- `collectionCreate`
|
|
187
|
+
- `collectionUpdate`
|
|
188
|
+
- `collectionDelete`
|
|
189
|
+
|
|
190
|
+
These events are set to bubble so you can listen to them on any parent element in the DOM.
|
|
191
|
+
The event object passed to the listener function has a `detail` property which is an object with a `type` and `id` property to indicate the type and id of the affected resource within the collection.
|
|
192
|
+
|
|
123
193
|
### File hosting
|
|
124
194
|
|
|
125
195
|
Saasufy provides basic HTTP/HTTPS file hosting functionality with support for client-side caching (with `ETag`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "saasufy-components",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.4.0",
|
|
4
4
|
"description": "Components for Saasufy.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"ag-collection": "^5.4.0",
|
|
21
21
|
"ag-model": "^3.2.0",
|
|
22
22
|
"capitalisk-auth-client": "^1.5.3",
|
|
23
|
-
"socketcluster-client": "^19.2.
|
|
23
|
+
"socketcluster-client": "^19.2.7",
|
|
24
24
|
"uuid": "^8.3.2"
|
|
25
25
|
},
|
|
26
26
|
"bugs": {
|
package/utils.js
CHANGED
|
@@ -510,6 +510,134 @@ export function formatError(error) {
|
|
|
510
510
|
return error.code === 1009 ? 'Resource exceeded the maximum size' : error.message;
|
|
511
511
|
}
|
|
512
512
|
|
|
513
|
+
export function getBatches(list, batchSize) {
|
|
514
|
+
let batches = [];
|
|
515
|
+
let currentBatch = [];
|
|
516
|
+
for (let item of list) {
|
|
517
|
+
if (currentBatch.length >= batchSize) {
|
|
518
|
+
batches.push(currentBatch);
|
|
519
|
+
currentBatch = [];
|
|
520
|
+
}
|
|
521
|
+
currentBatch.push(item);
|
|
522
|
+
}
|
|
523
|
+
if (currentBatch.length) {
|
|
524
|
+
batches.push(currentBatch);
|
|
525
|
+
}
|
|
526
|
+
return batches;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
export async function getRecordIds({ socket, type, viewName, viewParams, startPage, maxPages, pageSize }) {
|
|
530
|
+
if (startPage == null) startPage = 0;
|
|
531
|
+
if (maxPages == null) maxPages = 10;
|
|
532
|
+
if (pageSize == null) pageSize = 100;
|
|
533
|
+
let currentPage = startPage;
|
|
534
|
+
let lastPage = currentPage + maxPages;
|
|
535
|
+
let isLastPage = false;
|
|
536
|
+
let modelIds = [];
|
|
537
|
+
while (!isLastPage && currentPage < lastPage) {
|
|
538
|
+
let result = await socket.invoke('crud', {
|
|
539
|
+
action: 'read',
|
|
540
|
+
type,
|
|
541
|
+
offset: currentPage * pageSize,
|
|
542
|
+
view: viewName,
|
|
543
|
+
viewParams,
|
|
544
|
+
pageSize
|
|
545
|
+
});
|
|
546
|
+
isLastPage = result.isLastPage;
|
|
547
|
+
modelIds.push(...result.data);
|
|
548
|
+
currentPage++;
|
|
549
|
+
}
|
|
550
|
+
return modelIds;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
export async function getRecord({ socket, type, id, fields }) {
|
|
554
|
+
if (!fields) {
|
|
555
|
+
return socket.invoke('crud', {
|
|
556
|
+
action: 'read',
|
|
557
|
+
type,
|
|
558
|
+
id
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
let data = await Promise.all(
|
|
562
|
+
fields.map(async (field) => {
|
|
563
|
+
return socket.invoke('crud', {
|
|
564
|
+
action: 'read',
|
|
565
|
+
type,
|
|
566
|
+
id,
|
|
567
|
+
field
|
|
568
|
+
});
|
|
569
|
+
})
|
|
570
|
+
);
|
|
571
|
+
let fieldCount = fields.length;
|
|
572
|
+
let record = {};
|
|
573
|
+
for (let i = 0; i < fieldCount; i++) {
|
|
574
|
+
record[fields[i]] = data[i];
|
|
575
|
+
}
|
|
576
|
+
return record;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
export async function getRecordCount({ socket, type, viewName, viewParams, offset }) {
|
|
580
|
+
let result = await socket.invoke('crud', {
|
|
581
|
+
action: 'read',
|
|
582
|
+
type,
|
|
583
|
+
offset: offset || 0,
|
|
584
|
+
view: viewName,
|
|
585
|
+
viewParams,
|
|
586
|
+
pageSize: 0,
|
|
587
|
+
getCount: true
|
|
588
|
+
});
|
|
589
|
+
return Math.max(result.count - offset, 0);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
export async function* generateRecords({ socket, type, viewName, viewParams, fields, startPage, pageSize, maxAttempts }) {
|
|
593
|
+
if (startPage == null) startPage = 0;
|
|
594
|
+
if (pageSize == null) pageSize = 100;
|
|
595
|
+
if (maxAttempts == null) maxAttempts = 10;
|
|
596
|
+
|
|
597
|
+
let currentPage = startPage;
|
|
598
|
+
let isLastPage = false;
|
|
599
|
+
let failureCount = 0;
|
|
600
|
+
|
|
601
|
+
while (!isLastPage) {
|
|
602
|
+
try {
|
|
603
|
+
let result = await socket.invoke('crud', {
|
|
604
|
+
action: 'read',
|
|
605
|
+
type,
|
|
606
|
+
offset: currentPage * pageSize,
|
|
607
|
+
view: viewName,
|
|
608
|
+
viewParams,
|
|
609
|
+
pageSize
|
|
610
|
+
});
|
|
611
|
+
let idList = result.data || [];
|
|
612
|
+
for (let id of idList) {
|
|
613
|
+
yield await getRecord({ socket, type, id, fields });
|
|
614
|
+
}
|
|
615
|
+
isLastPage = result.isLastPage;
|
|
616
|
+
currentPage++;
|
|
617
|
+
failureCount = 0;
|
|
618
|
+
} catch (error) {
|
|
619
|
+
if (++failureCount > maxAttempts) {
|
|
620
|
+
throw new Error(`Record generation failed after max consecutive attempts because of error: ${error.message}`);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
export async function getRecords({ socket, type, viewName, viewParams, fields, startPage, maxPages, pageSize }) {
|
|
627
|
+
let recordIds = await getRecordIds({ socket, type, viewName, viewParams, startPage, maxPages, pageSize });
|
|
628
|
+
let recordIdBatches = getBatches(recordIds, pageSize);
|
|
629
|
+
let records = [];
|
|
630
|
+
for (let idBatch of recordIdBatches) {
|
|
631
|
+
let recordBatch = await Promise.all(
|
|
632
|
+
idBatch.map(async (id) => {
|
|
633
|
+
return getRecord({ socket, type, id, fields });
|
|
634
|
+
})
|
|
635
|
+
);
|
|
636
|
+
records.push(...recordBatch);
|
|
637
|
+
}
|
|
638
|
+
return records;
|
|
639
|
+
}
|
|
640
|
+
|
|
513
641
|
export function logAttributeChanges(...elementSelectors) {
|
|
514
642
|
let domObserverCallback = (mutationList) => {
|
|
515
643
|
for (let mutation of mutationList) {
|
|
@@ -544,4 +672,4 @@ export function logAttributeChanges(...elementSelectors) {
|
|
|
544
672
|
|
|
545
673
|
export function wait(duration) {
|
|
546
674
|
return new Promise((resolve) => setTimeout(resolve, duration));
|
|
547
|
-
}
|
|
675
|
+
}
|