zone4code-sdk 1.0.2 → 1.0.4

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/dist/index.js CHANGED
@@ -1,23 +1,3 @@
1
- // src/storage.ts
2
- var MemoryStorage = class {
3
- store = /* @__PURE__ */ new Map();
4
- getItem(key) {
5
- return this.store.get(key) ?? null;
6
- }
7
- setItem(key, value) {
8
- this.store.set(key, value);
9
- }
10
- removeItem(key) {
11
- this.store.delete(key);
12
- }
13
- };
14
- function getDefaultStorage() {
15
- if (typeof window !== "undefined" && window.localStorage) {
16
- return window.localStorage;
17
- }
18
- return new MemoryStorage();
19
- }
20
-
21
1
  // src/auth.ts
22
2
  function decodeJwtPayload(token) {
23
3
  try {
@@ -42,7 +22,9 @@ var AuthClient = class _AuthClient {
42
22
  tenantId;
43
23
  storage;
44
24
  storageKey;
25
+ refreshStorageKey;
45
26
  token = null;
27
+ refreshTokenValue = null;
46
28
  fetchFn;
47
29
  static TOKEN_KEY_PREFIX = "z4c_token_";
48
30
  constructor(options) {
@@ -50,6 +32,7 @@ var AuthClient = class _AuthClient {
50
32
  this.tenantId = options.tenantId;
51
33
  this.storage = options.storage;
52
34
  this.storageKey = options.storageKey || _AuthClient.TOKEN_KEY_PREFIX + this.tenantId;
35
+ this.refreshStorageKey = this.storageKey + "_refresh";
53
36
  this.fetchFn = options.fetchFn || (typeof fetch !== "undefined" ? fetch : globalThis.fetch);
54
37
  if (options.initialToken) {
55
38
  this.setToken(options.initialToken);
@@ -58,11 +41,18 @@ var AuthClient = class _AuthClient {
58
41
  if (stored) this.token = stored;
59
42
  }).catch(() => {
60
43
  });
44
+ Promise.resolve(this.storage.getItem(this.refreshStorageKey)).then((stored) => {
45
+ if (stored) this.refreshTokenValue = stored;
46
+ }).catch(() => {
47
+ });
61
48
  }
62
49
  }
63
50
  getToken() {
64
51
  return this.token;
65
52
  }
53
+ getRefreshToken() {
54
+ return this.refreshTokenValue;
55
+ }
66
56
  setToken(token) {
67
57
  this.token = token;
68
58
  if (token) {
@@ -71,6 +61,14 @@ var AuthClient = class _AuthClient {
71
61
  this.storage.removeItem(this.storageKey);
72
62
  }
73
63
  }
64
+ setRefreshToken(token) {
65
+ this.refreshTokenValue = token;
66
+ if (token) {
67
+ this.storage.setItem(this.refreshStorageKey, token);
68
+ } else {
69
+ this.storage.removeItem(this.refreshStorageKey);
70
+ }
71
+ }
74
72
  isAuthenticated() {
75
73
  return !!this.token;
76
74
  }
@@ -179,6 +177,9 @@ var AuthClient = class _AuthClient {
179
177
  if (token) {
180
178
  this.setToken(token);
181
179
  }
180
+ if (json.refresh_token) {
181
+ this.setRefreshToken(json.refresh_token);
182
+ }
182
183
  const user = this.getUser() || json.user;
183
184
  return {
184
185
  token: token || "",
@@ -187,6 +188,163 @@ var AuthClient = class _AuthClient {
187
188
  ...json
188
189
  };
189
190
  }
191
+ /**
192
+ * Authenticate via Google OAuth ID token
193
+ */
194
+ async loginWithGoogle(token) {
195
+ const url = `${this.gatewayUrl}/auth/${this.tenantId}/login/google`;
196
+ const res = await this.fetchFn(url, {
197
+ method: "POST",
198
+ headers: {
199
+ "Content-Type": "application/json",
200
+ "x-tenant-id": this.tenantId
201
+ },
202
+ body: JSON.stringify({ token })
203
+ });
204
+ if (!res.ok) {
205
+ const err = await res.json().catch(() => ({ message: res.statusText }));
206
+ throw new Error(err.message || err.error || `Google login failed with HTTP ${res.status}`);
207
+ }
208
+ const json = await res.json();
209
+ const jwt = json.token || json.access_token;
210
+ if (jwt) {
211
+ this.setToken(jwt);
212
+ }
213
+ if (json.refresh_token) {
214
+ this.setRefreshToken(json.refresh_token);
215
+ }
216
+ const user = this.getUser() || json.user;
217
+ return {
218
+ token: jwt || "",
219
+ refreshToken: json.refresh_token,
220
+ user,
221
+ ...json
222
+ };
223
+ }
224
+ /**
225
+ * Renew expired access token using stored or provided refresh token
226
+ */
227
+ async refreshToken(refreshToken) {
228
+ const tokenToUse = refreshToken || this.getRefreshToken();
229
+ if (!tokenToUse) {
230
+ throw new Error("No refresh token available");
231
+ }
232
+ const url = `${this.gatewayUrl}/auth/${this.tenantId}/refresh`;
233
+ const res = await this.fetchFn(url, {
234
+ method: "POST",
235
+ headers: {
236
+ "Content-Type": "application/json",
237
+ "x-tenant-id": this.tenantId
238
+ },
239
+ body: JSON.stringify({ refresh_token: tokenToUse })
240
+ });
241
+ if (!res.ok) {
242
+ const err = await res.json().catch(() => ({ message: res.statusText }));
243
+ throw new Error(err.message || err.error || `Token refresh failed with HTTP ${res.status}`);
244
+ }
245
+ const json = await res.json();
246
+ const token = json.token || json.access_token;
247
+ if (token) {
248
+ this.setToken(token);
249
+ }
250
+ if (json.refresh_token) {
251
+ this.setRefreshToken(json.refresh_token);
252
+ }
253
+ const user = this.getUser() || json.user;
254
+ return {
255
+ token: token || "",
256
+ refreshToken: json.refresh_token,
257
+ user,
258
+ ...json
259
+ };
260
+ }
261
+ /**
262
+ * Request password reset email
263
+ */
264
+ async forgotPassword(data) {
265
+ const payload = typeof data === "string" ? { email: data } : data;
266
+ const url = `${this.gatewayUrl}/auth/${this.tenantId}/auth/forgot-password`;
267
+ const res = await this.fetchFn(url, {
268
+ method: "POST",
269
+ headers: {
270
+ "Content-Type": "application/json",
271
+ "x-tenant-id": this.tenantId
272
+ },
273
+ body: JSON.stringify(payload)
274
+ });
275
+ const json = await res.json().catch(() => ({}));
276
+ if (!res.ok) {
277
+ throw new Error(json.message || json.error || `Forgot password request failed with HTTP ${res.status}`);
278
+ }
279
+ return json;
280
+ }
281
+ /**
282
+ * Complete password reset using action token
283
+ */
284
+ async resetPassword(data) {
285
+ const url = `${this.gatewayUrl}/auth/${this.tenantId}/auth/reset-password`;
286
+ const res = await this.fetchFn(url, {
287
+ method: "POST",
288
+ headers: {
289
+ "Content-Type": "application/json",
290
+ "x-tenant-id": this.tenantId
291
+ },
292
+ body: JSON.stringify(data)
293
+ });
294
+ const json = await res.json().catch(() => ({}));
295
+ if (!res.ok) {
296
+ throw new Error(json.message || json.error || `Reset password failed with HTTP ${res.status}`);
297
+ }
298
+ return json;
299
+ }
300
+ /**
301
+ * Change password for current or specified user
302
+ */
303
+ async changePassword(data, userId) {
304
+ const targetUserId = userId || this.getUser()?.id;
305
+ if (!targetUserId) {
306
+ throw new Error("User ID required: user is not logged in and no userId was provided");
307
+ }
308
+ const url = `${this.gatewayUrl}/auth/${this.tenantId}/user/${targetUserId}/change-password`;
309
+ const res = await this.fetchFn(url, {
310
+ method: "POST",
311
+ headers: {
312
+ "Content-Type": "application/json",
313
+ "x-tenant-id": this.tenantId,
314
+ ...this.token ? { "Authorization": `Bearer ${this.token}` } : {}
315
+ },
316
+ body: JSON.stringify(data)
317
+ });
318
+ if (!res.ok) {
319
+ const err = await res.json().catch(() => ({ message: res.statusText }));
320
+ throw new Error(err.message || err.error || `Change password failed with HTTP ${res.status}`);
321
+ }
322
+ return res.json().catch(() => ({ success: true }));
323
+ }
324
+ /**
325
+ * Update profile attributes for current or specified user
326
+ */
327
+ async updateProfile(data, userId) {
328
+ const targetUserId = userId || this.getUser()?.id;
329
+ if (!targetUserId) {
330
+ throw new Error("User ID required: user is not logged in and no userId was provided");
331
+ }
332
+ const url = `${this.gatewayUrl}/auth/${this.tenantId}/user/${targetUserId}`;
333
+ const res = await this.fetchFn(url, {
334
+ method: "PUT",
335
+ headers: {
336
+ "Content-Type": "application/json",
337
+ "x-tenant-id": this.tenantId,
338
+ ...this.token ? { "Authorization": `Bearer ${this.token}` } : {}
339
+ },
340
+ body: JSON.stringify(data)
341
+ });
342
+ if (!res.ok) {
343
+ const err = await res.json().catch(() => ({ message: res.statusText }));
344
+ throw new Error(err.message || err.error || `Update profile failed with HTTP ${res.status}`);
345
+ }
346
+ return res.json();
347
+ }
190
348
  /**
191
349
  * Fetch current authenticated user profile
192
350
  */
@@ -212,51 +370,11 @@ var AuthClient = class _AuthClient {
212
370
  return res.json();
213
371
  }
214
372
  /**
215
- * Log out and clear saved token
373
+ * Log out and clear saved tokens
216
374
  */
217
375
  logout() {
218
376
  this.setToken(null);
219
- }
220
- };
221
-
222
- // src/schema.ts
223
- var SchemaClient = class {
224
- gatewayUrl;
225
- tenantId;
226
- getToken;
227
- fetchFn;
228
- constructor(options) {
229
- this.gatewayUrl = options.gatewayUrl.replace(/\/+$/, "");
230
- this.tenantId = options.tenantId;
231
- this.getToken = options.getToken;
232
- this.fetchFn = options.fetchFn || (typeof fetch !== "undefined" ? fetch : globalThis.fetch);
233
- }
234
- getHeaders() {
235
- const headers = {
236
- "Content-Type": "application/json",
237
- "x-tenant-id": this.tenantId
238
- };
239
- const token = this.getToken();
240
- if (token) {
241
- headers["Authorization"] = `Bearer ${token}`;
242
- }
243
- return headers;
244
- }
245
- /**
246
- * Define a new JSON Schema or alter an existing schema for an entity type in real-time
247
- */
248
- async define(definition) {
249
- const url = `${this.gatewayUrl}/generic/${this.tenantId}/definition`;
250
- const res = await this.fetchFn(url, {
251
- method: "POST",
252
- headers: this.getHeaders(),
253
- body: JSON.stringify(definition)
254
- });
255
- if (!res.ok) {
256
- const err = await res.json().catch(() => ({ message: res.statusText }));
257
- throw new Error(err.message || `Failed to define schema for ${definition.typeName}`);
258
- }
259
- return res.json();
377
+ this.setRefreshToken(null);
260
378
  }
261
379
  };
262
380
 
@@ -343,6 +461,19 @@ var EntityQueryBuilder = class {
343
461
  this.queryParams.append(`related[${relationName}]`, ids);
344
462
  return this;
345
463
  }
464
+ /**
465
+ * Declaratively expand graph relations (Supabase-style join expansion)
466
+ * Example: .include('placed_by', 'payment', 'items')
467
+ */
468
+ include(...relations) {
469
+ const existing = this.queryParams.get("include");
470
+ const existingList = existing ? existing.split(",").map((s) => s.trim()) : [];
471
+ const combined = Array.from(/* @__PURE__ */ new Set([...existingList, ...relations.map((s) => s.trim())])).filter(Boolean);
472
+ if (combined.length > 0) {
473
+ this.queryParams.set("include", combined.join(","));
474
+ }
475
+ return this;
476
+ }
346
477
  // ----------------------------------------------------
347
478
  // Sorting, Pagination & Modifiers
348
479
  // ----------------------------------------------------
@@ -391,10 +522,21 @@ var EntityQueryBuilder = class {
391
522
  return res.json();
392
523
  }
393
524
  /**
394
- * Fetch a single entity by ID
525
+ * Fetch a single entity by ID with optional expansion and flattening
395
526
  */
396
- async get(id) {
397
- const url = `${this.gatewayUrl}/generic/${this.tenantId}/${this.typeName}/${id}`;
527
+ async get(id, options) {
528
+ const params = new URLSearchParams(this.queryParams);
529
+ if (options?.flat !== void 0) {
530
+ params.set("flat", options.flat ? "true" : "false");
531
+ }
532
+ if (options?.include && options.include.length > 0) {
533
+ const existing = params.get("include");
534
+ const existingList = existing ? existing.split(",").map((s) => s.trim()) : [];
535
+ const combined = Array.from(/* @__PURE__ */ new Set([...existingList, ...options.include.map((s) => s.trim())])).filter(Boolean);
536
+ params.set("include", combined.join(","));
537
+ }
538
+ const qs = params.toString();
539
+ const url = `${this.gatewayUrl}/generic/${this.tenantId}/${this.typeName}/${id}${qs ? `?${qs}` : ""}`;
398
540
  const res = await this.fetchFn(url, {
399
541
  headers: this.getHeaders()
400
542
  });
@@ -451,7 +593,194 @@ var EntityQueryBuilder = class {
451
593
  }
452
594
  return { success: true };
453
595
  }
596
+ /**
597
+ * Fetch revision history for an entity record
598
+ */
599
+ async revisions(id) {
600
+ const url = `${this.gatewayUrl}/generic/${this.tenantId}/${this.typeName}/${id}/revisions`;
601
+ const res = await this.fetchFn(url, {
602
+ headers: this.getHeaders()
603
+ });
604
+ if (!res.ok) {
605
+ const err = await res.json().catch(() => ({ message: res.statusText }));
606
+ throw new Error(err.message || `Failed to fetch revisions for ${this.typeName}/${id}`);
607
+ }
608
+ return res.json();
609
+ }
610
+ /**
611
+ * Fetch all graph relations for this entity record
612
+ */
613
+ async getRelations(id, lang) {
614
+ const qs = lang ? `?lang=${encodeURIComponent(lang)}` : "";
615
+ const url = `${this.gatewayUrl}/generic/${this.tenantId}/${this.typeName}/${id}/relations${qs}`;
616
+ const res = await this.fetchFn(url, {
617
+ headers: this.getHeaders()
618
+ });
619
+ if (!res.ok) {
620
+ const err = await res.json().catch(() => ({ message: res.statusText }));
621
+ throw new Error(err.message || `Failed to fetch relations for ${this.typeName}/${id}`);
622
+ }
623
+ return res.json();
624
+ }
625
+ /**
626
+ * Link this record to another entity record
627
+ */
628
+ async link(id, payload) {
629
+ const url = `${this.gatewayUrl}/generic/${this.tenantId}/${this.typeName}/${id}/relations`;
630
+ const res = await this.fetchFn(url, {
631
+ method: "POST",
632
+ headers: this.getHeaders(),
633
+ body: JSON.stringify(payload)
634
+ });
635
+ if (!res.ok) {
636
+ const err = await res.json().catch(() => ({ message: res.statusText }));
637
+ throw new Error(err.message || `Failed to create relation for ${this.typeName}/${id}`);
638
+ }
639
+ return res.json().catch(() => ({ success: true }));
640
+ }
641
+ /**
642
+ * Replace the full set of relations of one relation name
643
+ */
644
+ async setLinks(id, payload) {
645
+ const url = `${this.gatewayUrl}/generic/${this.tenantId}/${this.typeName}/${id}/relations`;
646
+ const res = await this.fetchFn(url, {
647
+ method: "PUT",
648
+ headers: this.getHeaders(),
649
+ body: JSON.stringify(payload)
650
+ });
651
+ if (!res.ok) {
652
+ const err = await res.json().catch(() => ({ message: res.statusText }));
653
+ throw new Error(err.message || `Failed to replace relations for ${this.typeName}/${id}`);
654
+ }
655
+ return res.json().catch(() => ({ success: true }));
656
+ }
657
+ /**
658
+ * Remove one graph link between two records
659
+ */
660
+ async unlink(id, relationName, targetId, targetTypeName) {
661
+ const url = `${this.gatewayUrl}/generic/${this.tenantId}/${this.typeName}/${id}/relations/${encodeURIComponent(relationName)}/${encodeURIComponent(targetId)}?targetTypeName=${encodeURIComponent(targetTypeName)}`;
662
+ const res = await this.fetchFn(url, {
663
+ method: "DELETE",
664
+ headers: this.getHeaders()
665
+ });
666
+ if (!res.ok) {
667
+ const err = await res.json().catch(() => ({ message: res.statusText }));
668
+ throw new Error(err.message || `Failed to delete relation ${relationName} for ${this.typeName}/${id}`);
669
+ }
670
+ return res.json().catch(() => ({ success: true }));
671
+ }
672
+ /**
673
+ * Execute a type-level workflow plugin action (e.g. checkout, batch-process)
674
+ */
675
+ async action(actionName, payload) {
676
+ const url = `${this.gatewayUrl}/generic/${this.tenantId}/${this.typeName}/actions/${actionName}`;
677
+ const res = await this.fetchFn(url, {
678
+ method: "POST",
679
+ headers: this.getHeaders(),
680
+ body: payload ? JSON.stringify(payload) : void 0
681
+ });
682
+ if (!res.ok) {
683
+ const err = await res.json().catch(() => ({ message: res.statusText }));
684
+ throw new Error(err.message || `Action ${actionName} failed on type ${this.typeName}`);
685
+ }
686
+ return res.json();
687
+ }
688
+ /**
689
+ * Execute an instance-level workflow plugin action (e.g. cancel order, approve)
690
+ */
691
+ async instanceAction(id, actionName, payload) {
692
+ const url = `${this.gatewayUrl}/generic/${this.tenantId}/${this.typeName}/${id}/actions/${actionName}`;
693
+ const res = await this.fetchFn(url, {
694
+ method: "POST",
695
+ headers: this.getHeaders(),
696
+ body: payload ? JSON.stringify(payload) : void 0
697
+ });
698
+ if (!res.ok) {
699
+ const err = await res.json().catch(() => ({ message: res.statusText }));
700
+ throw new Error(err.message || `Action ${actionName} failed on ${this.typeName}/${id}`);
701
+ }
702
+ return res.json();
703
+ }
704
+ /**
705
+ * Export matching entity records to CSV format with active filters
706
+ */
707
+ async exportCsv() {
708
+ const qs = this.queryParams.toString();
709
+ const url = `${this.gatewayUrl}/generic/${this.tenantId}/${this.typeName}/export/csv${qs ? `?${qs}` : ""}`;
710
+ const res = await this.fetchFn(url, {
711
+ headers: {
712
+ ...this.getHeaders(),
713
+ "Accept": "text/csv"
714
+ }
715
+ });
716
+ if (!res.ok) {
717
+ const err = await res.json().catch(() => ({ message: res.statusText }));
718
+ throw new Error(err.message || `Failed to export CSV for ${this.typeName}`);
719
+ }
720
+ return res.text();
721
+ }
722
+ };
723
+
724
+ // src/schema.ts
725
+ var SchemaClient = class {
726
+ gatewayUrl;
727
+ tenantId;
728
+ getToken;
729
+ fetchFn;
730
+ constructor(options) {
731
+ this.gatewayUrl = options.gatewayUrl.replace(/\/+$/, "");
732
+ this.tenantId = options.tenantId;
733
+ this.getToken = options.getToken;
734
+ this.fetchFn = options.fetchFn || (typeof fetch !== "undefined" ? fetch : globalThis.fetch);
735
+ }
736
+ getHeaders() {
737
+ const headers = {
738
+ "Content-Type": "application/json",
739
+ "x-tenant-id": this.tenantId
740
+ };
741
+ const token = this.getToken();
742
+ if (token) {
743
+ headers["Authorization"] = `Bearer ${token}`;
744
+ }
745
+ return headers;
746
+ }
747
+ /**
748
+ * Define a new JSON Schema or alter an existing schema for an entity type in real-time
749
+ */
750
+ async define(definition) {
751
+ const url = `${this.gatewayUrl}/generic/${this.tenantId}/definition`;
752
+ const res = await this.fetchFn(url, {
753
+ method: "POST",
754
+ headers: this.getHeaders(),
755
+ body: JSON.stringify(definition)
756
+ });
757
+ if (!res.ok) {
758
+ const err = await res.json().catch(() => ({ message: res.statusText }));
759
+ throw new Error(err.message || `Failed to define schema for ${definition.typeName}`);
760
+ }
761
+ return res.json();
762
+ }
763
+ };
764
+
765
+ // src/storage.ts
766
+ var MemoryStorage = class {
767
+ store = /* @__PURE__ */ new Map();
768
+ getItem(key) {
769
+ return this.store.get(key) ?? null;
770
+ }
771
+ setItem(key, value) {
772
+ this.store.set(key, value);
773
+ }
774
+ removeItem(key) {
775
+ this.store.delete(key);
776
+ }
454
777
  };
778
+ function getDefaultStorage() {
779
+ if (typeof window !== "undefined" && window.localStorage) {
780
+ return window.localStorage;
781
+ }
782
+ return new MemoryStorage();
783
+ }
455
784
 
456
785
  // src/client.ts
457
786
  var Zone4CodeClient = class {
@@ -505,6 +834,27 @@ var Zone4CodeClient = class {
505
834
  entities(typeName) {
506
835
  return this.from(typeName);
507
836
  }
837
+ /**
838
+ * Get current authenticated user profile and platform wallet
839
+ */
840
+ async getMe() {
841
+ const token = this.auth.getToken();
842
+ if (!token) {
843
+ throw new Error("Not authenticated: please call login() or setToken() first");
844
+ }
845
+ const res = await this.fetchFn(`${this.gatewayUrl}/generic/${this.tenantId}/me`, {
846
+ headers: {
847
+ "Content-Type": "application/json",
848
+ "Authorization": `Bearer ${token}`,
849
+ "x-tenant-id": this.tenantId
850
+ }
851
+ });
852
+ if (!res.ok) {
853
+ const err = await res.json().catch(() => ({ message: res.statusText }));
854
+ throw new Error(err.message || `Failed to fetch profile & wallet (HTTP ${res.status})`);
855
+ }
856
+ return res.json();
857
+ }
508
858
  /**
509
859
  * Check gateway connectivity & health
510
860
  */