@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.
package/README.md ADDED
@@ -0,0 +1,132 @@
1
+ # **Storecraft** Official Universal `Javascript` **SDK**
2
+
3
+ This is the official `storecraft` universal javascript `SDK` which is `fetch` based,
4
+ which means you can you it both at browser and at backend runtimes such (`node` / `bun` / `deno`)
5
+
6
+ It will allow you to fetch / mutate all of the resources at the `backend` in a
7
+ convenient manner with `javascript`, such as:
8
+ `products`, `collections`, `authentication`, `customers`, `orders`, `discounts`,
9
+ `storage`, `storefronts`, `shipping`, `statistics`, `tags`, `posts`, `notifications`,
10
+ `templates`, `extensions` and more :)
11
+
12
+
13
+ ## Authentication
14
+
15
+ Authentication and authorization is required for some resources, such as:
16
+ - Upserting / Removing (mutations) resources
17
+ - Querying a customer orders
18
+
19
+ Besides that, most of the resources are public and do not require authentication.
20
+
21
+
22
+ There are two strategies for `authentication`:
23
+
24
+
25
+ ### **Api Key** Authentication
26
+
27
+ ```js
28
+ import { StorecraftSDK } from '@storecraft/sdk'
29
+
30
+ const sdk = new StorecraftSDK(
31
+ {
32
+ backend: 'http://localhost:8000',
33
+ auth: {
34
+ apikey: <YOUR-API-KEY>
35
+ }
36
+ }
37
+ )
38
+
39
+ ```
40
+
41
+ ### **JWT** Authentication
42
+
43
+ You can `sign in`, and the `sdk` will save the `auth` result ans
44
+ will re-authenticate with `refresh token` when needed.
45
+
46
+
47
+ ```js
48
+ import { StorecraftSDK } from '@storecraft/sdk'
49
+
50
+ const sdk = new StorecraftSDK(
51
+ {
52
+ backend: 'http://localhost:8000',
53
+ }
54
+ );
55
+
56
+ const auth_result = await sdk.auth.signin(email, password);
57
+
58
+ ```
59
+
60
+ You, can also instantiate the `sdk` with previous authentication
61
+ information like so:
62
+
63
+ ```js
64
+ import { StorecraftSDK } from '@storecraft/sdk'
65
+
66
+ const sdk = new StorecraftSDK(
67
+ {
68
+ backend: 'http://localhost:8000',
69
+ auth: {
70
+ access_token: <OPTIONAL-ACCESS-TOKEN>,
71
+ refresh_token: <OPTIONAL-REFRESH-TOKEN>,
72
+ }
73
+ }
74
+ )
75
+
76
+ ```
77
+
78
+ ### subscribe to **JWT** auth updates
79
+
80
+ You can subscribe to auth updates like so:
81
+
82
+ ```js
83
+ import { StorecraftSDK } from '@storecraft/sdk'
84
+
85
+ const sdk = new StorecraftSDK(
86
+ {
87
+ backend: 'http://localhost:8000',
88
+ }
89
+ );
90
+
91
+ sdk.auth.add_sub(
92
+ ({ auth: ApiAuthResult, isAuthenticated: boolean }) => {
93
+ // Do something, like save locally
94
+ }
95
+ );
96
+
97
+ const auth_result = await sdk.auth.signin(email, password);
98
+ const auth_result = await sdk.auth.signout();
99
+
100
+ ```
101
+
102
+
103
+ ## Querying
104
+
105
+
106
+ Here are some examples for querying
107
+
108
+
109
+ ```js
110
+ import { StorecraftSDK } from '@storecraft/sdk'
111
+
112
+ const sdk = new StorecraftSDK();
113
+
114
+ const products: ProductType[] = await sdk.products.list(
115
+ {
116
+ expand: ['collections', 'variants'],
117
+ sortBy: ['updated_at', 'id'],
118
+ order: 'desc',
119
+ startAt: [
120
+ ['updated_at': '2024-03-24']
121
+ ],
122
+ limit: 5,
123
+ vql: '(keyword1 | keyword2) -(keyword3)'
124
+ }
125
+ )
126
+
127
+ ```
128
+
129
+
130
+ ```text
131
+ Author: Tomer Shalev (tomer.shalev@gmail.com)
132
+ ```
package/index.js ADDED
@@ -0,0 +1,103 @@
1
+ import Auth from './src/auth.js'
2
+ import Customers from './src/customers.js'
3
+ import Tags from './src/tags.js'
4
+ import Templates from './src/templates.js'
5
+ import Products from './src/products.js'
6
+ import Orders from './src/orders.js'
7
+ import Discounts from './src/discounts.js'
8
+ import Collections from './src/collections.js'
9
+ import ShippingMethods from './src/shipping.js'
10
+ import StoreFronts from './src/storefronts.js'
11
+ import Statistics from './src/statistics.js'
12
+ import Images from './src/images.js'
13
+ import Posts from './src/posts.js'
14
+ import Payments from './src/payments.js'
15
+ import Settings from './src/settings.js'
16
+ import Notifications from './src/notifications.js'
17
+ import Bots from './src/bots.js'
18
+ import Storage from './src/storage.js'
19
+
20
+
21
+ /**
22
+ * @typedef {import('@storecraft/core/v-api').ApiAuthResult |
23
+ * import('@storecraft/core/v-api').ApiKeyResult
24
+ * } SdkConfigAuth The `storecraft` **SDK** `auth` config, represents
25
+ * either `apikey` or `jwt` authentication
26
+ *
27
+ */
28
+
29
+
30
+ /**
31
+ *
32
+ * @typedef {object} StorecraftSDKConfig The `storecraft` **SDK** config
33
+ * @property {string} [endpoint] Endpoint of `backend`
34
+ * @property {SdkConfigAuth} [auth] `auth` info, may be either `apikey` or
35
+ * `jwt` results
36
+ */
37
+
38
+ /**
39
+ * The official `storecraft` admin **SDK** for `javascript`
40
+ */
41
+ export class StorecraftSDK {
42
+
43
+ /**@type {StorecraftSDKConfig} */
44
+ #_config = undefined;
45
+
46
+ /**
47
+ * @param {StorecraftSDKConfig} [config]
48
+ */
49
+ constructor(config) {
50
+ this.#_config = config;
51
+
52
+ this.auth = new Auth(this);
53
+ this.storage = new Storage(this);
54
+
55
+ this.customers = new Customers(this);
56
+ this.tags = new Tags(this);
57
+ this.templates = new Templates(this);
58
+ this.products = new Products(this);
59
+ this.orders = new Orders(this);
60
+ this.collections = new Collections(this);
61
+ this.discounts = new Discounts(this);
62
+ this.shipping = new ShippingMethods(this);
63
+ this.storefronts = new StoreFronts(this);
64
+ this.statistics = new Statistics(this);
65
+ this.images = new Images(this);
66
+ this.posts = new Posts(this);
67
+ this.payments = new Payments(this);
68
+ this.settings = new Settings(this);
69
+ this.notifications = new Notifications(this);
70
+ this.bots = new Bots(this);
71
+ }
72
+
73
+ /**
74
+ * @param {StorecraftSDKConfig} [config]
75
+ */
76
+ updateConfig(config) {
77
+ this.#_config = config;
78
+ }
79
+
80
+ get config() {
81
+ return this.#_config
82
+ }
83
+
84
+ }
85
+
86
+ /**
87
+ * @param {StorecraftSDKConfig} config
88
+ */
89
+ export const validateConfig = (config) => {
90
+ return true;
91
+ }
92
+
93
+ /**
94
+ * @param {StorecraftSDKConfig} [config]
95
+ */
96
+ export const create = (config) => {
97
+ const sdk = new StorecraftSDK(config);
98
+
99
+ return sdk;
100
+ }
101
+
102
+
103
+
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@storecraft/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Official storecraft Universal Javascript SDK",
5
+ "license": "MIT",
6
+ "author": "Tomer Shalev (https://github.com/store-craft)",
7
+ "homepage": "https://github.com/store-craft/storecraft",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/store-craft/storecraft.git",
11
+ "directory": "packages/sdk"
12
+ },
13
+ "keywords": [
14
+ "commerce",
15
+ "dashboard",
16
+ "code",
17
+ "storecraft"
18
+ ],
19
+ "scripts": {
20
+ "sdk:test": "echo \"Error: no test specified\" && exit 1",
21
+ "sdk:publish": "npm publish --access public"
22
+ },
23
+ "type": "module",
24
+ "types": "./types.d.ts",
25
+ "main": "./index.js",
26
+ "dependencies": {
27
+ "@storecraft/core": "^1.0.0"
28
+ }
29
+ }
package/src/auth.js ADDED
@@ -0,0 +1,382 @@
1
+ import { api_query_to_searchparams } from '@storecraft/core/v-api/utils.query.js';
2
+ import { StorecraftSDK } from '../index.js';
3
+ import { fetchApiWithAuth, url } from './utils.api.fetch.js';
4
+ import { assert } from './utils.functional.js';
5
+
6
+
7
+ /**
8
+ *
9
+ * @typedef {import('../index.js').SdkConfigAuth} SdkConfigAuth
10
+ *
11
+ *
12
+ * @typedef {object} SubscriberCallbackPayload
13
+ * @prop {SdkConfigAuth} auth
14
+ * @prop {boolean} isAuthenticated
15
+ *
16
+ *
17
+ * @typedef {(payload: SubscriberCallbackPayload) => void
18
+ * } SubscriberCallback Subscribe to `auth` updates for `JWT` auth
19
+ *
20
+ */
21
+
22
+
23
+ /**
24
+ * `Storecraft` authentication module:
25
+ * - Supports `subscribtion` to `auth` events
26
+ *
27
+ */
28
+ export default class Auth {
29
+
30
+ /**@type {Set<SubscriberCallback>} */
31
+ #subscribers = new Set();
32
+
33
+ /** @type {StorecraftSDK} */
34
+ #sdk;
35
+
36
+ /**
37
+ *
38
+ * @param {StorecraftSDK} sdk
39
+ */
40
+ constructor(sdk) {
41
+ this.#sdk = sdk
42
+ }
43
+
44
+ /**
45
+ *
46
+ * Get the current `auth` config, which is one of:
47
+ *
48
+ * 1. `JWT` with `access_token`, `refresh_token`, `claims`
49
+ * 2. `Api Key` with a single value, which can be used as is in:
50
+ * - `Authorization: Basic <api-key>`, or
51
+ * - `X-API-KEY: <api-key>`
52
+ *
53
+ * Notes:
54
+ *
55
+ * **JWT** is always recommended and is performing faster once you gain an
56
+ * `access_token`.
57
+ *
58
+ * **Api Key** represents a user which always makes the `backend` to verify
59
+ * the authentication and authorization and therefore may be slower, because
60
+ * the `backend` will verify against the database.
61
+ *
62
+ */
63
+ get currentAuth() {
64
+ return this.#sdk?.config?.auth;
65
+ }
66
+
67
+ /**
68
+ * @param {import('../index.js').SdkConfigAuth} value
69
+ */
70
+ set currentAuth(value) {
71
+ this.#sdk.config.auth = value;
72
+ }
73
+
74
+
75
+ init() {
76
+ }
77
+
78
+ /**
79
+ *
80
+ * Get a working token, by the following strategy:
81
+ *
82
+ * - If you are in `JWT` strategy:
83
+ * - If the current `access_token` will expire soon or is already expired
84
+ * - then, use `refresh_token` to re-authenticate
85
+ *
86
+ * - If you are in `Api Key` strategy, simply returns the `apikey`
87
+ *
88
+ * @param {boolean} [force_reauth=false]
89
+ *
90
+ */
91
+ async working_auth_token(force_reauth=false) {
92
+ if(this.currentAuth) {
93
+ if('apikey' in this.currentAuth) {
94
+ return this.currentAuth.apikey;
95
+ }
96
+ else if('access_token' in this.currentAuth) {
97
+ if(force_reauth || !this.isAuthenticated)
98
+ await this.reAuthenticateIfNeeded(force_reauth);
99
+ return this.currentAuth.access_token.token;
100
+ }
101
+ }
102
+
103
+ return undefined;
104
+ }
105
+
106
+
107
+ /**
108
+ *
109
+ * Perform re-authentication for `JWT` auth, which means:
110
+ *
111
+ * - use the `refresh_token` to gain a new `access_token`
112
+ *
113
+ * @param {boolean} [force=false]
114
+ *
115
+ */
116
+ async reAuthenticateIfNeeded(force=false) {
117
+ if(!this.currentAuth)
118
+ return;
119
+
120
+ if(this.isAuthenticated && !force)
121
+ return;
122
+
123
+ if('access_token' in this.currentAuth) {
124
+ const auth_res = await fetch(
125
+ url(this.#sdk.config, '/auth/refresh'),
126
+ {
127
+ method: 'post',
128
+ body: JSON.stringify(
129
+ {
130
+ refresh_token: this.currentAuth.refresh_token.token
131
+ }
132
+ ),
133
+ headers: {
134
+ 'Content-Type': 'application/json'
135
+ }
136
+ }
137
+ );
138
+
139
+ /** @type {import('@storecraft/core/v-api').ApiAuthResult} */
140
+ let payload = undefined;
141
+
142
+ if(auth_res.ok) {
143
+ payload = await auth_res.json();
144
+ }
145
+
146
+ this.#_update_and_notify_subscribers(payload);
147
+
148
+ return payload;
149
+ }
150
+ }
151
+
152
+ notify_subscribers = () => {
153
+ // console.log('this.isAuthenticated', this.isAuthenticated)
154
+ for(let s of this.#subscribers) {
155
+ s(
156
+ {
157
+ auth: this.currentAuth,
158
+ isAuthenticated: this.isAuthenticated
159
+ }
160
+ );
161
+
162
+ }
163
+ }
164
+
165
+ /**
166
+ * @param {SubscriberCallback} cb
167
+ * @returns {() => void} unsubscribe function
168
+ */
169
+ add_sub = cb => {
170
+ this.#subscribers.add(cb);
171
+
172
+ return () => {
173
+ this.#subscribers.delete(cb)
174
+ }
175
+ }
176
+
177
+ get authStrategy() {
178
+ if(this.currentAuth) {
179
+ if('apikey' in this.currentAuth)
180
+ return 'apikey';
181
+ else if('access_token' in this.currentAuth)
182
+ return 'jwt';
183
+ }
184
+
185
+ return 'unknown';
186
+ }
187
+
188
+ get isAuthenticated() {
189
+
190
+ if(this.currentAuth) {
191
+ if('apikey' in this.currentAuth) {
192
+ return Boolean(this.currentAuth.apikey);
193
+ }
194
+ else if('access_token' in this.currentAuth) {
195
+ const exp = this.currentAuth?.access_token?.claims?.exp;
196
+
197
+ return exp && (Date.now() < (exp - 60)*1000);
198
+ }
199
+ }
200
+
201
+ return false;
202
+ }
203
+
204
+ /**
205
+ *
206
+ * @param {import('@storecraft/core/v-api').ApiAuthResult} user
207
+ */
208
+ #_update_and_notify_subscribers = (user) => {
209
+ this.currentAuth = user
210
+ this.notify_subscribers();
211
+ }
212
+
213
+
214
+ /**
215
+ *
216
+ * @param {string} email
217
+ * @param {string} password
218
+ *
219
+ *
220
+ * @returns {Promise<import('@storecraft/core/v-api').ApiAuthResult>}
221
+ */
222
+ signin = async (email, password) => {
223
+ // console.log('ep ', email, password)
224
+
225
+ /** @type {import('@storecraft/core/v-api').ApiAuthSigninType} */
226
+ const info = {
227
+ email, password
228
+ }
229
+
230
+ const res = await fetch(
231
+ url(this.#sdk.config, `/auth/signin`),
232
+ {
233
+ method: 'post',
234
+ body: JSON.stringify(info),
235
+ headers: {
236
+ 'Content-Type' : 'application/json'
237
+ }
238
+ }
239
+ );
240
+
241
+ assert(res.ok, 'auth/error');
242
+
243
+ /** @type {import('@storecraft/core/v-api').ApiAuthResult} */
244
+ const payload = await res.json();
245
+
246
+ // console.log('auth_result', payload)
247
+
248
+ this.#_update_and_notify_subscribers(
249
+ payload
250
+ );
251
+
252
+ return payload;
253
+ }
254
+
255
+
256
+ /**
257
+ *
258
+ * @param {string} email
259
+ * @param {string} password
260
+ */
261
+ signup = async (email, password) => {
262
+ /** @type {import('@storecraft/core/v-api').ApiAuthSignupType} */
263
+ const info = {
264
+ email, password
265
+ }
266
+
267
+ const res = await fetch(
268
+ url(this.#sdk.config, `/auth/signup`),
269
+ {
270
+ method: 'post',
271
+ body: JSON.stringify(info),
272
+ headers: {
273
+ 'Content-Type' : 'application/json'
274
+ }
275
+ }
276
+ );
277
+
278
+ assert(res.ok, 'auth/error');
279
+
280
+ /** @type {import('@storecraft/core/v-api').ApiAuthResult} */
281
+ const payload = await res.json();
282
+
283
+ this.#_update_and_notify_subscribers(
284
+ payload
285
+ );
286
+
287
+ return payload;
288
+ }
289
+
290
+
291
+ signout = async () => {
292
+ console.log('signout');
293
+
294
+ this.#_update_and_notify_subscribers(
295
+ undefined
296
+ );
297
+
298
+ }
299
+
300
+
301
+ create_api_key = async () => {
302
+ /** @type {import('@storecraft/core/v-api').ApiKeyResult} */
303
+ const item = await fetchApiWithAuth(
304
+ this.#sdk,
305
+ '/auth/apikeys',
306
+ {
307
+ method: 'post'
308
+ }
309
+ );
310
+
311
+ return item.apikey;
312
+ }
313
+
314
+
315
+ /**
316
+ *
317
+ *
318
+ * @param {string} email_or_id
319
+ */
320
+ get_auth_user = async (email_or_id) => {
321
+ /** @type {import('@storecraft/core/v-api').AuthUserType} */
322
+ const item = await fetchApiWithAuth(
323
+ this.#sdk,
324
+ `/auth/users/${email_or_id}`,
325
+ {
326
+ method: 'get'
327
+ }
328
+ );
329
+
330
+ return item;
331
+ }
332
+
333
+ /**
334
+ *
335
+ *
336
+ * @param {string} email_or_id
337
+ *
338
+ */
339
+ remove_auth_user = async (email_or_id) => {
340
+ return fetchApiWithAuth(
341
+ this.#sdk,
342
+ `/auth/users/${email_or_id}`,
343
+ {
344
+ method: 'delete'
345
+ }
346
+ );
347
+ }
348
+
349
+ /**
350
+ *
351
+ *
352
+ * @param {import('@storecraft/core/v-api').ApiQuery} query
353
+ *
354
+ */
355
+ list_auth_users = async (query) => {
356
+ const sq = api_query_to_searchparams(query);
357
+
358
+ return fetchApiWithAuth(
359
+ this.#sdk,
360
+ `/auth/users?${sq.toString()}`,
361
+ {
362
+ method: 'get'
363
+ }
364
+ );
365
+ }
366
+
367
+
368
+ list_api_keys_auth_users = async () => {
369
+
370
+ /** @type {import('@storecraft/core/v-api').AuthUserType[]} */
371
+ const items = await fetchApiWithAuth(
372
+ this.#sdk,
373
+ '/auth/apikeys',
374
+ {
375
+ method: 'get'
376
+ }
377
+ );
378
+
379
+ return items;
380
+ }
381
+
382
+ }