@storecraft/sdk 0.1.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.
@@ -0,0 +1,74 @@
1
+ import { StorecraftSDK } from '../index.js'
2
+ import {
3
+ collection_base, fetchOnlyApiResponseWithAuth
4
+ } from './utils.api.fetch.js';
5
+
6
+ /**
7
+ * Base `products` **CRUD**
8
+ *
9
+ * @extends {collection_base<
10
+ * import('@storecraft/core/v-api').ProductTypeUpsert,
11
+ * import('@storecraft/core/v-api').ProductType>
12
+ * }
13
+ */
14
+ export default class Products extends collection_base {
15
+
16
+ /**
17
+ *
18
+ * @param {StorecraftSDK} sdk
19
+ */
20
+ constructor(sdk) {
21
+ super(sdk, 'products');
22
+ }
23
+
24
+ /**
25
+ *
26
+ * Change stock quantity of a `product` by a delta difference
27
+ * number.
28
+ *
29
+ * @param {string} id_or_handle `id` ot `handle`
30
+ * @param {number} howmuch a diff number by how much to update stock
31
+ */
32
+ changeStockOfBy = async (id_or_handle, howmuch) => {
33
+ const response = await fetchOnlyApiResponseWithAuth(
34
+ this.sdk,
35
+ `products/${id_or_handle}?quantityBy=${howmuch}`,
36
+ {
37
+ method: 'put'
38
+ }
39
+ );
40
+
41
+ return response.ok;
42
+ }
43
+
44
+ /**
45
+ * Add `products` to `collection`
46
+ *
47
+ * @param {import('@storecraft/core/v-api').ProductType[]} products
48
+ * @param {import('@storecraft/core/v-api').CollectionType} collection
49
+ */
50
+ batchAddProductsToCollection = async (products, collection) => {
51
+ for (const pr of products) {
52
+ await this.upsert({
53
+ ...pr,
54
+ collections: [...(pr.collections??[]), collection]
55
+ });
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Remove `products` from `collection`
61
+ *
62
+ * @param {import('@storecraft/core/v-api').ProductType[]} products
63
+ * @param {import('@storecraft/core/v-api').CollectionType} collection
64
+ */
65
+ batchRemoveProductsFromCollection = async (products, collection) => {
66
+ for (const pr of products) {
67
+ await this.upsert({
68
+ ...pr,
69
+ collections: (pr.collections??[]).filter(c => c.id!==collection.id)
70
+ });
71
+ }
72
+ }
73
+
74
+ }
@@ -0,0 +1,19 @@
1
+ import { StorecraftSDK } from '../index.js'
2
+ import { collection_base } from './utils.api.fetch.js';
3
+
4
+ /**
5
+ * Base `settings` **CRUD**
6
+ *
7
+ * @extends {collection_base<any, any>}
8
+ */
9
+ export default class Settings extends collection_base {
10
+
11
+ /**
12
+ *
13
+ * @param {StorecraftSDK} sdk
14
+ */
15
+ constructor(sdk) {
16
+ super(sdk, 'settings');
17
+ }
18
+
19
+ }
@@ -0,0 +1,22 @@
1
+ import { StorecraftSDK } from '../index.js'
2
+ import { collection_base } from './utils.api.fetch.js';
3
+
4
+ /**
5
+ * Base `shipping` **CRUD**
6
+ *
7
+ * @extends {collection_base<
8
+ * import('@storecraft/core/v-api').ShippingMethodTypeUpsert,
9
+ * import('@storecraft/core/v-api').ShippingMethodType>
10
+ * }
11
+ */
12
+ export default class Shipping extends collection_base {
13
+
14
+ /**
15
+ *
16
+ * @param {StorecraftSDK} sdk
17
+ */
18
+ constructor(sdk) {
19
+ super(sdk, 'shipping');
20
+ }
21
+
22
+ }
@@ -0,0 +1,99 @@
1
+ import { App } from '@storecraft/core';
2
+ import { StorecraftSDK } from '../index.js'
3
+ import { fetchApiWithAuth } from './utils.api.fetch.js';
4
+ import { api_query_to_searchparams } from '@storecraft/core/v-api/utils.query.js';
5
+
6
+ export default class Statistics {
7
+ /** @type {StorecraftSDK} */
8
+ #sdk;
9
+ /** @type {Record<string, any>} */
10
+ #cache = {};
11
+
12
+ /**
13
+ *
14
+ * @param {StorecraftSDK} sdk
15
+ */
16
+ constructor(sdk) {
17
+ this.#sdk = sdk
18
+ }
19
+
20
+ get sdk() {
21
+ return this.#sdk;
22
+ }
23
+
24
+ /**
25
+ * @param {string} key
26
+ *
27
+ * @returns {boolean}
28
+ */
29
+ isCacheValid = key => {
30
+ return false;
31
+ // return this.cache[key] &&
32
+ // (Date.now()-this.cache[key].updatedAt)<HOUR
33
+ }
34
+
35
+ /**
36
+ *
37
+ * @param {string} key
38
+ * @returns {import('@storecraft/core/v-api').OrdersStatisticsType}
39
+ */
40
+ fromCache = (key) => {
41
+ if(this.isCacheValid(key))
42
+ return this.#cache[key]
43
+ return undefined
44
+ }
45
+
46
+ /**
47
+ *
48
+ * @param {string} key
49
+ * @param {import('@storecraft/core/v-api').OrdersStatisticsType} value
50
+ */
51
+ putCache = (key, value) => {
52
+ this.#cache[key] = value
53
+ }
54
+
55
+
56
+ /**
57
+ * Load **Orders** `statistics`
58
+ *
59
+ * @param {string | number | Date} [from_day] `ISO` string | `UTC` | `timestamp` | `Date`
60
+ * @param {string | number | Date} [to_day] `ISO` string | `UTC` | `timestamp` | `Date`
61
+ *
62
+ * @returns {Promise<import('@storecraft/core/v-api').OrdersStatisticsType>}
63
+ */
64
+ orders = async (from_day, to_day) => {
65
+ const search = new URLSearchParams();
66
+
67
+ if(from_day)
68
+ search.set('fromDay', new Date(from_day).toISOString());
69
+ if(to_day)
70
+ search.set('toDay', new Date(to_day).toISOString());
71
+
72
+ return fetchApiWithAuth(
73
+ this.sdk,
74
+ `statistics/orders?${search.toString()}`
75
+ );
76
+ }
77
+
78
+ /**
79
+ * Load **count** `statistics`
80
+ *
81
+ * @param {keyof App["db"]["resources"]} table
82
+ * @param {import('@storecraft/core/v-api').ApiQuery} [query]
83
+ *
84
+ *
85
+ * @returns {Promise<number>}
86
+ *
87
+ *
88
+ * @throws
89
+ */
90
+ countOf = async (table, query) => {
91
+ const search = api_query_to_searchparams(query);
92
+
93
+ return fetchApiWithAuth(
94
+ this.sdk,
95
+ `statistics/count/${table}?${search.toString()}`
96
+ );
97
+ }
98
+
99
+ }
package/src/storage.js ADDED
@@ -0,0 +1,325 @@
1
+
2
+ import { StorecraftSDK } from '../index.js'
3
+ import { fetchOnlyApiResponseWithAuth } from './utils.api.fetch.js'
4
+
5
+ /**
6
+ *
7
+ * `Storecraft` storage service.
8
+ *
9
+ * Supports:
10
+ * - direct `downloads` / `uploads`
11
+ * - presigned-urls for `download` / `upload` (If supported)
12
+ * - `delete` files
13
+ *
14
+ */
15
+ export default class Storage {
16
+
17
+ /**
18
+ * @type {{
19
+ * features: import('@storecraft/core/v-storage').StorageFeatures
20
+ * }}
21
+ */
22
+ #cache = {
23
+ features: undefined
24
+ }
25
+
26
+ /**
27
+ * @param {StorecraftSDK} sdk of
28
+ */
29
+ constructor(sdk) {
30
+ this.sdk = sdk
31
+ }
32
+
33
+ /**
34
+ *
35
+ * Retrieve the `features` of `storage`, which informs:
36
+ * - Does `storage` supports `pre-signed` urls for `download` / `upload`
37
+ *
38
+ *
39
+ * @returns {Promise<import('@storecraft/core/v-storage').StorageFeatures>}
40
+ */
41
+ features = async () => {
42
+ if(this.#cache.features)
43
+ return this.#cache.features;
44
+
45
+ try {
46
+ const r = await fetchOnlyApiResponseWithAuth(
47
+ this.sdk,
48
+ `storage`,
49
+ {
50
+ method: 'get'
51
+ }
52
+ );
53
+
54
+ if(!r.ok)
55
+ throw new Error();
56
+
57
+ const json = await r.json();
58
+
59
+ this.#cache.features = json;;
60
+
61
+ return json;
62
+
63
+ } catch (e) {
64
+ console.log(e)
65
+ }
66
+
67
+ return {
68
+ supports_signed_urls: false
69
+ }
70
+
71
+ }
72
+
73
+ /**
74
+ * Get a blob from `storage` driver with `presigned` urls
75
+ *
76
+ * @param {string} key file path key,
77
+ * examples `image.png`, `collections/thumb.jpeg`
78
+ *
79
+ * @return {Promise<Blob>}
80
+ *
81
+ * @throws {import('@storecraft/core/v-api').error}
82
+ */
83
+ getBlobSigned = async (key) => {
84
+
85
+ const r = await fetchOnlyApiResponseWithAuth(
86
+ this.sdk,
87
+ `storage/${key}?signed=true`,
88
+ { method: 'get' }
89
+ );
90
+
91
+ const ctype = r.headers.get('Content-Type');
92
+
93
+ if(!r.ok) {
94
+ const error = await r.json();
95
+
96
+ throw error;
97
+ }
98
+
99
+ // `presigned` url instructions
100
+ if(ctype === 'application/json') {
101
+ /** @type {import('@storecraft/core/v-storage').StorageSignedOperation} */
102
+ const presigned_req = await r.json();
103
+
104
+ const presigned_res = await fetch(
105
+ presigned_req.url,
106
+ {
107
+ method: presigned_req.method,
108
+ headers: presigned_req.headers
109
+ }
110
+ );
111
+
112
+ return presigned_res.blob();
113
+ }
114
+
115
+ throw 'unknown'
116
+ }
117
+
118
+ /**
119
+ * Get a blob from `storage` driver, straight download. (Not recommended)
120
+ *
121
+ * @param {string} key file path key,
122
+ * examples `image.png`, `collections/thumb.jpeg`
123
+ *
124
+ * @return {Promise<Blob>}
125
+ *
126
+ * @throws {import('@storecraft/core/v-api').error}
127
+ */
128
+ getBlobUnsigned = async (key) => {
129
+
130
+ const r = await fetchOnlyApiResponseWithAuth(
131
+ this.sdk,
132
+ `storage/${key}?signed=false`,
133
+ { method: 'get' }
134
+ );
135
+
136
+ const ctype = r.headers.get('Content-Type');
137
+
138
+ if(!r.ok) {
139
+ const error = await r.json();
140
+
141
+ throw error;
142
+ }
143
+
144
+ return r.blob();
145
+ }
146
+
147
+ /**
148
+ * Get a blob from `storage` driver.
149
+ *
150
+ * @param {string} key file path key,
151
+ * examples `image.png`, `collections/thumb.jpeg`
152
+ *
153
+ * @return {Promise<Blob>}
154
+ *
155
+ * @throws {import('@storecraft/core/v-api').error}
156
+ */
157
+ getBlob = async (key) => {
158
+
159
+ const features = await this.features();
160
+
161
+ if(features.supports_signed_urls)
162
+ return this.getBlobSigned(key);
163
+
164
+ return this.getBlobUnsigned(key);
165
+ }
166
+
167
+ /** @param {string} path */
168
+ getText = (path) =>
169
+ this.getBlob(path).then(blob => blob.text());
170
+
171
+ /** @param {string} path */
172
+ getJson = (path) =>
173
+ this.getBlob(path).then(blob => blob.text().then(JSON.parse));
174
+
175
+ /** @param {string} path */
176
+ getImageObjectURL = (path) =>
177
+ this.getBlob(path).then(blob => URL.createObjectURL(blob));
178
+
179
+ /**
180
+ * get file source by inspecting the url:
181
+ *
182
+ * - If it starts with `storage://`, then use `backend`
183
+ * storage service, to download and convert it to encoded
184
+ * `object-url` for `<img/>`
185
+ *
186
+ * - Else. it is assumed to be a public `url`, and will
187
+ * return the given url.
188
+ *
189
+ * @param {string} url
190
+ * @param {boolean} isImage
191
+ */
192
+ getSource = async (url, isImage=true) => {
193
+ try {
194
+
195
+ const is_storage = url.startsWith('storage://');
196
+
197
+ // if we havent found a driver, rturn the url
198
+ if(!is_storage)
199
+ return url;
200
+
201
+ const key = url.split('storage://').at(-1);
202
+ const blob = await this.getBlob(key);
203
+
204
+ if(isImage)
205
+ return URL.createObjectURL(blob)
206
+ else
207
+ return blob.text().then(JSON.parse)
208
+ } catch(e) {
209
+ console.log(e)
210
+ }
211
+
212
+ return url;
213
+ }
214
+
215
+
216
+ /**
217
+ * Put a blob into `storage` driver with `presigned` urls
218
+ *
219
+ * @param {string} key file path key,
220
+ * examples `image.png`, `collections/thumb.jpeg`
221
+ * @param {string | Blob | Uint8Array | ArrayBuffer | File} data
222
+ *
223
+ */
224
+ putBytesSigned = async (key, data) => {
225
+
226
+ const r = await fetchOnlyApiResponseWithAuth(
227
+ this.sdk,
228
+ `storage/${key}?signed=true`,
229
+ { method: 'put' }
230
+ );
231
+
232
+ const ctype = r.headers.get('Content-Type');
233
+
234
+ if(!r.ok) {
235
+ const error = await r.json();
236
+ throw error;
237
+ }
238
+
239
+ // `presigned` url instructions
240
+ if(ctype === 'application/json') {
241
+ /** @type {import('@storecraft/core/v-storage').StorageSignedOperation} */
242
+ const presigned_req = await r.json();
243
+ const presigned_res = await fetch(
244
+ presigned_req.url,
245
+ {
246
+ method: presigned_req.method,
247
+ headers: presigned_req.headers,
248
+ body: data
249
+ }
250
+ );
251
+
252
+ return presigned_res.ok;
253
+ }
254
+
255
+ throw 'unknown'
256
+ }
257
+
258
+ /**
259
+ * Put a blob into `storage` driver with direct `upload` (Not Recommended)
260
+ *
261
+ * @param {string} key file path key,
262
+ * examples `image.png`, `collections/thumb.jpeg`
263
+ * @param {string | Blob | Uint8Array | ArrayBuffer | File} data
264
+ *
265
+ */
266
+ putBytesUnsigned = async (key, data) => {
267
+
268
+ const r = await fetchOnlyApiResponseWithAuth(
269
+ this.sdk,
270
+ `storage/${key}?signed=false`,
271
+ {
272
+ method: 'put',
273
+ body: data
274
+ }
275
+ );
276
+
277
+ const ctype = r.headers.get('Content-Type');
278
+
279
+ if(!r.ok) {
280
+ const error = await r.json();
281
+
282
+ throw error;
283
+ }
284
+
285
+ return r.ok;
286
+ }
287
+
288
+ /**
289
+ * Put bytes into `storage` driver.
290
+ *
291
+ * @param {string} key file path key,
292
+ * examples `image.png`, `collections/thumb.jpeg`
293
+ * @param {string | Blob | Uint8Array | ArrayBuffer | File} data
294
+ *
295
+ * @return {Promise<boolean>}
296
+ *
297
+ * @throws {import('@storecraft/core/v-api').error}
298
+ */
299
+ putBytes = async (key, data) => {
300
+
301
+ const features = await this.features();
302
+
303
+ if(features.supports_signed_urls)
304
+ return this.putBytesSigned(key, data);
305
+
306
+ return this.putBytesUnsigned(key, data);
307
+ }
308
+
309
+ /**
310
+ * Delete a `file` by key
311
+ *
312
+ * @param {string} key file path key,
313
+ * examples `image.png`, `collections/thumb.jpeg`
314
+ */
315
+ delete = async (key) => {
316
+ const r = await fetchOnlyApiResponseWithAuth(
317
+ this.sdk,
318
+ `storage/${key}`,
319
+ { method: 'delete' }
320
+ );
321
+
322
+ return r.ok;
323
+ }
324
+
325
+ }
@@ -0,0 +1,113 @@
1
+ import { StorecraftSDK } from '../index.js'
2
+ import { collection_base } from './utils.api.fetch.js';
3
+
4
+ /**
5
+ * Base `storefronts` **CRUD**
6
+ *
7
+ * @extends {collection_base<
8
+ * import('@storecraft/core/v-api').StorefrontTypeUpsert,
9
+ * import('@storecraft/core/v-api').StorefrontType>
10
+ * }
11
+ */
12
+ export default class Storefronts extends collection_base {
13
+
14
+ /**
15
+ *
16
+ * @param {StorecraftSDK} sdk
17
+ */
18
+ constructor(sdk) {
19
+ super(sdk, 'storefronts');
20
+ }
21
+
22
+ /**
23
+ *
24
+ * @param {StorefrontData} sf_data
25
+ */
26
+ publish = async (sf_data) => {
27
+ sf_data = {...sf_data}
28
+ try {
29
+ // Gather products
30
+ let products = await Promise.all(
31
+ sf_data.products.map(
32
+ async id => await this.context.products.get(id)
33
+ )
34
+ )
35
+ sf_data.products = delete_keys('search', 'createdAt')(
36
+ products.map(p => p[2])
37
+ )
38
+ } catch(e) {
39
+ throw 'products export error: ' + String(e)
40
+ }
41
+
42
+ /**
43
+ * @param {Handle[]} sf_data_entry
44
+ * @param {string} collectionId
45
+ * @param {(any) => any} filter_fn
46
+ */
47
+ const collect = async (sf_data_entry, collectionId, filter_fn=_=>true) => {
48
+ try {
49
+ // Gather collections
50
+ const has_all = sf_data_entry?.some(c => c==='ALL')
51
+ let items = []
52
+ if(has_all) {
53
+ items = await this.context[collectionId].list()
54
+ items = items.map(c => c[1]).filter(filter_fn)
55
+ }
56
+ else {
57
+ items = sf_data_entry?.map(
58
+ async id => {
59
+ const [_, __, coll] = await this.context[collectionId].get(id)
60
+ return coll
61
+ }
62
+ )
63
+ items = await Promise.all(items ?? [])
64
+ }
65
+ items = delete_keys('search')(items)
66
+ return items
67
+ } catch(e) {
68
+ throw `${collectionId} export error: ` + String(e)
69
+ }
70
+ }
71
+
72
+ sf_data.collections = await collect(
73
+ sf_data.collections, 'collections',
74
+ c => c?.active || c?.active===undefined
75
+ )
76
+ sf_data.shipping_methods = await collect(
77
+ sf_data.shipping_methods, 'shipping_methods',
78
+ c => c?.active || c?.active===undefined
79
+ )
80
+ sf_data.posts = await collect(
81
+ sf_data.posts, 'posts'
82
+ )
83
+ sf_data.discounts = await collect(
84
+ sf_data.discounts, 'discounts',
85
+ dis => dis.application.id===DiscountApplicationEnum.Auto.id && dis.enabled
86
+ )
87
+
88
+ try {
89
+ // Upload to bucket
90
+ const [url, ref] = await this.context.storage.uploadBytes(
91
+ `storefronts/${sf_data.handle}.json`,
92
+ pako.gzip(JSON.stringify(sf_data)),
93
+ {
94
+ contentType: 'application/json',
95
+ contentEncoding: 'gzip',
96
+ // cacheControl: `no-cache`
97
+ cacheControl: `public, max-age=${60*60*1}, must-revalidate`
98
+ }
99
+ )
100
+ await this.update(
101
+ sf_data.handle,
102
+ { _published: url }
103
+ )
104
+
105
+ // console.log(url, ref);
106
+ } catch(e) {
107
+ throw 'Upload export error: ' + String(e)
108
+ }
109
+
110
+ console.log('sf_data ', sf_data);
111
+ }
112
+
113
+ }
package/src/tags.js ADDED
@@ -0,0 +1,21 @@
1
+ import { StorecraftSDK } from '../index.js'
2
+ import { collection_base } from './utils.api.fetch.js';
3
+
4
+ /**
5
+ * Base `tags` **CRUD**
6
+ *
7
+ * @extends {collection_base<
8
+ * import('@storecraft/core/v-api').TagTypeUpsert,
9
+ * import('@storecraft/core/v-api').TagType>
10
+ * }
11
+ */
12
+ export default class Tags extends collection_base {
13
+
14
+ /**
15
+ *
16
+ * @param {StorecraftSDK} sdk
17
+ */
18
+ constructor(sdk) {
19
+ super(sdk, 'tags');
20
+ }
21
+ }
@@ -0,0 +1,21 @@
1
+ import { StorecraftSDK } from '../index.js'
2
+ import { collection_base } from './utils.api.fetch.js';
3
+
4
+ /**
5
+ * Base `templates` **CRUD**
6
+ *
7
+ * @extends {collection_base<
8
+ * import('@storecraft/core/v-api').TemplateTypeUpsert,
9
+ * import('@storecraft/core/v-api').TemplateType>
10
+ * }
11
+ */
12
+ export default class Templates extends collection_base {
13
+
14
+ /**
15
+ *
16
+ * @param {StorecraftSDK} sdk
17
+ */
18
+ constructor(sdk) {
19
+ super(sdk, 'templates');
20
+ }
21
+ }