saasufy-components 8.3.1 → 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.
Files changed (3) hide show
  1. package/README.md +71 -1
  2. package/package.json +1 -1
  3. 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
- ### Utility functions
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.1",
3
+ "version": "8.4.0",
4
4
  "description": "Components for Saasufy.",
5
5
  "main": "index.js",
6
6
  "scripts": {
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
+ }