@layers/amba-react 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # @layers/amba-react
2
+
3
+ React hooks for [amba](https://amba.dev). Companion to `@layers/amba-web`.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @layers/amba-react @layers/amba-web react
9
+ ```
10
+
11
+ ## Quickstart
12
+
13
+ ```tsx
14
+ import { Amba } from "@layers/amba-web";
15
+ import {
16
+ AmbaProvider,
17
+ useUser,
18
+ useCollection,
19
+ useFlag,
20
+ } from "@layers/amba-react";
21
+
22
+ // Configure at app start (before render)
23
+ await Amba.configure({ apiKey: import.meta.env.VITE_AMBA_API_KEY });
24
+
25
+ function App() {
26
+ return (
27
+ <AmbaProvider>
28
+ <Inner />
29
+ </AmbaProvider>
30
+ );
31
+ }
32
+
33
+ function Inner() {
34
+ const { user, isAuthenticated } = useUser();
35
+ const { data: todos, loading } = useCollection<{ id: string; title: string }>(
36
+ "todos",
37
+ );
38
+ const newUi = useFlag("new_ui");
39
+
40
+ if (!isAuthenticated) return <SignIn />;
41
+ if (loading) return <Spinner />;
42
+ return (
43
+ <div className={newUi ? "new-ui" : "classic"}>
44
+ <p>Welcome {user?.display_name}</p>
45
+ <ul>
46
+ {todos?.map((t) => (
47
+ <li key={t.id}>{t.title}</li>
48
+ ))}
49
+ </ul>
50
+ </div>
51
+ );
52
+ }
53
+ ```
54
+
55
+ ## Hooks
56
+
57
+ | Hook | Returns |
58
+ | ------------------------------- | -------------------------------------------------------- |
59
+ | `useAmba()` | Raw SDK handle. |
60
+ | `useUser()` | `{ user, isAuthenticated, loading }`. |
61
+ | `useCollection(name, options)` | `{ data, loading, error, refetch, response }`. |
62
+ | `useFlag(name)` | `boolean`. |
63
+ | `useVariant(name)` | `string \| null` (multi-variant flag assignment). |
64
+ | `useEntitlement(name)` | `boolean` — true if user has the entitlement. |
65
+ | `useTrackOnMount(event, props)` | Side-effect — tracks an event when the component mounts. |
66
+
67
+ ## License
68
+
69
+ MIT.
@@ -0,0 +1,274 @@
1
+ import * as _layers_amba_web0 from "@layers/amba-web";
2
+ import { FindOptions, FindResponse, FlagAssignment, User } from "@layers/amba-web";
3
+ import * as react0 from "react";
4
+ import { ReactNode } from "react";
5
+
6
+ //#region src/index.d.ts
7
+
8
+ interface AmbaContextValue {
9
+ /**
10
+ * Bump this counter to force a re-render of hooks that read SDK state
11
+ * (`useUser`, `useIsAuthenticated`, …). Bumped on every auth state
12
+ * change (sign-in / sign-out / refresh).
13
+ */
14
+ version: number;
15
+ bumpVersion: () => void;
16
+ }
17
+ /**
18
+ * Wraps your app and provides reactive subscriptions to the amba SDK.
19
+ *
20
+ * Call `Amba.configure(...)` BEFORE rendering this provider. It does not
21
+ * configure the SDK itself — it just plugs the React tree into the SDK's
22
+ * state-change channel.
23
+ */
24
+ declare function AmbaProvider({
25
+ children
26
+ }: {
27
+ children: ReactNode;
28
+ }): react0.FunctionComponentElement<react0.ProviderProps<AmbaContextValue | null>>;
29
+ /**
30
+ * Returns the underlying SDK handle (`Amba`). Useful for one-off calls
31
+ * that don't have a dedicated hook.
32
+ */
33
+ declare function useAmba(): {
34
+ configure: (config: _layers_amba_web0.AmbaConfig) => Promise<void>;
35
+ readonly anonymousId: string;
36
+ readonly appUserId: string | undefined;
37
+ readonly isAuthenticated: boolean;
38
+ setDebug: (enabled: boolean) => void;
39
+ events: {
40
+ track: (event: string, properties?: Record<string, unknown>) => Promise<void>;
41
+ };
42
+ auth: {
43
+ signInAnonymously: () => Promise<_layers_amba_web0.AuthResult>;
44
+ signInWithEmail: (email: string, password: string) => Promise<_layers_amba_web0.AuthResult>;
45
+ signUpWithEmail: (email: string, password: string) => Promise<_layers_amba_web0.AuthResult>;
46
+ signInWithSocial: (provider: _layers_amba_web0.SocialProvider, idToken: string) => Promise<_layers_amba_web0.AuthResult>;
47
+ signOut: (rotateAnonymousId?: boolean) => Promise<void>;
48
+ refresh: () => Promise<_layers_amba_web0.AuthResult>;
49
+ me: () => Promise<User>;
50
+ getSession: () => Promise<_layers_amba_web0.Session | null>;
51
+ getAnonymousId: () => Promise<string>;
52
+ onAuthStateChange: (cb: (session: _layers_amba_web0.Session | null) => void) => (() => void);
53
+ };
54
+ collections: {
55
+ find: <T = unknown>(name: string, options?: FindOptions) => Promise<FindResponse<T>>;
56
+ findOne: <T = unknown>(name: string, id: string) => Promise<T>;
57
+ insert: <T = unknown>(name: string, row: Record<string, unknown>) => Promise<T>;
58
+ update: <T = unknown>(name: string, id: string, set: Record<string, unknown>) => Promise<T>;
59
+ delete: (name: string, id: string) => Promise<{
60
+ data: {
61
+ deleted: boolean;
62
+ };
63
+ }>;
64
+ where: {
65
+ eq: (column: string, value: _layers_amba_web0.FilterValue) => _layers_amba_web0.Filter;
66
+ ne: (column: string, value: _layers_amba_web0.FilterValue) => _layers_amba_web0.Filter;
67
+ gt: (column: string, value: _layers_amba_web0.FilterValue) => _layers_amba_web0.Filter;
68
+ gte: (column: string, value: _layers_amba_web0.FilterValue) => _layers_amba_web0.Filter;
69
+ lt: (column: string, value: _layers_amba_web0.FilterValue) => _layers_amba_web0.Filter;
70
+ lte: (column: string, value: _layers_amba_web0.FilterValue) => _layers_amba_web0.Filter;
71
+ in: (column: string, values: _layers_amba_web0.FilterValue[]) => _layers_amba_web0.Filter;
72
+ notIn: (column: string, values: _layers_amba_web0.FilterValue[]) => _layers_amba_web0.Filter;
73
+ like: (column: string, pattern: string) => _layers_amba_web0.Filter;
74
+ ilike: (column: string, pattern: string) => _layers_amba_web0.Filter;
75
+ isNull: (column: string) => _layers_amba_web0.Filter;
76
+ isNotNull: (column: string) => _layers_amba_web0.Filter;
77
+ and: (...filters: _layers_amba_web0.Filter[]) => _layers_amba_web0.Filter;
78
+ or: (...filters: _layers_amba_web0.Filter[]) => _layers_amba_web0.Filter;
79
+ not: (filter: _layers_amba_web0.Filter) => _layers_amba_web0.Filter;
80
+ };
81
+ };
82
+ storage: {
83
+ presign: (params: {
84
+ bucket: string;
85
+ filename: string;
86
+ mimeType: string;
87
+ sizeBytes: number;
88
+ retentionDays?: number;
89
+ }) => Promise<_layers_amba_web0.PresignData>;
90
+ upload: (params: {
91
+ bucket: string;
92
+ file: File | Blob;
93
+ filename?: string;
94
+ retentionDays?: number;
95
+ }) => Promise<_layers_amba_web0.MediaAsset>;
96
+ };
97
+ push: {
98
+ register: (token: string, platform: _layers_amba_web0.PushPlatform, bundleId?: string) => Promise<_layers_amba_web0.PushToken>;
99
+ unregister: (token: string) => Promise<void>;
100
+ getTokens: () => Promise<_layers_amba_web0.PushToken[]>;
101
+ subscribe: (topic: string) => Promise<void>;
102
+ unsubscribe: (topic: string) => Promise<void>;
103
+ };
104
+ entitlements: {
105
+ list: () => Promise<_layers_amba_web0.UserEntitlement[]>;
106
+ has: (name: string) => Promise<boolean>;
107
+ };
108
+ ai: {
109
+ anthropic: {
110
+ messages: {
111
+ create: (request: _layers_amba_web0.AiMessageRequest) => Promise<_layers_amba_web0.AiMessageResponse>;
112
+ };
113
+ };
114
+ };
115
+ config: {
116
+ fetch: () => Promise<_layers_amba_web0.ConfigBundle>;
117
+ };
118
+ flags: {
119
+ fetch: () => Promise<FlagAssignment[]>;
120
+ };
121
+ achievements: {
122
+ getAll: () => Promise<_layers_amba_web0.Achievement[]>;
123
+ getProgress: () => Promise<_layers_amba_web0.AchievementProgress[]>;
124
+ };
125
+ challenges: {
126
+ getActive: () => Promise<_layers_amba_web0.Challenge[]>;
127
+ get: (id: string) => Promise<_layers_amba_web0.Challenge>;
128
+ getProgress: (id: string) => Promise<_layers_amba_web0.ChallengeProgress>;
129
+ claim: (id: string) => Promise<_layers_amba_web0.ChallengeProgress>;
130
+ };
131
+ currencies: {
132
+ getBalance: () => Promise<_layers_amba_web0.CurrencyBalance[]>;
133
+ getTransactions: (currencyKey: string) => Promise<_layers_amba_web0.CurrencyTransaction[]>;
134
+ };
135
+ inventory: {
136
+ getItems: () => Promise<_layers_amba_web0.InventoryItem[]>;
137
+ getItem: (id: string) => Promise<_layers_amba_web0.InventoryItem>;
138
+ purchase: (request: _layers_amba_web0.PurchaseRequest) => Promise<_layers_amba_web0.InventoryItem>;
139
+ consume: (request: _layers_amba_web0.ConsumeRequest) => Promise<_layers_amba_web0.InventoryItem>;
140
+ };
141
+ leaderboards: {
142
+ get: (key: string) => Promise<_layers_amba_web0.Leaderboard>;
143
+ getEntries: (key: string, limit?: number) => Promise<_layers_amba_web0.LeaderboardEntry[]>;
144
+ getMyRank: (key: string) => Promise<_layers_amba_web0.LeaderboardEntry>;
145
+ };
146
+ stores: {
147
+ list: () => Promise<_layers_amba_web0.Store[]>;
148
+ getPurchaseOptions: (storeKey: string) => Promise<_layers_amba_web0.PurchaseOption[]>;
149
+ purchase: (storeKey: string, purchaseOptionId: string, receipt: unknown) => Promise<_layers_amba_web0.PurchaseResult>;
150
+ };
151
+ xp: {
152
+ getBalance: () => Promise<_layers_amba_web0.XpBalance>;
153
+ getHistory: (limit?: number) => Promise<_layers_amba_web0.XpTransaction[]>;
154
+ claim: (grantKey: string) => Promise<_layers_amba_web0.XpTransaction>;
155
+ };
156
+ streaks: {
157
+ getAll: () => Promise<_layers_amba_web0.Streak[]>;
158
+ qualify: (streakKey: string) => Promise<_layers_amba_web0.Streak>;
159
+ };
160
+ feeds: {
161
+ getActivity: (feed?: string, cursor?: string) => Promise<_layers_amba_web0.FeedResponse>;
162
+ };
163
+ friends: {
164
+ getList: () => Promise<_layers_amba_web0.Friendship[]>;
165
+ getFriends: () => Promise<_layers_amba_web0.Friendship[]>;
166
+ blockUser: (userId: string) => Promise<_layers_amba_web0.Friendship>;
167
+ unblockUser: (userId: string) => Promise<void>;
168
+ removeBlock: (friendshipId: string) => Promise<void>;
169
+ };
170
+ groups: {
171
+ create: (params: _layers_amba_web0.GroupCreate) => Promise<_layers_amba_web0.Group>;
172
+ get: (id: string) => Promise<_layers_amba_web0.Group>;
173
+ update: (id: string, patch: _layers_amba_web0.GroupUpdate) => Promise<_layers_amba_web0.Group>;
174
+ delete: (id: string) => Promise<void>;
175
+ getMembers: (id: string) => Promise<_layers_amba_web0.GroupMember[]>;
176
+ join: (id: string) => Promise<_layers_amba_web0.GroupMember>;
177
+ leave: (id: string) => Promise<void>;
178
+ invite: (id: string, userId: string) => Promise<_layers_amba_web0.GroupMember>;
179
+ };
180
+ messaging: {
181
+ getConversations: () => Promise<_layers_amba_web0.Conversation[]>;
182
+ getMessage: (id: string) => Promise<_layers_amba_web0.Message>;
183
+ sendMessage: (request: _layers_amba_web0.SendMessageRequest) => Promise<_layers_amba_web0.Message>;
184
+ };
185
+ moderation: {
186
+ reportUser: (request: _layers_amba_web0.ReportRequest) => Promise<_layers_amba_web0.Report>;
187
+ reportContent: (request: _layers_amba_web0.ReportRequest) => Promise<_layers_amba_web0.Report>;
188
+ getReportStatus: (id: string) => Promise<_layers_amba_web0.Report>;
189
+ };
190
+ reviews: {
191
+ list: (targetType: string, targetId: string) => Promise<_layers_amba_web0.Review[]>;
192
+ create: (params: _layers_amba_web0.ReviewCreate) => Promise<_layers_amba_web0.Review>;
193
+ update: (id: string, patch: _layers_amba_web0.ReviewUpdate) => Promise<_layers_amba_web0.Review>;
194
+ delete: (id: string) => Promise<void>;
195
+ };
196
+ roles: {
197
+ getMyRoles: () => Promise<_layers_amba_web0.Role[]>;
198
+ hasPermission: (permission: string) => Promise<boolean>;
199
+ };
200
+ referrals: {
201
+ getReferralCode: () => Promise<_layers_amba_web0.ReferralCode>;
202
+ claimReferral: (code: string) => Promise<_layers_amba_web0.ReferralClaim>;
203
+ create: (code?: string, maxUses?: number) => Promise<_layers_amba_web0.ReferralCode>;
204
+ };
205
+ catalog: {
206
+ list: () => Promise<_layers_amba_web0.CatalogItem[]>;
207
+ };
208
+ content: {
209
+ getToday: (channel?: string) => Promise<_layers_amba_web0.ContentItem | null>;
210
+ getLibrary: (channel?: string, options?: {
211
+ limit?: number;
212
+ cursor?: string;
213
+ }) => Promise<_layers_amba_web0.ContentItem[]>;
214
+ getItem: (id: string) => Promise<_layers_amba_web0.ContentItem>;
215
+ updateItem: (id: string, state: unknown) => Promise<_layers_amba_web0.ContentItem>;
216
+ createItem: (channel: string, item: unknown) => Promise<_layers_amba_web0.ContentItem>;
217
+ };
218
+ deepLinks: {
219
+ get: (shortCode: string) => Promise<_layers_amba_web0.DeepLink>;
220
+ create: (params: _layers_amba_web0.DeepLinkCreate) => Promise<_layers_amba_web0.DeepLink>;
221
+ };
222
+ onboarding: {
223
+ getStatus: () => Promise<_layers_amba_web0.OnboardingStatus>;
224
+ nextStep: (payload: unknown) => Promise<_layers_amba_web0.OnboardingStatus>;
225
+ skipStep: () => Promise<_layers_amba_web0.OnboardingStatus>;
226
+ complete: () => Promise<_layers_amba_web0.OnboardingStatus>;
227
+ };
228
+ };
229
+ /**
230
+ * Returns `{ user, isAuthenticated }` for the currently-signed-in user.
231
+ * `user` is `null` until `auth.me()` resolves; the hook auto-fetches when
232
+ * the SDK is authenticated and refreshes on auth-state changes.
233
+ */
234
+ declare function useUser(): {
235
+ user: User | null;
236
+ isAuthenticated: boolean;
237
+ loading: boolean;
238
+ };
239
+ /**
240
+ * Reactive subscription to a collection. Re-fetches when `options` or
241
+ * `version` changes. Includes a `refetch` callback for manual refresh.
242
+ */
243
+ declare function useCollection<T = unknown>(name: string, options?: FindOptions): {
244
+ data: T[] | null;
245
+ loading: boolean;
246
+ error: Error | null;
247
+ refetch: () => Promise<void>;
248
+ response: FindResponse<T> | null;
249
+ };
250
+ /**
251
+ * Returns true if the named feature flag is enabled.
252
+ * Fetches the flag set once on mount; subsequent calls read from cache.
253
+ */
254
+ declare function useFlag(name: string): boolean;
255
+ /**
256
+ * Returns the variant assignment for a multi-variant flag, or `null`
257
+ * if unset / not loaded.
258
+ */
259
+ declare function useVariant(name: string): string | null;
260
+ /**
261
+ * Returns true if the user has the named entitlement (active subscription
262
+ * or one-time purchase grant). Defaults to false on loading / error —
263
+ * safe for paywall gating.
264
+ */
265
+ declare function useEntitlement(name: string): boolean;
266
+ /**
267
+ * Tracks an event when this hook mounts. Useful for page-view tracking:
268
+ * `useTrackOnMount('page_viewed', { path: '/home' })`.
269
+ */
270
+ declare function useTrackOnMount(event: string, properties?: Record<string, unknown>): void;
271
+ declare const SDK_VERSION = "0.1.0";
272
+ //#endregion
273
+ export { AmbaProvider, SDK_VERSION, useAmba, useCollection, useEntitlement, useFlag, useTrackOnMount, useUser, useVariant };
274
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,177 @@
1
+ import { Amba } from "@layers/amba-web";
2
+ import { createContext, createElement, useCallback, useContext, useEffect, useState } from "react";
3
+
4
+ //#region src/index.ts
5
+ const AmbaContext = createContext(null);
6
+ /**
7
+ * Wraps your app and provides reactive subscriptions to the amba SDK.
8
+ *
9
+ * Call `Amba.configure(...)` BEFORE rendering this provider. It does not
10
+ * configure the SDK itself — it just plugs the React tree into the SDK's
11
+ * state-change channel.
12
+ */
13
+ function AmbaProvider({ children }) {
14
+ const [version, setVersion] = useState(0);
15
+ const bumpVersion = useCallback(() => setVersion((v) => v + 1), []);
16
+ return createElement(AmbaContext.Provider, { value: {
17
+ version,
18
+ bumpVersion
19
+ } }, children);
20
+ }
21
+ function useAmbaContext() {
22
+ const ctx = useContext(AmbaContext);
23
+ if (!ctx) throw new Error("@layers/amba-react: hooks must be used inside <AmbaProvider>. Wrap your app with <AmbaProvider> after calling Amba.configure()");
24
+ return ctx;
25
+ }
26
+ /**
27
+ * Returns the underlying SDK handle (`Amba`). Useful for one-off calls
28
+ * that don't have a dedicated hook.
29
+ */
30
+ function useAmba() {
31
+ useAmbaContext();
32
+ return Amba;
33
+ }
34
+ /**
35
+ * Returns `{ user, isAuthenticated }` for the currently-signed-in user.
36
+ * `user` is `null` until `auth.me()` resolves; the hook auto-fetches when
37
+ * the SDK is authenticated and refreshes on auth-state changes.
38
+ */
39
+ function useUser() {
40
+ const { version } = useAmbaContext();
41
+ const [user, setUser] = useState(null);
42
+ const [loading, setLoading] = useState(false);
43
+ useEffect(() => {
44
+ if (!Amba.isAuthenticated) {
45
+ setUser(null);
46
+ return;
47
+ }
48
+ let cancelled = false;
49
+ setLoading(true);
50
+ Amba.auth.me().then((u) => {
51
+ if (!cancelled) setUser(u);
52
+ }).catch(() => {
53
+ if (!cancelled) setUser(null);
54
+ }).finally(() => {
55
+ if (!cancelled) setLoading(false);
56
+ });
57
+ return () => {
58
+ cancelled = true;
59
+ };
60
+ }, [version]);
61
+ return {
62
+ user,
63
+ isAuthenticated: Amba.isAuthenticated,
64
+ loading
65
+ };
66
+ }
67
+ /**
68
+ * Reactive subscription to a collection. Re-fetches when `options` or
69
+ * `version` changes. Includes a `refetch` callback for manual refresh.
70
+ */
71
+ function useCollection(name, options = {}) {
72
+ const { version } = useAmbaContext();
73
+ const [data, setData] = useState(null);
74
+ const [response, setResponse] = useState(null);
75
+ const [loading, setLoading] = useState(true);
76
+ const [error, setError] = useState(null);
77
+ const fetchData = useCallback(async () => {
78
+ setLoading(true);
79
+ setError(null);
80
+ try {
81
+ const resp = await Amba.collections.find(name, options);
82
+ setResponse(resp);
83
+ setData(resp.data);
84
+ } catch (e) {
85
+ setError(e instanceof Error ? e : new Error(String(e)));
86
+ } finally {
87
+ setLoading(false);
88
+ }
89
+ }, [name, JSON.stringify(options)]);
90
+ useEffect(() => {
91
+ fetchData();
92
+ }, [fetchData, version]);
93
+ return {
94
+ data,
95
+ loading,
96
+ error,
97
+ refetch: fetchData,
98
+ response
99
+ };
100
+ }
101
+ /**
102
+ * Returns true if the named feature flag is enabled.
103
+ * Fetches the flag set once on mount; subsequent calls read from cache.
104
+ */
105
+ function useFlag(name) {
106
+ useAmbaContext();
107
+ const [enabled, setEnabled] = useState(false);
108
+ useEffect(() => {
109
+ let cancelled = false;
110
+ Amba.flags.fetch().then((flags) => {
111
+ if (cancelled) return;
112
+ setEnabled(flags.find((f) => f.name === name)?.enabled ?? false);
113
+ }).catch(() => {
114
+ if (!cancelled) setEnabled(false);
115
+ });
116
+ return () => {
117
+ cancelled = true;
118
+ };
119
+ }, [name]);
120
+ return enabled;
121
+ }
122
+ /**
123
+ * Returns the variant assignment for a multi-variant flag, or `null`
124
+ * if unset / not loaded.
125
+ */
126
+ function useVariant(name) {
127
+ useAmbaContext();
128
+ const [variant, setVariant] = useState(null);
129
+ useEffect(() => {
130
+ let cancelled = false;
131
+ Amba.flags.fetch().then((flags) => {
132
+ if (cancelled) return;
133
+ setVariant(flags.find((f) => f.name === name)?.variant ?? null);
134
+ }).catch(() => {
135
+ if (!cancelled) setVariant(null);
136
+ });
137
+ return () => {
138
+ cancelled = true;
139
+ };
140
+ }, [name]);
141
+ return variant;
142
+ }
143
+ /**
144
+ * Returns true if the user has the named entitlement (active subscription
145
+ * or one-time purchase grant). Defaults to false on loading / error —
146
+ * safe for paywall gating.
147
+ */
148
+ function useEntitlement(name) {
149
+ useAmbaContext();
150
+ const [has, setHas] = useState(false);
151
+ useEffect(() => {
152
+ let cancelled = false;
153
+ Amba.entitlements.has(name).then((v) => {
154
+ if (!cancelled) setHas(v);
155
+ }).catch(() => {
156
+ if (!cancelled) setHas(false);
157
+ });
158
+ return () => {
159
+ cancelled = true;
160
+ };
161
+ }, [name]);
162
+ return has;
163
+ }
164
+ /**
165
+ * Tracks an event when this hook mounts. Useful for page-view tracking:
166
+ * `useTrackOnMount('page_viewed', { path: '/home' })`.
167
+ */
168
+ function useTrackOnMount(event, properties) {
169
+ useEffect(() => {
170
+ Amba.events.track(event, properties).catch(() => {});
171
+ }, []);
172
+ }
173
+ const SDK_VERSION = "0.1.0";
174
+
175
+ //#endregion
176
+ export { AmbaProvider, SDK_VERSION, useAmba, useCollection, useEntitlement, useFlag, useTrackOnMount, useUser, useVariant };
177
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @layers/amba-react — React hooks for the amba SDK.\n *\n * Companion package to `@layers/amba-web`. Wraps the imperative `Amba.*`\n * surface in reactive hooks that re-render on state change.\n *\n * @example\n * ```tsx\n * import { Amba } from '@layers/amba-web';\n * import { AmbaProvider, useUser, useCollection, useFlag } from '@layers/amba-react';\n *\n * await Amba.configure({ apiKey: import.meta.env.VITE_AMBA_API_KEY });\n *\n * function App() {\n * return (\n * <AmbaProvider>\n * <TodoList />\n * </AmbaProvider>\n * );\n * }\n *\n * function TodoList() {\n * const user = useUser();\n * const { data: todos, loading, refetch } = useCollection('todos', { limit: 20 });\n * const newOnboarding = useFlag('new_onboarding');\n * if (loading) return <Spinner />;\n * return <ul>{todos?.map(t => <li key={t.id}>{t.title}</li>)}</ul>;\n * }\n * ```\n */\n\nimport { Amba } from \"@layers/amba-web\";\nimport type {\n FindOptions,\n FindResponse,\n User,\n FlagAssignment,\n} from \"@layers/amba-web\";\nimport {\n createContext,\n createElement,\n useCallback,\n useContext,\n useEffect,\n useState,\n type ReactNode,\n} from \"react\";\n\ninterface AmbaContextValue {\n /**\n * Bump this counter to force a re-render of hooks that read SDK state\n * (`useUser`, `useIsAuthenticated`, …). Bumped on every auth state\n * change (sign-in / sign-out / refresh).\n */\n version: number;\n bumpVersion: () => void;\n}\n\nconst AmbaContext = createContext<AmbaContextValue | null>(null);\n\n/**\n * Wraps your app and provides reactive subscriptions to the amba SDK.\n *\n * Call `Amba.configure(...)` BEFORE rendering this provider. It does not\n * configure the SDK itself — it just plugs the React tree into the SDK's\n * state-change channel.\n */\nexport function AmbaProvider({ children }: { children: ReactNode }) {\n const [version, setVersion] = useState(0);\n const bumpVersion = useCallback(() => setVersion((v) => v + 1), []);\n return createElement(\n AmbaContext.Provider,\n { value: { version, bumpVersion } },\n children,\n );\n}\n\nfunction useAmbaContext(): AmbaContextValue {\n const ctx = useContext(AmbaContext);\n if (!ctx) {\n throw new Error(\n \"@layers/amba-react: hooks must be used inside <AmbaProvider>. Wrap your app with <AmbaProvider> after calling Amba.configure()\",\n );\n }\n return ctx;\n}\n\n/**\n * Returns the underlying SDK handle (`Amba`). Useful for one-off calls\n * that don't have a dedicated hook.\n */\nexport function useAmba() {\n // Subscribe to version so this re-renders on state change.\n useAmbaContext();\n return Amba;\n}\n\n/**\n * Returns `{ user, isAuthenticated }` for the currently-signed-in user.\n * `user` is `null` until `auth.me()` resolves; the hook auto-fetches when\n * the SDK is authenticated and refreshes on auth-state changes.\n */\nexport function useUser(): {\n user: User | null;\n isAuthenticated: boolean;\n loading: boolean;\n} {\n const { version } = useAmbaContext();\n const [user, setUser] = useState<User | null>(null);\n const [loading, setLoading] = useState(false);\n\n useEffect(() => {\n if (!Amba.isAuthenticated) {\n setUser(null);\n return;\n }\n let cancelled = false;\n setLoading(true);\n Amba.auth\n .me()\n .then((u) => {\n if (!cancelled) setUser(u);\n })\n .catch(() => {\n if (!cancelled) setUser(null);\n })\n .finally(() => {\n if (!cancelled) setLoading(false);\n });\n return () => {\n cancelled = true;\n };\n }, [version]);\n\n return { user, isAuthenticated: Amba.isAuthenticated, loading };\n}\n\n/**\n * Reactive subscription to a collection. Re-fetches when `options` or\n * `version` changes. Includes a `refetch` callback for manual refresh.\n */\nexport function useCollection<T = unknown>(\n name: string,\n options: FindOptions = {},\n): {\n data: T[] | null;\n loading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n response: FindResponse<T> | null;\n} {\n const { version } = useAmbaContext();\n const [data, setData] = useState<T[] | null>(null);\n const [response, setResponse] = useState<FindResponse<T> | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n // Stringify options for stable dependency tracking\n const optionsKey = JSON.stringify(options);\n\n const fetchData = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const resp = await Amba.collections.find<T>(name, options);\n setResponse(resp);\n setData(resp.data);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n } finally {\n setLoading(false);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [name, optionsKey]);\n\n useEffect(() => {\n fetchData();\n }, [fetchData, version]);\n\n return { data, loading, error, refetch: fetchData, response };\n}\n\n/**\n * Returns true if the named feature flag is enabled.\n * Fetches the flag set once on mount; subsequent calls read from cache.\n */\nexport function useFlag(name: string): boolean {\n useAmbaContext();\n const [enabled, setEnabled] = useState(false);\n\n useEffect(() => {\n let cancelled = false;\n Amba.flags\n .fetch()\n .then((flags: FlagAssignment[]) => {\n if (cancelled) return;\n const match = flags.find((f) => f.name === name);\n setEnabled(match?.enabled ?? false);\n })\n .catch(() => {\n if (!cancelled) setEnabled(false);\n });\n return () => {\n cancelled = true;\n };\n }, [name]);\n\n return enabled;\n}\n\n/**\n * Returns the variant assignment for a multi-variant flag, or `null`\n * if unset / not loaded.\n */\nexport function useVariant(name: string): string | null {\n useAmbaContext();\n const [variant, setVariant] = useState<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n Amba.flags\n .fetch()\n .then((flags: FlagAssignment[]) => {\n if (cancelled) return;\n const match = flags.find((f) => f.name === name);\n setVariant(match?.variant ?? null);\n })\n .catch(() => {\n if (!cancelled) setVariant(null);\n });\n return () => {\n cancelled = true;\n };\n }, [name]);\n\n return variant;\n}\n\n/**\n * Returns true if the user has the named entitlement (active subscription\n * or one-time purchase grant). Defaults to false on loading / error —\n * safe for paywall gating.\n */\nexport function useEntitlement(name: string): boolean {\n useAmbaContext();\n const [has, setHas] = useState(false);\n\n useEffect(() => {\n let cancelled = false;\n Amba.entitlements\n .has(name)\n .then((v) => {\n if (!cancelled) setHas(v);\n })\n .catch(() => {\n if (!cancelled) setHas(false);\n });\n return () => {\n cancelled = true;\n };\n }, [name]);\n\n return has;\n}\n\n/**\n * Tracks an event when this hook mounts. Useful for page-view tracking:\n * `useTrackOnMount('page_viewed', { path: '/home' })`.\n */\nexport function useTrackOnMount(\n event: string,\n properties?: Record<string, unknown>,\n) {\n useEffect(() => {\n Amba.events.track(event, properties).catch(() => {\n /* swallow — failure here is non-critical */\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n}\n\nexport const SDK_VERSION = \"0.1.0\";\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@layers/amba-react",
3
+ "version": "1.0.0",
4
+ "description": "React hooks for the amba SDK — useAmba, useUser, useCollection, useFlag, and more",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/layers/amba-sdks.git",
22
+ "directory": "packages/react"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "sideEffects": false,
28
+ "peerDependencies": {
29
+ "react": ">=18.0.0"
30
+ },
31
+ "dependencies": {
32
+ "@layers/amba-web": "^1.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "@testing-library/react": "^16.1.0",
36
+ "@types/react": "^19.1.0",
37
+ "@types/react-dom": "^19.1.0",
38
+ "@types/node": "^22.13.14",
39
+ "happy-dom": "^15.11.7",
40
+ "react": "^19.0.0",
41
+ "react-dom": "^19.0.0",
42
+ "tsdown": "^0.15.2",
43
+ "typescript": "^5.8.3",
44
+ "vitest": "^3.1.1"
45
+ },
46
+ "engines": {
47
+ "node": ">=20.0.0"
48
+ },
49
+ "scripts": {
50
+ "build": "tsdown",
51
+ "test": "vitest run"
52
+ }
53
+ }