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.cjs CHANGED
@@ -31,26 +31,6 @@ __export(index_exports, {
31
31
  });
32
32
  module.exports = __toCommonJS(index_exports);
33
33
 
34
- // src/storage.ts
35
- var MemoryStorage = class {
36
- store = /* @__PURE__ */ new Map();
37
- getItem(key) {
38
- return this.store.get(key) ?? null;
39
- }
40
- setItem(key, value) {
41
- this.store.set(key, value);
42
- }
43
- removeItem(key) {
44
- this.store.delete(key);
45
- }
46
- };
47
- function getDefaultStorage() {
48
- if (typeof window !== "undefined" && window.localStorage) {
49
- return window.localStorage;
50
- }
51
- return new MemoryStorage();
52
- }
53
-
54
34
  // src/auth.ts
55
35
  function decodeJwtPayload(token) {
56
36
  try {
@@ -75,7 +55,9 @@ var AuthClient = class _AuthClient {
75
55
  tenantId;
76
56
  storage;
77
57
  storageKey;
58
+ refreshStorageKey;
78
59
  token = null;
60
+ refreshTokenValue = null;
79
61
  fetchFn;
80
62
  static TOKEN_KEY_PREFIX = "z4c_token_";
81
63
  constructor(options) {
@@ -83,6 +65,7 @@ var AuthClient = class _AuthClient {
83
65
  this.tenantId = options.tenantId;
84
66
  this.storage = options.storage;
85
67
  this.storageKey = options.storageKey || _AuthClient.TOKEN_KEY_PREFIX + this.tenantId;
68
+ this.refreshStorageKey = this.storageKey + "_refresh";
86
69
  this.fetchFn = options.fetchFn || (typeof fetch !== "undefined" ? fetch : globalThis.fetch);
87
70
  if (options.initialToken) {
88
71
  this.setToken(options.initialToken);
@@ -91,11 +74,18 @@ var AuthClient = class _AuthClient {
91
74
  if (stored) this.token = stored;
92
75
  }).catch(() => {
93
76
  });
77
+ Promise.resolve(this.storage.getItem(this.refreshStorageKey)).then((stored) => {
78
+ if (stored) this.refreshTokenValue = stored;
79
+ }).catch(() => {
80
+ });
94
81
  }
95
82
  }
96
83
  getToken() {
97
84
  return this.token;
98
85
  }
86
+ getRefreshToken() {
87
+ return this.refreshTokenValue;
88
+ }
99
89
  setToken(token) {
100
90
  this.token = token;
101
91
  if (token) {
@@ -104,6 +94,14 @@ var AuthClient = class _AuthClient {
104
94
  this.storage.removeItem(this.storageKey);
105
95
  }
106
96
  }
97
+ setRefreshToken(token) {
98
+ this.refreshTokenValue = token;
99
+ if (token) {
100
+ this.storage.setItem(this.refreshStorageKey, token);
101
+ } else {
102
+ this.storage.removeItem(this.refreshStorageKey);
103
+ }
104
+ }
107
105
  isAuthenticated() {
108
106
  return !!this.token;
109
107
  }
@@ -212,6 +210,9 @@ var AuthClient = class _AuthClient {
212
210
  if (token) {
213
211
  this.setToken(token);
214
212
  }
213
+ if (json.refresh_token) {
214
+ this.setRefreshToken(json.refresh_token);
215
+ }
215
216
  const user = this.getUser() || json.user;
216
217
  return {
217
218
  token: token || "",
@@ -220,6 +221,163 @@ var AuthClient = class _AuthClient {
220
221
  ...json
221
222
  };
222
223
  }
224
+ /**
225
+ * Authenticate via Google OAuth ID token
226
+ */
227
+ async loginWithGoogle(token) {
228
+ const url = `${this.gatewayUrl}/auth/${this.tenantId}/login/google`;
229
+ const res = await this.fetchFn(url, {
230
+ method: "POST",
231
+ headers: {
232
+ "Content-Type": "application/json",
233
+ "x-tenant-id": this.tenantId
234
+ },
235
+ body: JSON.stringify({ token })
236
+ });
237
+ if (!res.ok) {
238
+ const err = await res.json().catch(() => ({ message: res.statusText }));
239
+ throw new Error(err.message || err.error || `Google login failed with HTTP ${res.status}`);
240
+ }
241
+ const json = await res.json();
242
+ const jwt = json.token || json.access_token;
243
+ if (jwt) {
244
+ this.setToken(jwt);
245
+ }
246
+ if (json.refresh_token) {
247
+ this.setRefreshToken(json.refresh_token);
248
+ }
249
+ const user = this.getUser() || json.user;
250
+ return {
251
+ token: jwt || "",
252
+ refreshToken: json.refresh_token,
253
+ user,
254
+ ...json
255
+ };
256
+ }
257
+ /**
258
+ * Renew expired access token using stored or provided refresh token
259
+ */
260
+ async refreshToken(refreshToken) {
261
+ const tokenToUse = refreshToken || this.getRefreshToken();
262
+ if (!tokenToUse) {
263
+ throw new Error("No refresh token available");
264
+ }
265
+ const url = `${this.gatewayUrl}/auth/${this.tenantId}/refresh`;
266
+ const res = await this.fetchFn(url, {
267
+ method: "POST",
268
+ headers: {
269
+ "Content-Type": "application/json",
270
+ "x-tenant-id": this.tenantId
271
+ },
272
+ body: JSON.stringify({ refresh_token: tokenToUse })
273
+ });
274
+ if (!res.ok) {
275
+ const err = await res.json().catch(() => ({ message: res.statusText }));
276
+ throw new Error(err.message || err.error || `Token refresh failed with HTTP ${res.status}`);
277
+ }
278
+ const json = await res.json();
279
+ const token = json.token || json.access_token;
280
+ if (token) {
281
+ this.setToken(token);
282
+ }
283
+ if (json.refresh_token) {
284
+ this.setRefreshToken(json.refresh_token);
285
+ }
286
+ const user = this.getUser() || json.user;
287
+ return {
288
+ token: token || "",
289
+ refreshToken: json.refresh_token,
290
+ user,
291
+ ...json
292
+ };
293
+ }
294
+ /**
295
+ * Request password reset email
296
+ */
297
+ async forgotPassword(data) {
298
+ const payload = typeof data === "string" ? { email: data } : data;
299
+ const url = `${this.gatewayUrl}/auth/${this.tenantId}/auth/forgot-password`;
300
+ const res = await this.fetchFn(url, {
301
+ method: "POST",
302
+ headers: {
303
+ "Content-Type": "application/json",
304
+ "x-tenant-id": this.tenantId
305
+ },
306
+ body: JSON.stringify(payload)
307
+ });
308
+ const json = await res.json().catch(() => ({}));
309
+ if (!res.ok) {
310
+ throw new Error(json.message || json.error || `Forgot password request failed with HTTP ${res.status}`);
311
+ }
312
+ return json;
313
+ }
314
+ /**
315
+ * Complete password reset using action token
316
+ */
317
+ async resetPassword(data) {
318
+ const url = `${this.gatewayUrl}/auth/${this.tenantId}/auth/reset-password`;
319
+ const res = await this.fetchFn(url, {
320
+ method: "POST",
321
+ headers: {
322
+ "Content-Type": "application/json",
323
+ "x-tenant-id": this.tenantId
324
+ },
325
+ body: JSON.stringify(data)
326
+ });
327
+ const json = await res.json().catch(() => ({}));
328
+ if (!res.ok) {
329
+ throw new Error(json.message || json.error || `Reset password failed with HTTP ${res.status}`);
330
+ }
331
+ return json;
332
+ }
333
+ /**
334
+ * Change password for current or specified user
335
+ */
336
+ async changePassword(data, userId) {
337
+ const targetUserId = userId || this.getUser()?.id;
338
+ if (!targetUserId) {
339
+ throw new Error("User ID required: user is not logged in and no userId was provided");
340
+ }
341
+ const url = `${this.gatewayUrl}/auth/${this.tenantId}/user/${targetUserId}/change-password`;
342
+ const res = await this.fetchFn(url, {
343
+ method: "POST",
344
+ headers: {
345
+ "Content-Type": "application/json",
346
+ "x-tenant-id": this.tenantId,
347
+ ...this.token ? { "Authorization": `Bearer ${this.token}` } : {}
348
+ },
349
+ body: JSON.stringify(data)
350
+ });
351
+ if (!res.ok) {
352
+ const err = await res.json().catch(() => ({ message: res.statusText }));
353
+ throw new Error(err.message || err.error || `Change password failed with HTTP ${res.status}`);
354
+ }
355
+ return res.json().catch(() => ({ success: true }));
356
+ }
357
+ /**
358
+ * Update profile attributes for current or specified user
359
+ */
360
+ async updateProfile(data, userId) {
361
+ const targetUserId = userId || this.getUser()?.id;
362
+ if (!targetUserId) {
363
+ throw new Error("User ID required: user is not logged in and no userId was provided");
364
+ }
365
+ const url = `${this.gatewayUrl}/auth/${this.tenantId}/user/${targetUserId}`;
366
+ const res = await this.fetchFn(url, {
367
+ method: "PUT",
368
+ headers: {
369
+ "Content-Type": "application/json",
370
+ "x-tenant-id": this.tenantId,
371
+ ...this.token ? { "Authorization": `Bearer ${this.token}` } : {}
372
+ },
373
+ body: JSON.stringify(data)
374
+ });
375
+ if (!res.ok) {
376
+ const err = await res.json().catch(() => ({ message: res.statusText }));
377
+ throw new Error(err.message || err.error || `Update profile failed with HTTP ${res.status}`);
378
+ }
379
+ return res.json();
380
+ }
223
381
  /**
224
382
  * Fetch current authenticated user profile
225
383
  */
@@ -245,51 +403,11 @@ var AuthClient = class _AuthClient {
245
403
  return res.json();
246
404
  }
247
405
  /**
248
- * Log out and clear saved token
406
+ * Log out and clear saved tokens
249
407
  */
250
408
  logout() {
251
409
  this.setToken(null);
252
- }
253
- };
254
-
255
- // src/schema.ts
256
- var SchemaClient = class {
257
- gatewayUrl;
258
- tenantId;
259
- getToken;
260
- fetchFn;
261
- constructor(options) {
262
- this.gatewayUrl = options.gatewayUrl.replace(/\/+$/, "");
263
- this.tenantId = options.tenantId;
264
- this.getToken = options.getToken;
265
- this.fetchFn = options.fetchFn || (typeof fetch !== "undefined" ? fetch : globalThis.fetch);
266
- }
267
- getHeaders() {
268
- const headers = {
269
- "Content-Type": "application/json",
270
- "x-tenant-id": this.tenantId
271
- };
272
- const token = this.getToken();
273
- if (token) {
274
- headers["Authorization"] = `Bearer ${token}`;
275
- }
276
- return headers;
277
- }
278
- /**
279
- * Define a new JSON Schema or alter an existing schema for an entity type in real-time
280
- */
281
- async define(definition) {
282
- const url = `${this.gatewayUrl}/generic/${this.tenantId}/definition`;
283
- const res = await this.fetchFn(url, {
284
- method: "POST",
285
- headers: this.getHeaders(),
286
- body: JSON.stringify(definition)
287
- });
288
- if (!res.ok) {
289
- const err = await res.json().catch(() => ({ message: res.statusText }));
290
- throw new Error(err.message || `Failed to define schema for ${definition.typeName}`);
291
- }
292
- return res.json();
410
+ this.setRefreshToken(null);
293
411
  }
294
412
  };
295
413
 
@@ -376,6 +494,19 @@ var EntityQueryBuilder = class {
376
494
  this.queryParams.append(`related[${relationName}]`, ids);
377
495
  return this;
378
496
  }
497
+ /**
498
+ * Declaratively expand graph relations (Supabase-style join expansion)
499
+ * Example: .include('placed_by', 'payment', 'items')
500
+ */
501
+ include(...relations) {
502
+ const existing = this.queryParams.get("include");
503
+ const existingList = existing ? existing.split(",").map((s) => s.trim()) : [];
504
+ const combined = Array.from(/* @__PURE__ */ new Set([...existingList, ...relations.map((s) => s.trim())])).filter(Boolean);
505
+ if (combined.length > 0) {
506
+ this.queryParams.set("include", combined.join(","));
507
+ }
508
+ return this;
509
+ }
379
510
  // ----------------------------------------------------
380
511
  // Sorting, Pagination & Modifiers
381
512
  // ----------------------------------------------------
@@ -424,10 +555,21 @@ var EntityQueryBuilder = class {
424
555
  return res.json();
425
556
  }
426
557
  /**
427
- * Fetch a single entity by ID
558
+ * Fetch a single entity by ID with optional expansion and flattening
428
559
  */
429
- async get(id) {
430
- const url = `${this.gatewayUrl}/generic/${this.tenantId}/${this.typeName}/${id}`;
560
+ async get(id, options) {
561
+ const params = new URLSearchParams(this.queryParams);
562
+ if (options?.flat !== void 0) {
563
+ params.set("flat", options.flat ? "true" : "false");
564
+ }
565
+ if (options?.include && options.include.length > 0) {
566
+ const existing = params.get("include");
567
+ const existingList = existing ? existing.split(",").map((s) => s.trim()) : [];
568
+ const combined = Array.from(/* @__PURE__ */ new Set([...existingList, ...options.include.map((s) => s.trim())])).filter(Boolean);
569
+ params.set("include", combined.join(","));
570
+ }
571
+ const qs = params.toString();
572
+ const url = `${this.gatewayUrl}/generic/${this.tenantId}/${this.typeName}/${id}${qs ? `?${qs}` : ""}`;
431
573
  const res = await this.fetchFn(url, {
432
574
  headers: this.getHeaders()
433
575
  });
@@ -484,7 +626,194 @@ var EntityQueryBuilder = class {
484
626
  }
485
627
  return { success: true };
486
628
  }
629
+ /**
630
+ * Fetch revision history for an entity record
631
+ */
632
+ async revisions(id) {
633
+ const url = `${this.gatewayUrl}/generic/${this.tenantId}/${this.typeName}/${id}/revisions`;
634
+ const res = await this.fetchFn(url, {
635
+ headers: this.getHeaders()
636
+ });
637
+ if (!res.ok) {
638
+ const err = await res.json().catch(() => ({ message: res.statusText }));
639
+ throw new Error(err.message || `Failed to fetch revisions for ${this.typeName}/${id}`);
640
+ }
641
+ return res.json();
642
+ }
643
+ /**
644
+ * Fetch all graph relations for this entity record
645
+ */
646
+ async getRelations(id, lang) {
647
+ const qs = lang ? `?lang=${encodeURIComponent(lang)}` : "";
648
+ const url = `${this.gatewayUrl}/generic/${this.tenantId}/${this.typeName}/${id}/relations${qs}`;
649
+ const res = await this.fetchFn(url, {
650
+ headers: this.getHeaders()
651
+ });
652
+ if (!res.ok) {
653
+ const err = await res.json().catch(() => ({ message: res.statusText }));
654
+ throw new Error(err.message || `Failed to fetch relations for ${this.typeName}/${id}`);
655
+ }
656
+ return res.json();
657
+ }
658
+ /**
659
+ * Link this record to another entity record
660
+ */
661
+ async link(id, payload) {
662
+ const url = `${this.gatewayUrl}/generic/${this.tenantId}/${this.typeName}/${id}/relations`;
663
+ const res = await this.fetchFn(url, {
664
+ method: "POST",
665
+ headers: this.getHeaders(),
666
+ body: JSON.stringify(payload)
667
+ });
668
+ if (!res.ok) {
669
+ const err = await res.json().catch(() => ({ message: res.statusText }));
670
+ throw new Error(err.message || `Failed to create relation for ${this.typeName}/${id}`);
671
+ }
672
+ return res.json().catch(() => ({ success: true }));
673
+ }
674
+ /**
675
+ * Replace the full set of relations of one relation name
676
+ */
677
+ async setLinks(id, payload) {
678
+ const url = `${this.gatewayUrl}/generic/${this.tenantId}/${this.typeName}/${id}/relations`;
679
+ const res = await this.fetchFn(url, {
680
+ method: "PUT",
681
+ headers: this.getHeaders(),
682
+ body: JSON.stringify(payload)
683
+ });
684
+ if (!res.ok) {
685
+ const err = await res.json().catch(() => ({ message: res.statusText }));
686
+ throw new Error(err.message || `Failed to replace relations for ${this.typeName}/${id}`);
687
+ }
688
+ return res.json().catch(() => ({ success: true }));
689
+ }
690
+ /**
691
+ * Remove one graph link between two records
692
+ */
693
+ async unlink(id, relationName, targetId, targetTypeName) {
694
+ const url = `${this.gatewayUrl}/generic/${this.tenantId}/${this.typeName}/${id}/relations/${encodeURIComponent(relationName)}/${encodeURIComponent(targetId)}?targetTypeName=${encodeURIComponent(targetTypeName)}`;
695
+ const res = await this.fetchFn(url, {
696
+ method: "DELETE",
697
+ headers: this.getHeaders()
698
+ });
699
+ if (!res.ok) {
700
+ const err = await res.json().catch(() => ({ message: res.statusText }));
701
+ throw new Error(err.message || `Failed to delete relation ${relationName} for ${this.typeName}/${id}`);
702
+ }
703
+ return res.json().catch(() => ({ success: true }));
704
+ }
705
+ /**
706
+ * Execute a type-level workflow plugin action (e.g. checkout, batch-process)
707
+ */
708
+ async action(actionName, payload) {
709
+ const url = `${this.gatewayUrl}/generic/${this.tenantId}/${this.typeName}/actions/${actionName}`;
710
+ const res = await this.fetchFn(url, {
711
+ method: "POST",
712
+ headers: this.getHeaders(),
713
+ body: payload ? JSON.stringify(payload) : void 0
714
+ });
715
+ if (!res.ok) {
716
+ const err = await res.json().catch(() => ({ message: res.statusText }));
717
+ throw new Error(err.message || `Action ${actionName} failed on type ${this.typeName}`);
718
+ }
719
+ return res.json();
720
+ }
721
+ /**
722
+ * Execute an instance-level workflow plugin action (e.g. cancel order, approve)
723
+ */
724
+ async instanceAction(id, actionName, payload) {
725
+ const url = `${this.gatewayUrl}/generic/${this.tenantId}/${this.typeName}/${id}/actions/${actionName}`;
726
+ const res = await this.fetchFn(url, {
727
+ method: "POST",
728
+ headers: this.getHeaders(),
729
+ body: payload ? JSON.stringify(payload) : void 0
730
+ });
731
+ if (!res.ok) {
732
+ const err = await res.json().catch(() => ({ message: res.statusText }));
733
+ throw new Error(err.message || `Action ${actionName} failed on ${this.typeName}/${id}`);
734
+ }
735
+ return res.json();
736
+ }
737
+ /**
738
+ * Export matching entity records to CSV format with active filters
739
+ */
740
+ async exportCsv() {
741
+ const qs = this.queryParams.toString();
742
+ const url = `${this.gatewayUrl}/generic/${this.tenantId}/${this.typeName}/export/csv${qs ? `?${qs}` : ""}`;
743
+ const res = await this.fetchFn(url, {
744
+ headers: {
745
+ ...this.getHeaders(),
746
+ "Accept": "text/csv"
747
+ }
748
+ });
749
+ if (!res.ok) {
750
+ const err = await res.json().catch(() => ({ message: res.statusText }));
751
+ throw new Error(err.message || `Failed to export CSV for ${this.typeName}`);
752
+ }
753
+ return res.text();
754
+ }
755
+ };
756
+
757
+ // src/schema.ts
758
+ var SchemaClient = class {
759
+ gatewayUrl;
760
+ tenantId;
761
+ getToken;
762
+ fetchFn;
763
+ constructor(options) {
764
+ this.gatewayUrl = options.gatewayUrl.replace(/\/+$/, "");
765
+ this.tenantId = options.tenantId;
766
+ this.getToken = options.getToken;
767
+ this.fetchFn = options.fetchFn || (typeof fetch !== "undefined" ? fetch : globalThis.fetch);
768
+ }
769
+ getHeaders() {
770
+ const headers = {
771
+ "Content-Type": "application/json",
772
+ "x-tenant-id": this.tenantId
773
+ };
774
+ const token = this.getToken();
775
+ if (token) {
776
+ headers["Authorization"] = `Bearer ${token}`;
777
+ }
778
+ return headers;
779
+ }
780
+ /**
781
+ * Define a new JSON Schema or alter an existing schema for an entity type in real-time
782
+ */
783
+ async define(definition) {
784
+ const url = `${this.gatewayUrl}/generic/${this.tenantId}/definition`;
785
+ const res = await this.fetchFn(url, {
786
+ method: "POST",
787
+ headers: this.getHeaders(),
788
+ body: JSON.stringify(definition)
789
+ });
790
+ if (!res.ok) {
791
+ const err = await res.json().catch(() => ({ message: res.statusText }));
792
+ throw new Error(err.message || `Failed to define schema for ${definition.typeName}`);
793
+ }
794
+ return res.json();
795
+ }
796
+ };
797
+
798
+ // src/storage.ts
799
+ var MemoryStorage = class {
800
+ store = /* @__PURE__ */ new Map();
801
+ getItem(key) {
802
+ return this.store.get(key) ?? null;
803
+ }
804
+ setItem(key, value) {
805
+ this.store.set(key, value);
806
+ }
807
+ removeItem(key) {
808
+ this.store.delete(key);
809
+ }
487
810
  };
811
+ function getDefaultStorage() {
812
+ if (typeof window !== "undefined" && window.localStorage) {
813
+ return window.localStorage;
814
+ }
815
+ return new MemoryStorage();
816
+ }
488
817
 
489
818
  // src/client.ts
490
819
  var Zone4CodeClient = class {
@@ -538,6 +867,27 @@ var Zone4CodeClient = class {
538
867
  entities(typeName) {
539
868
  return this.from(typeName);
540
869
  }
870
+ /**
871
+ * Get current authenticated user profile and platform wallet
872
+ */
873
+ async getMe() {
874
+ const token = this.auth.getToken();
875
+ if (!token) {
876
+ throw new Error("Not authenticated: please call login() or setToken() first");
877
+ }
878
+ const res = await this.fetchFn(`${this.gatewayUrl}/generic/${this.tenantId}/me`, {
879
+ headers: {
880
+ "Content-Type": "application/json",
881
+ "Authorization": `Bearer ${token}`,
882
+ "x-tenant-id": this.tenantId
883
+ }
884
+ });
885
+ if (!res.ok) {
886
+ const err = await res.json().catch(() => ({ message: res.statusText }));
887
+ throw new Error(err.message || `Failed to fetch profile & wallet (HTTP ${res.status})`);
888
+ }
889
+ return res.json();
890
+ }
541
891
  /**
542
892
  * Check gateway connectivity & health
543
893
  */