@sprinterai/supabase 0.2.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.
Files changed (170) hide show
  1. package/LICENSE +21 -0
  2. package/dist/__test-utils__/mock-supabase.d.ts +18 -0
  3. package/dist/__test-utils__/mock-supabase.d.ts.map +1 -0
  4. package/dist/__test-utils__/mock-supabase.js +89 -0
  5. package/dist/__test-utils__/mock-supabase.js.map +1 -0
  6. package/dist/auth/adapter.d.ts +41 -0
  7. package/dist/auth/adapter.d.ts.map +1 -0
  8. package/dist/auth/adapter.js +73 -0
  9. package/dist/auth/adapter.js.map +1 -0
  10. package/dist/auth/claims.d.ts +31 -0
  11. package/dist/auth/claims.d.ts.map +1 -0
  12. package/dist/auth/claims.js +31 -0
  13. package/dist/auth/claims.js.map +1 -0
  14. package/dist/auth/claims.test.d.ts +2 -0
  15. package/dist/auth/claims.test.d.ts.map +1 -0
  16. package/dist/auth/claims.test.js +32 -0
  17. package/dist/auth/claims.test.js.map +1 -0
  18. package/dist/auth/index.d.ts +5 -0
  19. package/dist/auth/index.d.ts.map +1 -0
  20. package/dist/auth/index.js +4 -0
  21. package/dist/auth/index.js.map +1 -0
  22. package/dist/auth/permissions.d.ts +14 -0
  23. package/dist/auth/permissions.d.ts.map +1 -0
  24. package/dist/auth/permissions.js +25 -0
  25. package/dist/auth/permissions.js.map +1 -0
  26. package/dist/auth/permissions.test.d.ts +2 -0
  27. package/dist/auth/permissions.test.d.ts.map +1 -0
  28. package/dist/auth/permissions.test.js +40 -0
  29. package/dist/auth/permissions.test.js.map +1 -0
  30. package/dist/clients/admin.d.ts +9 -0
  31. package/dist/clients/admin.d.ts.map +1 -0
  32. package/dist/clients/admin.js +14 -0
  33. package/dist/clients/admin.js.map +1 -0
  34. package/dist/clients/admin.test.d.ts +2 -0
  35. package/dist/clients/admin.test.d.ts.map +1 -0
  36. package/dist/clients/admin.test.js +31 -0
  37. package/dist/clients/admin.test.js.map +1 -0
  38. package/dist/clients/browser.d.ts +9 -0
  39. package/dist/clients/browser.d.ts.map +1 -0
  40. package/dist/clients/browser.js +14 -0
  41. package/dist/clients/browser.js.map +1 -0
  42. package/dist/clients/browser.test.d.ts +2 -0
  43. package/dist/clients/browser.test.d.ts.map +1 -0
  44. package/dist/clients/browser.test.js +29 -0
  45. package/dist/clients/browser.test.js.map +1 -0
  46. package/dist/clients/index.d.ts +4 -0
  47. package/dist/clients/index.d.ts.map +1 -0
  48. package/dist/clients/index.js +4 -0
  49. package/dist/clients/index.js.map +1 -0
  50. package/dist/clients/server.d.ts +22 -0
  51. package/dist/clients/server.d.ts.map +1 -0
  52. package/dist/clients/server.js +25 -0
  53. package/dist/clients/server.js.map +1 -0
  54. package/dist/clients/server.test.d.ts +2 -0
  55. package/dist/clients/server.test.d.ts.map +1 -0
  56. package/dist/clients/server.test.js +26 -0
  57. package/dist/clients/server.test.js.map +1 -0
  58. package/dist/index.d.ts +11 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +11 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/realtime/channel.d.ts +24 -0
  63. package/dist/realtime/channel.d.ts.map +1 -0
  64. package/dist/realtime/channel.js +20 -0
  65. package/dist/realtime/channel.js.map +1 -0
  66. package/dist/realtime/index.d.ts +3 -0
  67. package/dist/realtime/index.d.ts.map +1 -0
  68. package/dist/realtime/index.js +2 -0
  69. package/dist/realtime/index.js.map +1 -0
  70. package/dist/stores/agent-store.d.ts +14 -0
  71. package/dist/stores/agent-store.d.ts.map +1 -0
  72. package/dist/stores/agent-store.js +47 -0
  73. package/dist/stores/agent-store.js.map +1 -0
  74. package/dist/stores/agent-store.test.d.ts +2 -0
  75. package/dist/stores/agent-store.test.d.ts.map +1 -0
  76. package/dist/stores/agent-store.test.js +64 -0
  77. package/dist/stores/agent-store.test.js.map +1 -0
  78. package/dist/stores/chat-store.d.ts +25 -0
  79. package/dist/stores/chat-store.d.ts.map +1 -0
  80. package/dist/stores/chat-store.js +148 -0
  81. package/dist/stores/chat-store.js.map +1 -0
  82. package/dist/stores/chat-store.test.d.ts +2 -0
  83. package/dist/stores/chat-store.test.d.ts.map +1 -0
  84. package/dist/stores/chat-store.test.js +83 -0
  85. package/dist/stores/chat-store.test.js.map +1 -0
  86. package/dist/stores/entity-store.d.ts +21 -0
  87. package/dist/stores/entity-store.d.ts.map +1 -0
  88. package/dist/stores/entity-store.js +169 -0
  89. package/dist/stores/entity-store.js.map +1 -0
  90. package/dist/stores/entity-store.test.d.ts +2 -0
  91. package/dist/stores/entity-store.test.d.ts.map +1 -0
  92. package/dist/stores/entity-store.test.js +125 -0
  93. package/dist/stores/entity-store.test.js.map +1 -0
  94. package/dist/stores/factory.d.ts +19 -0
  95. package/dist/stores/factory.d.ts.map +1 -0
  96. package/dist/stores/factory.js +25 -0
  97. package/dist/stores/factory.js.map +1 -0
  98. package/dist/stores/factory.test.d.ts +2 -0
  99. package/dist/stores/factory.test.d.ts.map +1 -0
  100. package/dist/stores/factory.test.js +41 -0
  101. package/dist/stores/factory.test.js.map +1 -0
  102. package/dist/stores/index.d.ts +11 -0
  103. package/dist/stores/index.d.ts.map +1 -0
  104. package/dist/stores/index.js +10 -0
  105. package/dist/stores/index.js.map +1 -0
  106. package/dist/stores/memory-store.d.ts +22 -0
  107. package/dist/stores/memory-store.d.ts.map +1 -0
  108. package/dist/stores/memory-store.js +59 -0
  109. package/dist/stores/memory-store.js.map +1 -0
  110. package/dist/stores/memory-store.test.d.ts +2 -0
  111. package/dist/stores/memory-store.test.d.ts.map +1 -0
  112. package/dist/stores/memory-store.test.js +70 -0
  113. package/dist/stores/memory-store.test.js.map +1 -0
  114. package/dist/stores/task-store.d.ts +17 -0
  115. package/dist/stores/task-store.d.ts.map +1 -0
  116. package/dist/stores/task-store.js +69 -0
  117. package/dist/stores/task-store.js.map +1 -0
  118. package/dist/stores/task-store.test.d.ts +2 -0
  119. package/dist/stores/task-store.test.d.ts.map +1 -0
  120. package/dist/stores/task-store.test.js +86 -0
  121. package/dist/stores/task-store.test.js.map +1 -0
  122. package/dist/stores/tenant-store.d.ts +11 -0
  123. package/dist/stores/tenant-store.d.ts.map +1 -0
  124. package/dist/stores/tenant-store.js +23 -0
  125. package/dist/stores/tenant-store.js.map +1 -0
  126. package/dist/stores/tool-store.d.ts +16 -0
  127. package/dist/stores/tool-store.d.ts.map +1 -0
  128. package/dist/stores/tool-store.js +50 -0
  129. package/dist/stores/tool-store.js.map +1 -0
  130. package/dist/stores/tool-store.test.d.ts +2 -0
  131. package/dist/stores/tool-store.test.d.ts.map +1 -0
  132. package/dist/stores/tool-store.test.js +56 -0
  133. package/dist/stores/tool-store.test.js.map +1 -0
  134. package/dist/stores/view-store.d.ts +14 -0
  135. package/dist/stores/view-store.d.ts.map +1 -0
  136. package/dist/stores/view-store.js +51 -0
  137. package/dist/stores/view-store.js.map +1 -0
  138. package/dist/stores/view-store.test.d.ts +2 -0
  139. package/dist/stores/view-store.test.d.ts.map +1 -0
  140. package/dist/stores/view-store.test.js +57 -0
  141. package/dist/stores/view-store.test.js.map +1 -0
  142. package/dist/tenant/actions.d.ts +44 -0
  143. package/dist/tenant/actions.d.ts.map +1 -0
  144. package/dist/tenant/actions.js +137 -0
  145. package/dist/tenant/actions.js.map +1 -0
  146. package/dist/tenant/constants.d.ts +9 -0
  147. package/dist/tenant/constants.d.ts.map +1 -0
  148. package/dist/tenant/constants.js +13 -0
  149. package/dist/tenant/constants.js.map +1 -0
  150. package/dist/tenant/constants.test.d.ts +2 -0
  151. package/dist/tenant/constants.test.d.ts.map +1 -0
  152. package/dist/tenant/constants.test.js +25 -0
  153. package/dist/tenant/constants.test.js.map +1 -0
  154. package/dist/tenant/context.d.ts +20 -0
  155. package/dist/tenant/context.d.ts.map +1 -0
  156. package/dist/tenant/context.js +99 -0
  157. package/dist/tenant/context.js.map +1 -0
  158. package/dist/tenant/index.d.ts +6 -0
  159. package/dist/tenant/index.d.ts.map +1 -0
  160. package/dist/tenant/index.js +5 -0
  161. package/dist/tenant/index.js.map +1 -0
  162. package/dist/tenant/roles.d.ts +7 -0
  163. package/dist/tenant/roles.d.ts.map +1 -0
  164. package/dist/tenant/roles.js +6 -0
  165. package/dist/tenant/roles.js.map +1 -0
  166. package/dist/types/index.d.ts +8 -0
  167. package/dist/types/index.d.ts.map +1 -0
  168. package/dist/types/index.js +6 -0
  169. package/dist/types/index.js.map +1 -0
  170. package/package.json +72 -0
@@ -0,0 +1,57 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { SupabaseViewStore } from "./view-store";
3
+ import { createMockClient } from "../__test-utils__/mock-supabase";
4
+ describe("SupabaseViewStore", () => {
5
+ describe("getView", () => {
6
+ it("returns view by slug", async () => {
7
+ const client = createMockClient();
8
+ const view = {
9
+ id: "v-1",
10
+ slug: "default-list",
11
+ name: "Default List",
12
+ layout: "list",
13
+ };
14
+ client.mockTable("views", { data: view, error: null });
15
+ const store = new SupabaseViewStore(client);
16
+ const result = await store.getView("default-list");
17
+ expect(result).toEqual(view);
18
+ });
19
+ it("returns null when view not found", async () => {
20
+ const client = createMockClient();
21
+ client.mockTable("views", { data: null, error: null });
22
+ const store = new SupabaseViewStore(client);
23
+ const result = await store.getView("nonexistent");
24
+ expect(result).toBeNull();
25
+ });
26
+ });
27
+ describe("getDefaultView", () => {
28
+ it("returns default view for entity type", async () => {
29
+ const client = createMockClient();
30
+ const view = {
31
+ id: "v-1",
32
+ slug: "opp-detail",
33
+ entity_type_slug: "opportunity",
34
+ is_default: true,
35
+ layout: "detail",
36
+ };
37
+ client.mockTable("views", { data: view, error: null });
38
+ const store = new SupabaseViewStore(client);
39
+ const result = await store.getDefaultView("opportunity", "detail");
40
+ expect(result).toEqual(view);
41
+ });
42
+ });
43
+ describe("listViews", () => {
44
+ it("returns all views", async () => {
45
+ const client = createMockClient();
46
+ const views = [
47
+ { id: "v-1", slug: "list", layout: "list" },
48
+ { id: "v-2", slug: "detail", layout: "detail" },
49
+ ];
50
+ client.mockTable("views", { data: views, error: null });
51
+ const store = new SupabaseViewStore(client);
52
+ const result = await store.listViews();
53
+ expect(result).toHaveLength(2);
54
+ });
55
+ });
56
+ });
57
+ //# sourceMappingURL=view-store.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view-store.test.js","sourceRoot":"","sources":["../../src/stores/view-store.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import type { TenantRecord } from '@sprinterai/core';
2
+ import type { SupabaseClient } from '@supabase/supabase-js';
3
+ /**
4
+ * Switch the active tenant for a user.
5
+ */
6
+ export declare function switchTenant(client: SupabaseClient, userId: string, tenantId: string): Promise<void>;
7
+ /**
8
+ * Create a new tenant and make the specified user the admin.
9
+ */
10
+ export declare function createTenant(adminClient: SupabaseClient, userId: string, input: {
11
+ name: string;
12
+ slug: string;
13
+ }): Promise<TenantRecord>;
14
+ /**
15
+ * Add a member to a tenant by email.
16
+ */
17
+ export declare function addTenantMember(adminClient: SupabaseClient, input: {
18
+ tenantId: string;
19
+ email: string;
20
+ role: 'admin' | 'member' | 'viewer' | 'guest';
21
+ }): Promise<void>;
22
+ /**
23
+ * Remove a member from a tenant.
24
+ */
25
+ export declare function removeTenantMember(adminClient: SupabaseClient, tenantId: string, userId: string): Promise<void>;
26
+ /**
27
+ * Get members of a tenant.
28
+ */
29
+ export declare function getTenantMembers(adminClient: SupabaseClient, tenantId: string): Promise<Array<{
30
+ userId: string;
31
+ email: string;
32
+ displayName: string;
33
+ role: string;
34
+ roleSlug: string;
35
+ }>>;
36
+ /**
37
+ * Ensure a user has a profile and default tenant membership.
38
+ * Call on login/callback for pre-migration users.
39
+ */
40
+ export declare function ensureUserProvisioned(adminClient: SupabaseClient, user: {
41
+ id: string;
42
+ email?: string;
43
+ }): Promise<void>;
44
+ //# sourceMappingURL=actions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/tenant/actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAiB5D;;GAEG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAYf;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,WAAW,EAAE,cAAc,EAC3B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACpC,OAAO,CAAC,YAAY,CAAC,CA+BvB;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,WAAW,EAAE,cAAc,EAC3B,KAAK,EAAE;IACL,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;CAC/C,GACA,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,cAAc,EAC3B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,cAAc,EAC3B,QAAQ,EAAE,MAAM,GACf,OAAO,CACR,KAAK,CAAC;IACJ,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CACH,CAyBA;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,WAAW,EAAE,cAAc,EAC3B,IAAI,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACnC,OAAO,CAAC,IAAI,CAAC,CA4Bf"}
@@ -0,0 +1,137 @@
1
+ import { APP_TO_DB_ROLE, ROLE_IDS, mapRoleSlug } from '@sprinterai/core';
2
+ import { DEFAULT_TENANT_ID } from './constants';
3
+ /**
4
+ * Look up a role ID by DB slug. Uses admin client to bypass RLS.
5
+ */
6
+ async function getRoleId(adminClient, dbSlug) {
7
+ const { data } = await adminClient
8
+ .from('roles')
9
+ .select('id')
10
+ .eq('slug', dbSlug)
11
+ .limit(1)
12
+ .maybeSingle();
13
+ return data?.id ?? ROLE_IDS.member;
14
+ }
15
+ /**
16
+ * Switch the active tenant for a user.
17
+ */
18
+ export async function switchTenant(client, userId, tenantId) {
19
+ // Verify membership
20
+ const { data: membership } = await client
21
+ .from('user_tenants')
22
+ .select('id')
23
+ .eq('user_id', userId)
24
+ .eq('tenant_id', tenantId)
25
+ .maybeSingle();
26
+ if (!membership)
27
+ throw new Error('Not a member of this tenant');
28
+ await client.from('profiles').update({ active_tenant_id: tenantId }).eq('id', userId);
29
+ }
30
+ /**
31
+ * Create a new tenant and make the specified user the admin.
32
+ */
33
+ export async function createTenant(adminClient, userId, input) {
34
+ const [tenantResult, adminRoleId] = await Promise.all([
35
+ adminClient
36
+ .from('tenants')
37
+ .insert({
38
+ id: crypto.randomUUID(),
39
+ name: input.name,
40
+ slug: input.slug,
41
+ tenant_type: 'team',
42
+ })
43
+ .select('*')
44
+ .single(),
45
+ getRoleId(adminClient, 'tenant_admin'),
46
+ ]);
47
+ if (tenantResult.error) {
48
+ throw new Error(`Failed to create tenant: ${tenantResult.error.message}`);
49
+ }
50
+ const tenant = tenantResult.data;
51
+ // Add creator as admin
52
+ await adminClient.from('user_tenants').insert({
53
+ tenant_id: tenant.id,
54
+ user_id: userId,
55
+ role_id: adminRoleId,
56
+ });
57
+ // Switch to new tenant
58
+ await adminClient.from('profiles').update({ active_tenant_id: tenant.id }).eq('id', userId);
59
+ return tenant;
60
+ }
61
+ /**
62
+ * Add a member to a tenant by email.
63
+ */
64
+ export async function addTenantMember(adminClient, input) {
65
+ const dbSlug = APP_TO_DB_ROLE[input.role] ?? 'member';
66
+ const [profileResult, roleId] = await Promise.all([
67
+ adminClient.from('profiles').select('id').eq('email', input.email).maybeSingle(),
68
+ getRoleId(adminClient, dbSlug),
69
+ ]);
70
+ if (!profileResult.data) {
71
+ throw new Error(`No user found with email: ${input.email}`);
72
+ }
73
+ const { error } = await adminClient.from('user_tenants').insert({
74
+ tenant_id: input.tenantId,
75
+ user_id: profileResult.data.id,
76
+ role_id: roleId,
77
+ });
78
+ if (error) {
79
+ if (error.code === '23505')
80
+ throw new Error('User is already a member');
81
+ throw new Error(`Failed to add member: ${error.message}`);
82
+ }
83
+ }
84
+ /**
85
+ * Remove a member from a tenant.
86
+ */
87
+ export async function removeTenantMember(adminClient, tenantId, userId) {
88
+ await adminClient.from('user_tenants').delete().eq('tenant_id', tenantId).eq('user_id', userId);
89
+ }
90
+ /**
91
+ * Get members of a tenant.
92
+ */
93
+ export async function getTenantMembers(adminClient, tenantId) {
94
+ const { data } = await adminClient
95
+ .from('user_tenants')
96
+ .select('user_id, roles(slug, name), profiles(email, display_name)')
97
+ .eq('tenant_id', tenantId)
98
+ .order('created_at');
99
+ if (!data)
100
+ return [];
101
+ return data.map((m) => {
102
+ const row = m;
103
+ const profile = row.profiles;
104
+ const role = row.roles;
105
+ const roleSlug = role?.slug ?? 'member';
106
+ return {
107
+ userId: row.user_id ?? '',
108
+ email: profile?.email ?? '',
109
+ displayName: profile?.display_name ?? '',
110
+ role: mapRoleSlug(roleSlug),
111
+ roleSlug,
112
+ };
113
+ });
114
+ }
115
+ /**
116
+ * Ensure a user has a profile and default tenant membership.
117
+ * Call on login/callback for pre-migration users.
118
+ */
119
+ export async function ensureUserProvisioned(adminClient, user) {
120
+ const profileUpsert = await adminClient.from('profiles').upsert({
121
+ id: user.id,
122
+ email: user.email,
123
+ display_name: user.email?.split('@')[0] ?? 'User',
124
+ }, { onConflict: 'id' });
125
+ if (profileUpsert.error) {
126
+ throw new Error(`Failed to provision profile for user "${user.id}": ${profileUpsert.error.message}`);
127
+ }
128
+ const membershipUpsert = await adminClient.from('user_tenants').upsert({
129
+ tenant_id: DEFAULT_TENANT_ID,
130
+ user_id: user.id,
131
+ role_id: ROLE_IDS.guest,
132
+ }, { onConflict: 'user_id,tenant_id' });
133
+ if (membershipUpsert.error) {
134
+ throw new Error(`Failed to provision default tenant membership for user "${user.id}": ${membershipUpsert.error.message}`);
135
+ }
136
+ }
137
+ //# sourceMappingURL=actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/tenant/actions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export declare const DEFAULT_TENANT_ID = "00000000-0000-0000-0000-000000000000";
2
+ export declare const DEFAULT_TENANT_SLUG = "default";
3
+ /**
4
+ * Build a tenant-scoped URL path. The default tenant uses bare paths.
5
+ * Example: tenantUrl("my-team", "/dashboard") -> "/t/my-team/dashboard"
6
+ * Example: tenantUrl("default", "/dashboard") -> "/dashboard"
7
+ */
8
+ export declare function tenantUrl(tenantSlug: string, path: string): string;
9
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/tenant/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iBAAiB,yCAAyC,CAAC;AAExE,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAE7C;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAGlE"}
@@ -0,0 +1,13 @@
1
+ export const DEFAULT_TENANT_ID = "00000000-0000-0000-0000-000000000000";
2
+ export const DEFAULT_TENANT_SLUG = "default";
3
+ /**
4
+ * Build a tenant-scoped URL path. The default tenant uses bare paths.
5
+ * Example: tenantUrl("my-team", "/dashboard") -> "/t/my-team/dashboard"
6
+ * Example: tenantUrl("default", "/dashboard") -> "/dashboard"
7
+ */
8
+ export function tenantUrl(tenantSlug, path) {
9
+ if (!tenantSlug || tenantSlug === DEFAULT_TENANT_SLUG)
10
+ return path;
11
+ return `/t/${tenantSlug}${path}`;
12
+ }
13
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/tenant/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,iBAAiB,GAAG,sCAAsC,CAAC;AAExE,MAAM,CAAC,MAAM,mBAAmB,GAAG,SAAS,CAAC;AAE7C;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,UAAkB,EAAE,IAAY;IACxD,IAAI,CAAC,UAAU,IAAI,UAAU,KAAK,mBAAmB;QAAE,OAAO,IAAI,CAAC;IACnE,OAAO,MAAM,UAAU,GAAG,IAAI,EAAE,CAAC;AACnC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=constants.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.test.d.ts","sourceRoot":"","sources":["../../src/tenant/constants.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,25 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { DEFAULT_TENANT_ID, DEFAULT_TENANT_SLUG, tenantUrl } from "./constants";
3
+ describe("tenant constants", () => {
4
+ it("exports the default tenant ID", () => {
5
+ expect(DEFAULT_TENANT_ID).toBe("00000000-0000-0000-0000-000000000000");
6
+ });
7
+ it("exports the default tenant slug", () => {
8
+ expect(DEFAULT_TENANT_SLUG).toBe("default");
9
+ });
10
+ });
11
+ describe("tenantUrl", () => {
12
+ it("returns bare path for default tenant slug", () => {
13
+ expect(tenantUrl("default", "/dashboard")).toBe("/dashboard");
14
+ });
15
+ it("returns bare path for empty slug", () => {
16
+ expect(tenantUrl("", "/dashboard")).toBe("/dashboard");
17
+ });
18
+ it("returns tenant-scoped path for custom slug", () => {
19
+ expect(tenantUrl("my-team", "/dashboard")).toBe("/t/my-team/dashboard");
20
+ });
21
+ it("handles root path", () => {
22
+ expect(tenantUrl("acme", "/")).toBe("/t/acme/");
23
+ });
24
+ });
25
+ //# sourceMappingURL=constants.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.test.js","sourceRoot":"","sources":["../../src/tenant/constants.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEhF,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;IACzB,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,SAAS,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,CAAC,SAAS,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,CAAC,SAAS,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mBAAmB,EAAE,GAAG,EAAE;QAC3B,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { TenantContext } from '@sprinterai/core';
2
+ import type { SupabaseClient } from '@supabase/supabase-js';
3
+ /**
4
+ * Resolve the active tenant context for a user.
5
+ * This is the framework-agnostic version — no React.cache() or next/headers.
6
+ * Integrations (Next.js) should wrap this with their caching layer.
7
+ *
8
+ * @param client - Authenticated Supabase client (has user session)
9
+ * @param options.tenantSlugOverride - Optional slug from URL (/t/[slug]/...)
10
+ */
11
+ export declare function getTenantContext(client: SupabaseClient, options?: {
12
+ tenantSlugOverride?: string;
13
+ }): Promise<TenantContext>;
14
+ /**
15
+ * Convenience wrapper — returns just the active tenant ID.
16
+ */
17
+ export declare function getActiveTenantId(client: SupabaseClient, options?: {
18
+ tenantSlugOverride?: string;
19
+ }): Promise<string>;
20
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/tenant/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAI5D;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,cAAc,EACtB,OAAO,CAAC,EAAE;IACR,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,GACA,OAAO,CAAC,aAAa,CAAC,CAwGxB;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,cAAc,EACtB,OAAO,CAAC,EAAE;IAAE,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAAE,GACxC,OAAO,CAAC,MAAM,CAAC,CAGjB"}
@@ -0,0 +1,99 @@
1
+ import { mapRoleSlug } from '@sprinterai/core';
2
+ import { DEFAULT_TENANT_ID } from './constants';
3
+ /**
4
+ * Resolve the active tenant context for a user.
5
+ * This is the framework-agnostic version — no React.cache() or next/headers.
6
+ * Integrations (Next.js) should wrap this with their caching layer.
7
+ *
8
+ * @param client - Authenticated Supabase client (has user session)
9
+ * @param options.tenantSlugOverride - Optional slug from URL (/t/[slug]/...)
10
+ */
11
+ export async function getTenantContext(client, options) {
12
+ const { data: claimsData } = await client.auth.getClaims();
13
+ if (!claimsData) {
14
+ throw new Error('Not authenticated');
15
+ }
16
+ const { claims } = claimsData;
17
+ const userId = claims.sub;
18
+ const userEmail = claims.email ?? '';
19
+ // Try to get profile with active_tenant_id
20
+ const { data: profile } = await client
21
+ .from('profiles')
22
+ .select('active_tenant_id, display_name, email')
23
+ .eq('id', userId)
24
+ .maybeSingle();
25
+ // Check for tenant slug override from URL
26
+ let activeTenantId = profile?.active_tenant_id ?? DEFAULT_TENANT_ID;
27
+ const urlTenantSlug = options?.tenantSlugOverride;
28
+ if (urlTenantSlug) {
29
+ const { data: slugTenant } = await client
30
+ .from('tenants')
31
+ .select('id')
32
+ .eq('slug', urlTenantSlug)
33
+ .maybeSingle();
34
+ if (slugTenant)
35
+ activeTenantId = slugTenant.id;
36
+ }
37
+ // Get the tenant membership via user_tenants + roles
38
+ const { data: membership } = await client
39
+ .from('user_tenants')
40
+ .select('role_id, roles(slug, name), tenants(id, name, slug, logo_url)')
41
+ .eq('user_id', userId)
42
+ .eq('tenant_id', activeTenantId)
43
+ .maybeSingle();
44
+ const joined = membership;
45
+ if (joined?.tenants) {
46
+ const tenant = joined.tenants;
47
+ const roleSlug = joined.roles?.slug ?? 'member';
48
+ return {
49
+ tenantId: tenant.id,
50
+ tenantName: tenant.name ?? 'Untitled',
51
+ tenantSlug: tenant.slug,
52
+ tenantLogoUrl: tenant.logo_url ?? null,
53
+ userId,
54
+ userEmail: profile?.email ?? userEmail,
55
+ userDisplayName: profile?.display_name ?? userEmail.split('@')[0] ?? '',
56
+ role: mapRoleSlug(roleSlug),
57
+ };
58
+ }
59
+ // Fallback: try first membership
60
+ if (!urlTenantSlug) {
61
+ const { data: memberships } = await client
62
+ .from('user_tenants')
63
+ .select('roles(slug, name), tenants(id, name, slug, logo_url)')
64
+ .eq('user_id', userId)
65
+ .order('created_at');
66
+ const fallbackMemberships = (memberships ?? []).filter((row) => row.tenants);
67
+ const fallbackMembership = fallbackMemberships.find((row) => {
68
+ const tenant = row.tenants;
69
+ return tenant?.id === DEFAULT_TENANT_ID;
70
+ }) ?? fallbackMemberships[0];
71
+ if (fallbackMembership) {
72
+ const row = fallbackMembership;
73
+ const tenant = row.tenants;
74
+ const roleSlug = row.roles?.slug ?? 'member';
75
+ return {
76
+ tenantId: tenant.id,
77
+ tenantName: tenant.name ?? 'Untitled',
78
+ tenantSlug: tenant.slug,
79
+ tenantLogoUrl: tenant.logo_url ?? null,
80
+ userId,
81
+ userEmail: profile?.email ?? userEmail,
82
+ userDisplayName: profile?.display_name ?? userEmail.split('@')[0] ?? '',
83
+ role: mapRoleSlug(roleSlug),
84
+ };
85
+ }
86
+ }
87
+ const tenantDescriptor = urlTenantSlug
88
+ ? `tenant slug "${urlTenantSlug}"`
89
+ : `tenant "${activeTenantId}"`;
90
+ throw new Error(`Active tenant membership not found for ${tenantDescriptor}`);
91
+ }
92
+ /**
93
+ * Convenience wrapper — returns just the active tenant ID.
94
+ */
95
+ export async function getActiveTenantId(client, options) {
96
+ const ctx = await getTenantContext(client, options);
97
+ return ctx.tenantId;
98
+ }
99
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/tenant/context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export { getTenantContext, getActiveTenantId } from "./context";
2
+ export { DEFAULT_TENANT_ID, DEFAULT_TENANT_SLUG, tenantUrl } from "./constants";
3
+ export { ROLE_IDS, APP_TO_DB_ROLE, mapRoleSlug, isAdminRole } from "./roles";
4
+ export type { AppRole } from "./roles";
5
+ export { switchTenant, createTenant, addTenantMember, removeTenantMember, getTenantMembers, ensureUserProvisioned, } from "./actions";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tenant/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC7E,YAAY,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,WAAW,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { getTenantContext, getActiveTenantId } from "./context";
2
+ export { DEFAULT_TENANT_ID, DEFAULT_TENANT_SLUG, tenantUrl } from "./constants";
3
+ export { ROLE_IDS, APP_TO_DB_ROLE, mapRoleSlug, isAdminRole } from "./roles";
4
+ export { switchTenant, createTenant, addTenantMember, removeTenantMember, getTenantMembers, ensureUserProvisioned, } from "./actions";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tenant/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE7E,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,WAAW,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Re-export role utilities from @sprinterai/core.
3
+ * This keeps tenant code co-located while avoiding duplication.
4
+ */
5
+ export { ROLE_IDS, APP_TO_DB_ROLE, mapRoleSlug, isAdminRole, } from "@sprinterai/core";
6
+ export type { AppRole } from "@sprinterai/core";
7
+ //# sourceMappingURL=roles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roles.d.ts","sourceRoot":"","sources":["../../src/tenant/roles.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,QAAQ,EACR,cAAc,EACd,WAAW,EACX,WAAW,GACZ,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Re-export role utilities from @sprinterai/core.
3
+ * This keeps tenant code co-located while avoiding duplication.
4
+ */
5
+ export { ROLE_IDS, APP_TO_DB_ROLE, mapRoleSlug, isAdminRole, } from "@sprinterai/core";
6
+ //# sourceMappingURL=roles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roles.js","sourceRoot":"","sources":["../../src/tenant/roles.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,QAAQ,EACR,cAAc,EACd,WAAW,EACX,WAAW,GACZ,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Re-export database types. Consumers generate these per-project via `supabase gen types`.
3
+ * This module provides a minimal shape so that store code can reference table names.
4
+ */
5
+ import type { SupabaseClient } from "@supabase/supabase-js";
6
+ /** Alias for the Supabase client type used throughout the package. */
7
+ export type TypedSupabaseClient = SupabaseClient;
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,sEAAsE;AACtE,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Re-export database types. Consumers generate these per-project via `supabase gen types`.
3
+ * This module provides a minimal shape so that store code can reference table names.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@sprinterai/supabase",
3
+ "version": "0.2.0",
4
+ "description": "Supabase store implementations and auth adapter for the Sprinter AI platform",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ },
14
+ "./clients": {
15
+ "types": "./dist/clients/index.d.ts",
16
+ "import": "./dist/clients/index.js"
17
+ },
18
+ "./auth": {
19
+ "types": "./dist/auth/index.d.ts",
20
+ "import": "./dist/auth/index.js"
21
+ },
22
+ "./tenant": {
23
+ "types": "./dist/tenant/index.d.ts",
24
+ "import": "./dist/tenant/index.js"
25
+ },
26
+ "./stores": {
27
+ "types": "./dist/stores/index.d.ts",
28
+ "import": "./dist/stores/index.js"
29
+ }
30
+ },
31
+ "files": [
32
+ "dist"
33
+ ],
34
+ "dependencies": {
35
+ "@supabase/ssr": "^0.9.0",
36
+ "@supabase/supabase-js": "^2.49.0",
37
+ "@sprinterai/core": "0.2.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^25.5.0",
41
+ "@vitest/coverage-v8": "^3.0.0",
42
+ "eslint": "^9.0.0",
43
+ "typescript": "^5.7.0",
44
+ "vitest": "^3.0.0",
45
+ "@sprinterai/eslint-config": "0.1.0"
46
+ },
47
+ "publishConfig": {
48
+ "access": "restricted"
49
+ },
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "https://github.com/tylerdr/sprinter-platform.git",
53
+ "directory": "packages/supabase"
54
+ },
55
+ "keywords": [
56
+ "sprinter",
57
+ "ai",
58
+ "platform",
59
+ "venture-factory"
60
+ ],
61
+ "engines": {
62
+ "node": ">=20"
63
+ },
64
+ "scripts": {
65
+ "build": "tsc",
66
+ "lint": "eslint src/",
67
+ "test": "vitest run",
68
+ "test:coverage": "vitest run --coverage",
69
+ "typecheck": "tsc --noEmit",
70
+ "clean": "rm -rf dist"
71
+ }
72
+ }