@maravilla-labs/platform 0.3.5 → 0.3.8

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,3 +1,75 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/request-scope.ts
12
+ var request_scope_exports = {};
13
+ __export(request_scope_exports, {
14
+ anonymousCaller: () => anonymousCaller,
15
+ getCurrentRequestStore: () => getCurrentRequestStore,
16
+ getRequestAuthHeader: () => getRequestAuthHeader,
17
+ runWithRequest: () => runWithRequest
18
+ });
19
+ async function ensureAls() {
20
+ if (alsImpl) return alsImpl;
21
+ try {
22
+ const { AsyncLocalStorage } = await import("async_hooks");
23
+ const inner = new AsyncLocalStorage();
24
+ alsImpl = {
25
+ run: (s, fn) => inner.run(s, fn),
26
+ getStore: () => inner.getStore()
27
+ };
28
+ } catch {
29
+ let cached;
30
+ alsImpl = {
31
+ run: (s, fn) => {
32
+ const prev = cached;
33
+ cached = s;
34
+ try {
35
+ return fn();
36
+ } finally {
37
+ cached = prev;
38
+ }
39
+ },
40
+ getStore: () => cached
41
+ };
42
+ }
43
+ return alsImpl;
44
+ }
45
+ async function runWithRequest(fn) {
46
+ const als = await ensureAls();
47
+ return als.run({ policyEnabled: true }, () => Promise.resolve(fn()));
48
+ }
49
+ function getCurrentRequestStore() {
50
+ return alsImpl?.getStore();
51
+ }
52
+ function anonymousCaller() {
53
+ return {
54
+ user_id: "",
55
+ email: "",
56
+ is_admin: false,
57
+ roles: [],
58
+ is_anonymous: true
59
+ };
60
+ }
61
+ function getRequestAuthHeader() {
62
+ const token = alsImpl?.getStore()?.token;
63
+ return token ? { Authorization: `Bearer ${token}` } : {};
64
+ }
65
+ var alsImpl;
66
+ var init_request_scope = __esm({
67
+ "src/request-scope.ts"() {
68
+ "use strict";
69
+ alsImpl = null;
70
+ }
71
+ });
72
+
1
73
  // src/media.ts
2
74
  var RemoteMediaService = class {
3
75
  constructor(baseUrl, headers) {
@@ -7,9 +79,10 @@ var RemoteMediaService = class {
7
79
  baseUrl;
8
80
  headers;
9
81
  async fetch(url, options = {}) {
82
+ const { getRequestAuthHeader: getRequestAuthHeader2 } = await Promise.resolve().then(() => (init_request_scope(), request_scope_exports));
10
83
  const response = await fetch(url, {
11
84
  ...options,
12
- headers: { ...this.headers, ...options.headers }
85
+ headers: { ...this.headers, ...getRequestAuthHeader2(), ...options.headers }
13
86
  });
14
87
  if (!response.ok) {
15
88
  const error = await response.text();
@@ -51,6 +124,7 @@ var RemoteMediaService = class {
51
124
  };
52
125
 
53
126
  // src/remote-client.ts
127
+ init_request_scope();
54
128
  var RemoteKvNamespace = class {
55
129
  constructor(baseUrl, namespace, headers) {
56
130
  this.baseUrl = baseUrl;
@@ -69,7 +143,7 @@ var RemoteKvNamespace = class {
69
143
  async fetch(url, options = {}) {
70
144
  const response = await fetch(url, {
71
145
  ...options,
72
- headers: { ...this.headers, ...options.headers }
146
+ headers: { ...this.headers, ...getRequestAuthHeader(), ...options.headers }
73
147
  });
74
148
  if (!response.ok && response.status !== 404) {
75
149
  const error = await response.text();
@@ -127,7 +201,7 @@ var RemoteDatabase = class {
127
201
  async fetch(url, options = {}) {
128
202
  const response = await fetch(url, {
129
203
  ...options,
130
- headers: { ...this.headers, ...options.headers }
204
+ headers: { ...this.headers, ...getRequestAuthHeader(), ...options.headers }
131
205
  });
132
206
  if (!response.ok && response.status !== 404) {
133
207
  const error = await response.text();
@@ -238,7 +312,7 @@ var RemoteStorage = class _RemoteStorage {
238
312
  async fetch(url, options = {}) {
239
313
  const response = await fetch(url, {
240
314
  ...options,
241
- headers: { ...this.headers, ...options.headers }
315
+ headers: { ...this.headers, ...getRequestAuthHeader(), ...options.headers }
242
316
  });
243
317
  if (!response.ok && response.status !== 404) {
244
318
  const error = await response.text();
@@ -247,25 +321,29 @@ var RemoteStorage = class _RemoteStorage {
247
321
  return response;
248
322
  }
249
323
  async generateUploadUrl(key, contentType, options) {
324
+ const res = await this.fetch(`${this.baseUrl}/api/storage/upload-url`, {
325
+ method: "POST",
326
+ body: JSON.stringify({ key, content_type: contentType, size_limit: options?.sizeLimit })
327
+ });
328
+ const data = await res.json();
250
329
  return {
251
- url: `${this.baseUrl}/api/storage/${encodeURIComponent(key)}`,
252
- method: "PUT",
253
- headers: {
254
- "Content-Type": contentType,
255
- ...this.headers
256
- // Include tenant headers
257
- },
258
- expiresIn: 3600
259
- // 1 hour
330
+ url: data.url.startsWith("http") ? data.url : `${this.baseUrl}${data.url}`,
331
+ method: data.method,
332
+ headers: { ...data.headers, ...this.headers },
333
+ expiresIn: data.expiresIn
260
334
  };
261
335
  }
262
336
  async generateDownloadUrl(key, options) {
337
+ const res = await this.fetch(`${this.baseUrl}/api/storage/download-url`, {
338
+ method: "POST",
339
+ body: JSON.stringify({ key, expires_in: options?.expiresIn })
340
+ });
341
+ const data = await res.json();
263
342
  return {
264
- url: `${this.baseUrl}/api/storage/${encodeURIComponent(key)}`,
265
- method: "GET",
266
- headers: {},
267
- expiresIn: options?.expiresIn || 3600
268
- // Default 1 hour
343
+ url: data.url.startsWith("http") ? data.url : `${this.baseUrl}${data.url}`,
344
+ method: data.method,
345
+ headers: data.headers,
346
+ expiresIn: data.expiresIn
269
347
  };
270
348
  }
271
349
  // Dev-mode: emit the same `/_assets/<key>` shape production does.
@@ -396,7 +474,7 @@ var RemotePresenceService = class {
396
474
  async join(channel, userId, metadata) {
397
475
  const res = await fetch(`${this.baseUrl}/api/realtime/presence/${encodeURIComponent(channel)}/join`, {
398
476
  method: "POST",
399
- headers: this.headers,
477
+ headers: { ...this.headers, ...getRequestAuthHeader() },
400
478
  body: JSON.stringify({ userId, metadata })
401
479
  });
402
480
  const data = await res.json();
@@ -405,7 +483,7 @@ var RemotePresenceService = class {
405
483
  async leave(channel, userId) {
406
484
  const res = await fetch(`${this.baseUrl}/api/realtime/presence/${encodeURIComponent(channel)}/leave`, {
407
485
  method: "POST",
408
- headers: this.headers,
486
+ headers: { ...this.headers, ...getRequestAuthHeader() },
409
487
  body: JSON.stringify({ userId })
410
488
  });
411
489
  const data = await res.json();
@@ -413,7 +491,7 @@ var RemotePresenceService = class {
413
491
  }
414
492
  async members(channel) {
415
493
  const res = await fetch(`${this.baseUrl}/api/realtime/presence/${encodeURIComponent(channel)}`, {
416
- headers: this.headers
494
+ headers: { ...this.headers, ...getRequestAuthHeader() }
417
495
  });
418
496
  const data = await res.json();
419
497
  return data.members ?? [];
@@ -431,19 +509,19 @@ var RemoteRealtimeService = class {
431
509
  async publish(channel, data, options) {
432
510
  await fetch(`${this.baseUrl}/api/realtime/publish`, {
433
511
  method: "POST",
434
- headers: this.headers,
512
+ headers: { ...this.headers, ...getRequestAuthHeader() },
435
513
  body: JSON.stringify({ channel, data, userId: options?.userId })
436
514
  });
437
515
  }
438
516
  async channels() {
439
517
  const res = await fetch(`${this.baseUrl}/api/realtime/channels`, {
440
- headers: this.headers
518
+ headers: { ...this.headers, ...getRequestAuthHeader() }
441
519
  });
442
520
  const data = await res.json();
443
521
  return data.channels ?? [];
444
522
  }
445
523
  };
446
- var RemoteAuthService = class {
524
+ var RemoteAuthService = class _RemoteAuthService {
447
525
  constructor(baseUrl, headers) {
448
526
  this.baseUrl = baseUrl;
449
527
  this.headers = headers;
@@ -453,7 +531,7 @@ var RemoteAuthService = class {
453
531
  async post(path, body) {
454
532
  const res = await fetch(`${this.baseUrl}/api/platform/auth${path}`, {
455
533
  method: "POST",
456
- headers: this.headers,
534
+ headers: { ...this.headers, ...getRequestAuthHeader() },
457
535
  body: body !== void 0 ? JSON.stringify(body) : void 0
458
536
  });
459
537
  if (!res.ok) {
@@ -514,6 +592,131 @@ var RemoteAuthService = class {
514
592
  async handleOAuthCallback(provider, params) {
515
593
  return this.post("/oauth/callback", { provider, code: params.code, state: params.state });
516
594
  }
595
+ // ── Groups ──
596
+ async createGroup(options) {
597
+ return this.post("/groups/create", options);
598
+ }
599
+ async listGroups() {
600
+ return this.post("/groups/list", {});
601
+ }
602
+ async getGroup(groupId) {
603
+ return this.post("/groups/get", { group_id: groupId });
604
+ }
605
+ async updateGroup(groupId, options) {
606
+ return this.post("/groups/update", { group_id: groupId, ...options });
607
+ }
608
+ async deleteGroup(groupId) {
609
+ await this.post("/groups/delete", { group_id: groupId });
610
+ }
611
+ async addUserToGroup(userId, groupId) {
612
+ await this.post("/groups/add-user", { user_id: userId, group_id: groupId });
613
+ }
614
+ async removeUserFromGroup(userId, groupId) {
615
+ await this.post("/groups/remove-user", { user_id: userId, group_id: groupId });
616
+ }
617
+ async getUserGroups(userId) {
618
+ return this.post("/groups/get-user-groups", { user_id: userId });
619
+ }
620
+ async getGroupMembers(groupId) {
621
+ return this.post("/groups/get-members", { group_id: groupId });
622
+ }
623
+ async getGroupPermissions(groupId) {
624
+ return this.post("/groups/get-permissions", { group_id: groupId });
625
+ }
626
+ async setGroupPermissions(groupId, permissions) {
627
+ await this.post("/groups/set-permissions", { group_id: groupId, permissions });
628
+ }
629
+ // ── Circles ──
630
+ async createCircle(options) {
631
+ return this.post("/circles/create", options);
632
+ }
633
+ async listCircles() {
634
+ return this.post("/circles/list", {});
635
+ }
636
+ async getCircle(circleId) {
637
+ return this.post("/circles/get", { circle_id: circleId });
638
+ }
639
+ async updateCircle(circleId, options) {
640
+ return this.post("/circles/update", { circle_id: circleId, ...options });
641
+ }
642
+ async deleteCircle(circleId) {
643
+ await this.post("/circles/delete", { circle_id: circleId });
644
+ }
645
+ async addCircleMember(circleId, options) {
646
+ await this.post("/circles/add-member", { circle_id: circleId, ...options });
647
+ }
648
+ async removeCircleMember(circleId, userId) {
649
+ await this.post("/circles/remove-member", { circle_id: circleId, user_id: userId });
650
+ }
651
+ async getCircleMembers(circleId) {
652
+ return this.post("/circles/get-members", { circle_id: circleId });
653
+ }
654
+ async getUserCircles(userId) {
655
+ return this.post("/circles/get-user-circles", { user_id: userId });
656
+ }
657
+ // ── Resources ──
658
+ async createResource(options) {
659
+ return this.post("/resources/create", options);
660
+ }
661
+ async listResources() {
662
+ return this.post("/resources/list", {});
663
+ }
664
+ async updateResource(resourceId, options) {
665
+ return this.post("/resources/update", { resource_id: resourceId, ...options });
666
+ }
667
+ async deleteResource(resourceId) {
668
+ await this.post("/resources/delete", { resource_id: resourceId });
669
+ }
670
+ // ── Relation types ──
671
+ async createRelationType(options) {
672
+ return this.post("/relation-types/create", options);
673
+ }
674
+ async listRelationTypes() {
675
+ return this.post("/relation-types/list", {});
676
+ }
677
+ async updateRelationType(id, options) {
678
+ return this.post("/relation-types/update", { id, ...options });
679
+ }
680
+ async deleteRelationType(id) {
681
+ await this.post("/relation-types/delete", { id });
682
+ }
683
+ // ── Profile ──
684
+ async getProfile(userId) {
685
+ return this.post("/profile/get", { user_id: userId });
686
+ }
687
+ async setProfile(userId, data) {
688
+ await this.post("/profile/set", { user_id: userId, data });
689
+ }
690
+ // ── Auth config ──
691
+ async getAuthConfig() {
692
+ return this.post("/config/get", {});
693
+ }
694
+ async setAuthConfig(config) {
695
+ await this.post("/config/set", config);
696
+ }
697
+ // ── Stewardship sub-namespace ──
698
+ stewardship = {
699
+ resolve: (userId) => this.post("/stewardship/resolve", { user_id: userId }),
700
+ createOverride: (options) => this.post("/stewardship/create-override", options),
701
+ revoke: async (id) => {
702
+ await this.post("/stewardship/revoke", { id });
703
+ },
704
+ checkPermission: async (stewardId, wardId, resource, action) => {
705
+ const r = await this.post("/stewardship/check-permission", {
706
+ steward_id: stewardId,
707
+ ward_id: wardId,
708
+ resource,
709
+ action
710
+ });
711
+ return Boolean(r?.allowed);
712
+ },
713
+ createActAs: (stewardId, wardId) => this.post("/stewardship/create-act-as", { steward_id: stewardId, ward_id: wardId }),
714
+ listAudit: (userId, options = {}) => this.post("/stewardship/list-audit", {
715
+ user_id: userId,
716
+ limit: options.limit ?? 50,
717
+ offset: options.offset ?? 0
718
+ })
719
+ };
517
720
  withAuth(handler) {
518
721
  const self = this;
519
722
  return async function(request) {
@@ -546,25 +749,66 @@ var RemoteAuthService = class {
546
749
  }
547
750
  // ── Request-scoped identity + authorization ──
548
751
  //
549
- // These APIs operate on per-request state inside the platform runtime and
550
- // don't have a remote equivalent. Throw so callers get a clear message
551
- // instead of silently wrong behavior.
552
- setCurrentUser(_token) {
553
- return Promise.reject(new Error(
554
- "platform.auth.setCurrentUser is only available inside the Maravilla runtime. Remote clients should pass the Authorization header with each request instead."
555
- ));
752
+ // In dev mode the SDK uses Node's AsyncLocalStorage (via
753
+ // request-scope.ts) to track per-request state tenants must wrap
754
+ // their inbound request handler with `runWithRequest(...)`. Without
755
+ // that wrapper, set/get/can fall back to clear errors / anonymous
756
+ // results so the misuse is obvious rather than silent.
757
+ async setCurrentUser(token) {
758
+ const { getCurrentRequestStore: getCurrentRequestStore2 } = await Promise.resolve().then(() => (init_request_scope(), request_scope_exports));
759
+ const store = getCurrentRequestStore2();
760
+ if (!store) {
761
+ return;
762
+ }
763
+ if (!token) {
764
+ store.token = void 0;
765
+ store.user = void 0;
766
+ return;
767
+ }
768
+ store.token = token;
769
+ store.user = await this.validate(token);
556
770
  }
557
771
  getCurrentUser() {
558
- throw new Error(
559
- "platform.auth.getCurrentUser is only available inside the Maravilla runtime. Remote clients have no per-request caller context."
560
- );
772
+ const mod = _RemoteAuthService._requestScope;
773
+ const store = mod?.getCurrentRequestStore?.();
774
+ const user = store?.user;
775
+ if (!user) {
776
+ return mod?.anonymousCaller?.() ?? {
777
+ user_id: "",
778
+ email: "",
779
+ is_admin: false,
780
+ roles: [],
781
+ is_anonymous: true
782
+ };
783
+ }
784
+ return {
785
+ user_id: user.id,
786
+ email: user.email,
787
+ is_admin: false,
788
+ roles: user.groups ?? [],
789
+ is_anonymous: false
790
+ };
561
791
  }
562
- can(_action, _resourceId, _node) {
563
- return Promise.reject(new Error(
564
- "platform.auth.can is only available inside the Maravilla runtime. Remote clients cannot evaluate per-request policies because there is no bound caller."
565
- ));
792
+ async can(action, resourceId, node) {
793
+ const { getCurrentRequestStore: getCurrentRequestStore2 } = await Promise.resolve().then(() => (init_request_scope(), request_scope_exports));
794
+ const store = getCurrentRequestStore2();
795
+ const token = store?.token;
796
+ if (!token) {
797
+ return false;
798
+ }
799
+ const r = await this.post("/can", {
800
+ token,
801
+ action,
802
+ resource_id: resourceId,
803
+ node: node ?? null
804
+ });
805
+ return Boolean(r?.allowed);
566
806
  }
567
807
  };
808
+ Promise.resolve().then(() => (init_request_scope(), request_scope_exports)).then((mod) => {
809
+ RemoteAuthService._requestScope = mod;
810
+ }).catch(() => {
811
+ });
568
812
  var RemotePolicyService = class {
569
813
  setEnabled(_enabled) {
570
814
  throw new Error(
@@ -587,7 +831,7 @@ var RemoteWorkflows = class {
587
831
  async request(path, options = {}) {
588
832
  const response = await fetch(`${this.baseUrl}${path}`, {
589
833
  ...options,
590
- headers: { ...this.headers, ...options.headers }
834
+ headers: { ...this.headers, ...getRequestAuthHeader(), ...options.headers }
591
835
  });
592
836
  if (!response.ok && response.status !== 404) {
593
837
  const error = await response.text();
@@ -1598,6 +1842,7 @@ var transforms = {
1598
1842
  };
1599
1843
 
1600
1844
  // src/index.ts
1845
+ init_request_scope();
1601
1846
  var cachedPlatform = void 0;
1602
1847
  function getPlatform(options) {
1603
1848
  if (cachedPlatform) {
@@ -1640,12 +1885,14 @@ export {
1640
1885
  attachTrack,
1641
1886
  clearPlatformCache,
1642
1887
  detachTrack,
1888
+ getCurrentRequestStore,
1643
1889
  getOrCreateClientId,
1644
1890
  getPlatform,
1645
1891
  keyFor,
1646
1892
  offsetBefore,
1647
1893
  registerPush,
1648
1894
  renFetch,
1895
+ runWithRequest,
1649
1896
  storageDelete,
1650
1897
  storageUpload,
1651
1898
  transforms,