@storecraft/sdk 1.0.12 → 1.0.14

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/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * @import { StorecraftSDKConfig } from './types.js'
3
- * @import { App } from '@storecraft/core'
4
3
  */
5
4
  import Auth from './src/auth.js'
6
5
  import Customers from './src/customers.js'
@@ -22,6 +21,7 @@ import Notifications from './src/notifications.js'
22
21
  import Storage from './src/storage.js'
23
22
  import AI from './src/ai.js'
24
23
  import Search from './src/search.js'
24
+ import { fetchApiWithAuth, fetchOnlyApiResponseWithAuth } from './src/utils.api.fetch.js'
25
25
 
26
26
  /**
27
27
  * @description The official `storecraft` universal **SDK** for `javascript`
@@ -59,6 +59,48 @@ export class StorecraftSDK {
59
59
  this.notifications = new Notifications(this);
60
60
  }
61
61
 
62
+ /**
63
+ * @description
64
+ * - Prepends `backend` endpoint.
65
+ * - Fetches with `authentication` middleware.
66
+ * - Refreshed `auth` if needed.
67
+ * - Throws a `json` representation of the `error`,
68
+ * if the request is `bad`
69
+ *
70
+ * @template {any} [R=any]
71
+ *
72
+ * @param {string} path relative path in api
73
+ * @param {RequestInit} [init] request `init` type
74
+ * @param {URLSearchParams} [query] url search params
75
+ *
76
+ * @throws {error}
77
+ *
78
+ * @returns {Promise<R>}
79
+ */
80
+ fetchApiWithAuth = (path, init, query) => {
81
+ return fetchApiWithAuth(
82
+ this, path, init, query
83
+ )
84
+ }
85
+
86
+ /**
87
+ * @description
88
+ * - Prepends `backend` endpoint.
89
+ * - Fetches with `authentication` middleware.
90
+ * - Refreshed `auth` if needed.
91
+ *
92
+ * @param {string} path relative path in api
93
+ * @param {RequestInit} [init] request `init` type
94
+ * @param {URLSearchParams} [query] url search params
95
+ *
96
+ * @returns {Promise<Response>}
97
+ */
98
+ fetchOnlyApiResponseWithAuth = (path, init, query) => {
99
+ return fetchOnlyApiResponseWithAuth(
100
+ this, path, init, query
101
+ )
102
+ }
103
+
62
104
  /**
63
105
  * @param {StorecraftSDKConfig} [config]
64
106
  */
package/jsconfig.json CHANGED
@@ -1,6 +1,8 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "checkJs": true,
4
+ "allowJs": true,
5
+ "maxNodeModuleJsDepth": 100,
4
6
  "target": "ESNext",
5
7
  "module": "NodeNext",
6
8
  "moduleResolution": "NodeNext",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storecraft/sdk",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "description": "Official storecraft Universal Javascript SDK",
5
5
  "license": "MIT",
6
6
  "author": "Tomer Shalev (https://github.com/store-craft)",
package/src/ai.js CHANGED
@@ -2,16 +2,21 @@
2
2
  * @import {
3
3
  * AgentRunParameters, AgentRunResponse
4
4
  * } from '@storecraft/core/ai/agents/types.js'
5
+ * @import {
6
+ * HEADER_STORECRAFT_THREAD_ID_LITERAL
7
+ * } from '@storecraft/core/rest/con.ai.routes.js'
5
8
  * @import { content } from '@storecraft/core/ai/types.public.js'
6
9
  */
7
10
 
8
- import { HEADER_STORECRAFT_THREAD_ID } from '@storecraft/core/rest/con.ai.routes.js';
9
11
  import { StorecraftSDK } from '../index.js'
10
12
  import { url } from './utils.api.fetch.js';
11
13
 
14
+ const HEADER_STORECRAFT_THREAD_ID = /** @satisfies {HEADER_STORECRAFT_THREAD_ID_LITERAL} */ (
15
+ 'X-STORECRAFT-THREAD-ID'
16
+ );
17
+
12
18
  /**
13
19
  * @description **AI**
14
- *
15
20
  */
16
21
  export default class AI {
17
22
 
@@ -26,12 +31,13 @@ export default class AI {
26
31
  /**
27
32
  * @description Speak with the main `storecraft` agent sync. It is
28
33
  * recommended to use the streamed version {@link streamSpeak}
34
+ * @param {string} agent_handle agent identifier
29
35
  * @param {AgentRunParameters} params
30
36
  * @returns {Promise<AgentRunResponse>}
31
37
  */
32
- speak = async (params) => {
38
+ speak = async (agent_handle, params) => {
33
39
  const response = await fetch(
34
- url(this.sdk.config, 'ai/agent/run'),
40
+ url(this.sdk.config, `ai/agents/${agent_handle}/run`),
35
41
  {
36
42
  method: 'post',
37
43
  body: JSON.stringify(params),
@@ -46,11 +52,12 @@ export default class AI {
46
52
 
47
53
  /**
48
54
  * @description Stream Speak with the main `storecraft` agent via Server-Sent Events
55
+ * @param {string} agent_handle agent identifier
49
56
  * @param {AgentRunParameters} params
50
57
  */
51
- streamSpeak = async function(params) {
58
+ streamSpeak = async function(agent_handle, params) {
52
59
  const response = await fetch(
53
- url(this.sdk.config, 'ai/agent/stream'),
60
+ url(this.sdk.config, `ai/agents/${agent_handle}/stream`),
54
61
  {
55
62
  method: 'post',
56
63
  body: JSON.stringify(params),
@@ -60,7 +67,9 @@ export default class AI {
60
67
  }
61
68
  );
62
69
 
63
- const threadId = response.headers.get(HEADER_STORECRAFT_THREAD_ID ?? 'X-Storecraft-Thread-Id');
70
+ const threadId = response.headers.get(
71
+ HEADER_STORECRAFT_THREAD_ID ?? 'X-Storecraft-Thread-Id'
72
+ );
64
73
 
65
74
  if(!threadId) {
66
75
  throw new Error(
@@ -93,7 +102,6 @@ const sleep = (ms=100) => {
93
102
  */
94
103
  const StreamSpeakGenerator = async function *(stream) {
95
104
  for await (const sse of SSEGenerator(stream)) {
96
- await sleep(50);
97
105
  yield ( /** @type {content} */ (JSON.parse(sse.data)));
98
106
  }
99
107
  }
package/src/auth.js CHANGED
@@ -1,19 +1,16 @@
1
1
  /**
2
2
  * @import {
3
- * ApiAuthChangePasswordType, ApiAuthResult, ApiAuthSigninType, ApiAuthSignupType,
4
- * ApiKeyResult, ApiQuery, AuthUserType, error,
5
- OAuthProvider,
6
- OAuthProviderCreateURIParams,
7
- OAuthProviderCreateURIResponse,
8
- OAuthProvidersList,
9
- SignWithOAuthProviderParams
3
+ * ApiAuthChangePasswordType, ApiAuthResult, ApiAuthSigninType,
4
+ * ApiAuthSignupType, ApiKeyResult, ApiQuery, AuthUserType, error,
5
+ * OAuthProvider, OAuthProviderCreateURIParams,
6
+ * OAuthProviderCreateURIResponse, SignWithOAuthProviderParams
10
7
  * } from '@storecraft/core/api'
11
8
  * @import { SdkConfigAuth } from '../types.js';
12
9
  */
13
10
 
14
11
  import { api_query_to_searchparams } from '@storecraft/core/api/utils.query.js';
15
12
  import { StorecraftSDK } from '../index.js';
16
- import { fetchApiWithAuth, url } from './utils.api.fetch.js';
13
+ import { count_query_of_resource, fetchApiWithAuth, url } from './utils.api.fetch.js';
17
14
  import { assert } from './utils.functional.js';
18
15
 
19
16
 
@@ -381,7 +378,7 @@ export default class Auth {
381
378
  this.#sdk,
382
379
  '/auth/apikeys',
383
380
  {
384
- method: 'post'
381
+ method: 'post',
385
382
  }
386
383
  );
387
384
 
@@ -424,10 +421,7 @@ export default class Auth {
424
421
  }
425
422
 
426
423
  /**
427
- *
428
- *
429
424
  * @param {ApiQuery<AuthUserType>} query
430
- *
431
425
  */
432
426
  list_auth_users = async (query) => {
433
427
  const sq = api_query_to_searchparams(query);
@@ -442,6 +436,18 @@ export default class Auth {
442
436
  return items;
443
437
  }
444
438
 
439
+ /**
440
+ * @param {ApiQuery<AuthUserType>} query
441
+ */
442
+ count_auth_users_query = async (query) => {
443
+ const sq = api_query_to_searchparams(query);
444
+ return count_query_of_resource(
445
+ this.#sdk,
446
+ `/auth/users`,
447
+ query
448
+ )
449
+ }
450
+
445
451
 
446
452
  list_api_keys_auth_users = async () => {
447
453
 
@@ -458,15 +464,15 @@ export default class Auth {
458
464
  }
459
465
 
460
466
  identity_providers_list = async () => {
461
- /** @type {OAuthProvider[]} */
462
- const items = await fetchApiWithAuth(
463
- this.#sdk,
464
- '/auth/identity-providers',
465
- {
466
- method: 'get'
467
- }
468
- );
469
- return items;
467
+ /** @type {OAuthProvider[]} */
468
+ const items = await fetchApiWithAuth(
469
+ this.#sdk,
470
+ '/auth/identity-providers',
471
+ {
472
+ method: 'get'
473
+ }
474
+ );
475
+ return items;
470
476
  }
471
477
 
472
478
  /**
@@ -478,7 +484,7 @@ export default class Auth {
478
484
  /** @type {OAuthProviderCreateURIResponse} */
479
485
  const result = await fetchApiWithAuth(
480
486
  this.#sdk,
481
- '/auth/identity-providers/create_authorization_uri',
487
+ '/auth/identity-providers/create-authorization-uri',
482
488
  {
483
489
  method: 'post',
484
490
  body: JSON.stringify(params),
@@ -1,9 +1,13 @@
1
1
  /**
2
- * @import { CollectionType, CollectionTypeUpsert } from '@storecraft/core/api'
2
+ * @import {
3
+ * ApiQuery, CollectionType, CollectionTypeUpsert, ProductType, VariantType
4
+ * } from '@storecraft/core/api'
3
5
  */
4
6
  import { StorecraftSDK } from '../index.js'
5
- import { collection_base, fetchApiWithAuth } from './utils.api.fetch.js';
6
- import { filter_fields, filter_unused } from './utils.functional.js';
7
+ import {
8
+ collection_base, count_query_of_resource,
9
+ fetchApiWithAuth, list_from_collection_resource
10
+ } from './utils.api.fetch.js';
7
11
 
8
12
  /**
9
13
  * @description Base `collections` **CRUD**
@@ -41,34 +45,55 @@ export default class Collections extends collection_base {
41
45
  return result
42
46
  }
43
47
 
44
- // /**
45
- // * Add tags in bulk to products in collection
46
- // * @param {string} colId
47
- // * @param {string[]} tags
48
- // * @param {boolean} add true for add false for remove
49
- // */
50
- // bulkAddRemoveTags = async (colId, tags, add=true) => {
48
+ /**
49
+ * @description List all the tags of products in a collection, This is helpful
50
+ * for building a filter system in the frontend if you know in advance all
51
+ * the tags of the products in a collection
52
+ *
53
+ * @param {string} id_or_handle Collection `id` or `handle`
54
+ * @return {Promise<string[]>} List of tags
55
+ */
56
+ list_used_products_tags = async (id_or_handle) => {
57
+ const result = await fetchApiWithAuth(
58
+ this.sdk,
59
+ `collections/${id_or_handle}/products/used_tags`,
60
+ {
61
+ method: 'get'
62
+ }
63
+ );
51
64
 
52
- // // first get all products in collection
53
- // const tag_all = tags ?? []
54
- // const tag_all_prefixed = tag_all.map(t => `tag:${t}`)
55
- // const tag_vs = tag_all.map(it => it.split('_').pop())
65
+ return result
66
+ }
56
67
 
57
- // var products = await this.context.products.list([`col:${colId}`], 10000)
58
- // // console.log('products ', products)
59
- // // console.log('colId ', colId)
60
- // const batch = writeBatch(this.context.firebase.db)
61
- // products.forEach(it => {
62
- // const ref = doc(this.context.firebase.db, 'products', it[0])
63
- // batch.update(ref, {
64
- // tags : add ? arrayUnion(...tags) : arrayRemove(...tags),
65
- // search : add ? arrayUnion(...tag_all, ...tag_all_prefixed, ...tag_vs) :
66
- // arrayRemove(...tag_all, ...tag_all_prefixed, ...tag_vs),
67
- // updatedAt : Date.now()
68
- // })
69
- // })
70
- // await batch.commit()
68
+ /**
69
+ * @description Query the `products` in a collection
70
+ *
71
+ * @param {string} id_or_handle Collection `id` or `handle`
72
+ * @param {ApiQuery<(ProductType | VariantType)>} query query
73
+ * @return {Promise<(ProductType | VariantType)[]>} List of products in collection
74
+ */
75
+ query_collection_products = async (id_or_handle, query) => {
76
+ const result = list_from_collection_resource(
77
+ this.sdk,
78
+ `collections/${id_or_handle}/products`,
79
+ query
80
+ );
81
+ return result
82
+ }
71
83
 
72
- // }
84
+ /**
85
+ * @description Count the number of `products` in a collection by a query
86
+ *
87
+ * @param {string} id_or_handle Collection `id` or `handle`
88
+ * @param {ApiQuery<(ProductType | VariantType)>} query query
89
+ * @return {Promise<number>} count
90
+ */
91
+ count_collection_products_query = async (id_or_handle, query) => {
92
+ return count_query_of_resource(
93
+ this.sdk,
94
+ `collections/${id_or_handle}/products`,
95
+ query
96
+ );
97
+ }
73
98
 
74
99
  }
package/src/customers.js CHANGED
@@ -1,8 +1,13 @@
1
1
  /**
2
- * @import { CustomerType, CustomerTypeUpsert } from '@storecraft/core/api'
2
+ * @import {
3
+ * ApiQuery, CustomerType, CustomerTypeUpsert, OrderData
4
+ * } from '@storecraft/core/api'
3
5
  */
4
6
  import { StorecraftSDK } from '../index.js'
5
- import { collection_base } from './utils.api.fetch.js';
7
+ import {
8
+ collection_base, count_query_of_resource,
9
+ list_from_collection_resource
10
+ } from './utils.api.fetch.js';
6
11
 
7
12
  /**
8
13
  * @description Base `customers` **CRUD**
@@ -18,4 +23,37 @@ export default class Customers extends collection_base {
18
23
  constructor(sdk) {
19
24
  super(sdk, 'customers');
20
25
  }
26
+
27
+ /**
28
+ * @description Query customer orders, this is only available to admin and
29
+ * the customer (with auth token)
30
+ *
31
+ * @param {string} id_or_handle customer `id` or `handle` or `email`
32
+ * @param {ApiQuery<OrderData>} query query
33
+ * @return {Promise<OrderData[]>} List of orders of customer
34
+ */
35
+ query_customer_orders = async (id_or_handle, query) => {
36
+ const result = list_from_collection_resource(
37
+ this.sdk,
38
+ `customers/${id_or_handle}/orders`,
39
+ query
40
+ );
41
+ return result
42
+ }
43
+
44
+ /**
45
+ * @description Count the number of orders of a specific
46
+ * customer with a query
47
+ *
48
+ * @param {string} id_or_handle customer `id` or `handle` or `email`
49
+ * @param {ApiQuery<OrderData>} query query
50
+ * @return {Promise<number>} count
51
+ */
52
+ count_customer_orders_query = async (id_or_handle, query) => {
53
+ return count_query_of_resource(
54
+ this.sdk,
55
+ `customers/${id_or_handle}/orders`,
56
+ query
57
+ );
58
+ }
21
59
  }
package/src/discounts.js CHANGED
@@ -1,8 +1,16 @@
1
1
  /**
2
- * @import { DiscountType, DiscountTypeUpsert } from '@storecraft/core/api'
2
+ * @import {
3
+ * ApiQuery, DiscountType, DiscountTypeUpsert,
4
+ ProductType,
5
+ VariantType
6
+ * } from '@storecraft/core/api'
3
7
  */
4
8
  import { StorecraftSDK } from '../index.js'
5
- import { collection_base } from './utils.api.fetch.js';
9
+ import {
10
+ collection_base, count_query_of_resource,
11
+ fetchApiWithAuth,
12
+ list_from_collection_resource
13
+ } from './utils.api.fetch.js';
6
14
 
7
15
  /**
8
16
  * @description Base `discounts` **CRUD**
@@ -19,4 +27,57 @@ export default class Discounts extends collection_base {
19
27
  super(sdk, 'discounts');
20
28
  }
21
29
 
30
+ /**
31
+ * @description Each discount has eligible products,
32
+ * you can query and filter these products by discount
33
+ *
34
+ * @param {string} id_or_handle discount `id` or `handle`
35
+ * @param {ApiQuery<ProductType | VariantType>} query query
36
+ * @return {Promise<(ProductType | VariantType)[]>} List of discounts
37
+ */
38
+ query_discount_products = async (id_or_handle, query) => {
39
+ const result = list_from_collection_resource(
40
+ this.sdk,
41
+ `discounts/${id_or_handle}/products`,
42
+ query
43
+ );
44
+ return result;
45
+ }
46
+
47
+ /**
48
+ * @description Each discount has eligible products,
49
+ * you can count the query products by discount
50
+ *
51
+ * @param {string} id_or_handle discount `id` or `handle`
52
+ * @param {ApiQuery<ProductType | VariantType>} query query
53
+ * @return {Promise<number>} count
54
+ */
55
+ count_discount_products_query = async (id_or_handle, query) => {
56
+ return count_query_of_resource(
57
+ this.sdk,
58
+ `discounts/${id_or_handle}/products`,
59
+ query
60
+ );
61
+ }
62
+
63
+ /**
64
+ * @description List all the tags of products in a collection, This is helpful
65
+ * for building a filter system in the frontend if you know in advance all
66
+ * the tags of the products in a collection
67
+ *
68
+ * @param {string} id_or_handle Discount `id` or `handle`
69
+ * @return {Promise<string[]>} List of tags
70
+ */
71
+ list_used_discount_products_tags = async (id_or_handle) => {
72
+ const result = await fetchApiWithAuth(
73
+ this.sdk,
74
+ `discounts/${id_or_handle}/products/used_tags`,
75
+ {
76
+ method: 'get'
77
+ }
78
+ );
79
+
80
+ return result
81
+ }
82
+
22
83
  }
package/src/payments.js CHANGED
@@ -3,7 +3,8 @@
3
3
  */
4
4
  import { StorecraftSDK } from '../index.js'
5
5
  import {
6
- fetchApiWithAuth, get_from_collection_resource, list_from_collection_resource
6
+ fetchApiWithAuth, get_from_collection_resource,
7
+ list_from_collection_resource
7
8
  } from './utils.api.fetch.js';
8
9
 
9
10
  /**
package/src/products.js CHANGED
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { StorecraftSDK } from '../index.js'
5
5
  import {
6
- collection_base, fetchOnlyApiResponseWithAuth
6
+ collection_base, fetchApiWithAuth, fetchOnlyApiResponseWithAuth
7
7
  } from './utils.api.fetch.js';
8
8
 
9
9
  /**
@@ -21,6 +21,26 @@ export default class Products extends collection_base {
21
21
  super(sdk, 'products');
22
22
  }
23
23
 
24
+ /**
25
+ * @description List all of the tags of all the products deduped,
26
+ * This is helpful for building a filter system in the frontend if
27
+ * you know in advance all the tags of the products in a collection,
28
+ * also see the collection confined version db_collections.list_collection_products_tags
29
+ *
30
+ * @return {Promise<string[]>} List of tags
31
+ */
32
+ list_used_tags = async () => {
33
+ const result = await fetchApiWithAuth(
34
+ this.sdk,
35
+ `products/used_tags`,
36
+ {
37
+ method: 'get'
38
+ }
39
+ );
40
+
41
+ return result
42
+ }
43
+
24
44
  /**
25
45
  *
26
46
  * Change stock quantity of a `product` by a delta difference
package/src/search.js CHANGED
@@ -1,7 +1,4 @@
1
1
  /**
2
- * @import {
3
- * AgentRunParameters, AgentRunResponse
4
- * } from '@storecraft/core/ai/agents/types.js'
5
2
  * @import {
6
3
  * ApiQuery, QuickSearchResult, SimilaritySearchInput, SimilaritySearchResult
7
4
  * } from '@storecraft/core/api'
@@ -9,9 +6,8 @@
9
6
 
10
7
  import {
11
8
  api_query_to_searchparams, object_to_search_params,
12
- parse_query, string_array_to_string
9
+ string_array_to_string
13
10
  } from '@storecraft/core/api/utils.query.js';
14
- import { HEADER_STORECRAFT_THREAD_ID } from '@storecraft/core/rest/con.ai.routes.js';
15
11
  import { StorecraftSDK } from '../index.js'
16
12
  import { fetchApiWithAuth, url } from './utils.api.fetch.js';
17
13
 
package/src/storage.js CHANGED
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * @import { StorageFeatures, StorageSignedOperation } from '@storecraft/core/storage'
3
- * @import { error } from '@storecraft/core/api'
4
3
  */
5
4
 
6
5
  import { StorecraftSDK } from '../index.js'
@@ -190,30 +189,35 @@ export default class Storage {
190
189
  * - Else. it is assumed to be a public `url`, and will
191
190
  * return the given url.
192
191
  *
192
+ * @template {false | true} [IS_IMAGE=true]
193
193
  * @param {string} url
194
- * @param {boolean} isImage
194
+ * @param {IS_IMAGE} [isImage=true]
195
+ * @returns {Promise<IS_IMAGE extends true ? string : any>}
195
196
  */
196
- getSource = async (url, isImage=true) => {
197
+ getSource = async (url, isImage=(/** @type {IS_IMAGE} */ (true))) => {
197
198
  try {
198
199
 
199
200
  const is_storage = url.startsWith('storage://');
200
201
 
201
202
  // if we havent found a driver, rturn the url
202
203
  if(!is_storage)
203
- return url;
204
+ return /** @type {IS_IMAGE extends true ? string : any} */(url);
204
205
 
205
206
  const key = url.split('storage://').at(-1);
206
207
  const blob = await this.getBlob(key);
207
208
 
208
- if(isImage)
209
- return URL.createObjectURL(blob)
209
+ if(isImage) {
210
+ return /** @type {IS_IMAGE extends true ? string : any} */ (
211
+ URL.createObjectURL(blob)
212
+ )
213
+ }
210
214
  else
211
215
  return blob.text().then(JSON.parse)
212
216
  } catch(e) {
213
217
  console.log(e)
214
218
  }
215
219
 
216
- return url;
220
+ return /** @type {IS_IMAGE extends true ? string : any} */ (url);
217
221
  }
218
222
 
219
223
 
@@ -6,7 +6,6 @@
6
6
  import {
7
7
  api_query_to_searchparams
8
8
  } from '@storecraft/core/api/utils.query.js';
9
- import { assert } from './utils.functional.js';
10
9
 
11
10
 
12
11
  /**
@@ -43,7 +42,9 @@ export const url = (config, path, query) => {
43
42
  *
44
43
  * @returns {Promise<Response>}
45
44
  */
46
- export const fetchOnlyApiResponseWithAuth = async (sdk, path, init={}, query=undefined) => {
45
+ export const fetchOnlyApiResponseWithAuth = async (
46
+ sdk, path, init={}, query=undefined
47
+ ) => {
47
48
 
48
49
  const auth_token = await sdk.auth.working_auth_token();
49
50
  const auth_header_value = (
@@ -63,12 +64,6 @@ export const fetchOnlyApiResponseWithAuth = async (sdk, path, init={}, query=und
63
64
  );
64
65
 
65
66
  return response;
66
-
67
- // const auth_problem = response.status >= 400 && response.status < 500;
68
-
69
- // if(auth_problem) {
70
-
71
- // }
72
67
  }
73
68
 
74
69
 
@@ -80,7 +75,7 @@ export const fetchOnlyApiResponseWithAuth = async (sdk, path, init={}, query=und
80
75
  * - Throws a `json` representation of the `error`,
81
76
  * if the request is `bad`
82
77
  *
83
- * @template {any} R
78
+ * @template {any} [R=any]
84
79
  *
85
80
  * @param {StorecraftSDK} sdk
86
81
  * @param {string} path relative path in api
@@ -91,7 +86,9 @@ export const fetchOnlyApiResponseWithAuth = async (sdk, path, init={}, query=und
91
86
  *
92
87
  * @returns {Promise<R>}
93
88
  */
94
- export const fetchApiWithAuth = async (sdk, path, init={}, query=undefined) => {
89
+ export const fetchApiWithAuth = async (
90
+ sdk, path, init={}, query=undefined
91
+ ) => {
95
92
 
96
93
  const response = await fetchOnlyApiResponseWithAuth(
97
94
  sdk, path, init, query
@@ -163,6 +160,24 @@ export async function upsert_to_collection_resource(sdk, resource, item) {
163
160
  );
164
161
  }
165
162
 
163
+ /**
164
+ * @description Count the number of items in a query
165
+ * @template G
166
+ * @param {StorecraftSDK} sdk
167
+ * @param {string} resource base path of resource
168
+ * @param {ApiQuery<G>} [query] the query
169
+ * @returns {Promise<number>} count
170
+ */
171
+ export async function count_query_of_resource(sdk, resource, query) {
172
+ const sq = api_query_to_searchparams(query);
173
+ return fetchApiWithAuth(
174
+ sdk,
175
+ `${resource}/count_query?${sq.toString()}`,
176
+ {
177
+ method: 'get',
178
+ }
179
+ ).then((result) => Number(result.count));
180
+ }
166
181
 
167
182
  /**
168
183
  *
@@ -230,7 +245,7 @@ export class collection_base {
230
245
  /**
231
246
  *
232
247
  * @param {StorecraftSDK} sdk storecraft sdk
233
- * @param {string} base_name base name of resource type
248
+ * @param {string} base_name base path of resource type
234
249
  */
235
250
  constructor(sdk, base_name) {
236
251
  this.#sdk = sdk;
@@ -272,16 +287,21 @@ export class collection_base {
272
287
  }
273
288
 
274
289
  /**
275
- *
276
290
  * @param {ApiQuery<G>} query Query object
277
- *
278
- *
279
291
  * @returns {Promise<G[]>}
280
292
  */
281
293
  async list(query) {
282
294
  return list_from_collection_resource(this.sdk, this.base_name, query);
283
295
  }
284
296
 
297
+ /**
298
+ * @description Count the number of items in a query
299
+ * @param {ApiQuery<G>} query Query object
300
+ */
301
+ async count_query(query) {
302
+ return count_query_of_resource(this.sdk, this.base_name, query);
303
+ }
304
+
285
305
  get base_name() {
286
306
  return this.#base_name;
287
307
  }