shipos 0.1.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.
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Feature Flags Module
3
+ *
4
+ * Provides namespaced access to feature flag operations.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * const flags = await shipos.featureFlags.getAll();
9
+ * const isEnabled = await shipos.featureFlags.get('new-feature', false);
10
+ * ```
11
+ */
12
+ import type { Cache } from "../cache";
13
+ import type { FlagsResponse } from "../types";
14
+ /**
15
+ * Internal context shared by the parent ShipOS client
16
+ */
17
+ export interface FeatureFlagsContext {
18
+ request: <T>(endpoint: string, options?: {
19
+ method?: string;
20
+ body?: unknown;
21
+ }) => Promise<T>;
22
+ cache: Cache;
23
+ getEnvironment: () => string;
24
+ log: (...args: unknown[]) => void;
25
+ }
26
+ export declare class FeatureFlagsModule {
27
+ private ctx;
28
+ constructor(ctx: FeatureFlagsContext);
29
+ /**
30
+ * Get all feature flags
31
+ *
32
+ * @returns Record of flag keys to boolean values
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * const flags = await shipos.featureFlags.getAll();
37
+ * // => { "new-dashboard": true, "beta-features": false }
38
+ * ```
39
+ */
40
+ getAll(): Promise<FlagsResponse>;
41
+ /**
42
+ * Get a single feature flag value
43
+ *
44
+ * @param key - The flag key
45
+ * @param defaultValue - Default value if flag is not found (defaults to false)
46
+ * @returns The flag value or default
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * const isEnabled = await shipos.featureFlags.get('new-dashboard', false);
51
+ * ```
52
+ */
53
+ get(key: string, defaultValue?: boolean): Promise<boolean>;
54
+ /**
55
+ * Refresh flags (bypass cache)
56
+ *
57
+ * @returns Fresh flags from the API
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * const freshFlags = await shipos.featureFlags.refresh();
62
+ * ```
63
+ */
64
+ refresh(): Promise<FlagsResponse>;
65
+ }
66
+ //# sourceMappingURL=feature-flags.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature-flags.d.ts","sourceRoot":"","sources":["../../src/modules/feature-flags.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5F,KAAK,EAAE,KAAK,CAAC;IACb,cAAc,EAAE,MAAM,MAAM,CAAC;IAC7B,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;CACnC;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,GAAG,CAAsB;gBAErB,GAAG,EAAE,mBAAmB;IAIpC;;;;;;;;;;OAUG;IACG,MAAM,IAAI,OAAO,CAAC,aAAa,CAAC;IActC;;;;;;;;;;;OAWG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,GAAE,OAAe,GAAG,OAAO,CAAC,OAAO,CAAC;IAKvE;;;;;;;;;OASG;IACG,OAAO,IAAI,OAAO,CAAC,aAAa,CAAC;CAKxC"}
@@ -0,0 +1,85 @@
1
+ import type { ReactNode } from "react";
2
+ /**
3
+ * Feature Component Props
4
+ */
5
+ interface FeatureProps {
6
+ /** The feature flag key */
7
+ flag: string;
8
+ /** Content to render when the flag is enabled */
9
+ children: ReactNode;
10
+ /** Content to render when the flag is disabled (optional) */
11
+ fallback?: ReactNode;
12
+ /** Content to render while loading (optional) */
13
+ loading?: ReactNode;
14
+ }
15
+ /**
16
+ * Feature Component
17
+ *
18
+ * Conditionally renders content based on a feature flag.
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * // Basic usage
23
+ * <Feature flag="new-dashboard">
24
+ * <NewDashboard />
25
+ * </Feature>
26
+ *
27
+ * // With fallback
28
+ * <Feature flag="new-dashboard" fallback={<OldDashboard />}>
29
+ * <NewDashboard />
30
+ * </Feature>
31
+ *
32
+ * // With loading state
33
+ * <Feature flag="new-dashboard" loading={<Skeleton />}>
34
+ * <NewDashboard />
35
+ * </Feature>
36
+ * ```
37
+ */
38
+ export declare function Feature({ flag, children, fallback, loading, }: FeatureProps): import("react/jsx-runtime").JSX.Element;
39
+ /**
40
+ * FeatureEnabled Component Props
41
+ */
42
+ interface FeatureEnabledProps {
43
+ /** The feature flag key */
44
+ flag: string;
45
+ /** Content to render when the flag is enabled */
46
+ children: ReactNode;
47
+ }
48
+ /**
49
+ * FeatureEnabled Component
50
+ *
51
+ * Only renders children when the flag is enabled.
52
+ * Simpler alternative to Feature when you don't need fallback.
53
+ *
54
+ * @example
55
+ * ```tsx
56
+ * <FeatureEnabled flag="beta-features">
57
+ * <BetaBanner />
58
+ * </FeatureEnabled>
59
+ * ```
60
+ */
61
+ export declare function FeatureEnabled({ flag, children }: FeatureEnabledProps): import("react/jsx-runtime").JSX.Element | null;
62
+ /**
63
+ * FeatureDisabled Component Props
64
+ */
65
+ interface FeatureDisabledProps {
66
+ /** The feature flag key */
67
+ flag: string;
68
+ /** Content to render when the flag is disabled */
69
+ children: ReactNode;
70
+ }
71
+ /**
72
+ * FeatureDisabled Component
73
+ *
74
+ * Only renders children when the flag is disabled.
75
+ *
76
+ * @example
77
+ * ```tsx
78
+ * <FeatureDisabled flag="maintenance-mode">
79
+ * <MainContent />
80
+ * </FeatureDisabled>
81
+ * ```
82
+ */
83
+ export declare function FeatureDisabled({ flag, children }: FeatureDisabledProps): import("react/jsx-runtime").JSX.Element | null;
84
+ export {};
85
+ //# sourceMappingURL=components.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../../src/react/components.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC;;GAEG;AACH,UAAU,YAAY;IACpB,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,QAAQ,EAAE,SAAS,CAAC;IACpB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,iDAAiD;IACjD,OAAO,CAAC,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,OAAO,CAAC,EACtB,IAAI,EACJ,QAAQ,EACR,QAAe,EACf,OAAc,GACf,EAAE,YAAY,2CAQd;AAED;;GAEG;AACH,UAAU,mBAAmB;IAC3B,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,mBAAmB,kDAQrE;AAED;;GAEG;AACH,UAAU,oBAAoB;IAC5B,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,oBAAoB,kDAQvE"}
@@ -0,0 +1,115 @@
1
+ import type { CustomerInfo, SignInOptions } from "../types";
2
+ /**
3
+ * Hook to get all feature flags
4
+ *
5
+ * @example
6
+ * ```tsx
7
+ * function MyComponent() {
8
+ * const { flags, isLoading, error } = useFlags();
9
+ *
10
+ * if (isLoading) return <div>Loading...</div>;
11
+ * if (error) return <div>Error: {error.message}</div>;
12
+ *
13
+ * return (
14
+ * <div>
15
+ * {flags['new-feature'] && <NewFeature />}
16
+ * </div>
17
+ * );
18
+ * }
19
+ * ```
20
+ */
21
+ export declare function useFlags(): {
22
+ flags: import("..").FlagsResponse;
23
+ isLoading: boolean;
24
+ error: Error | null;
25
+ refreshFlags: () => Promise<void>;
26
+ };
27
+ /**
28
+ * Hook to get a single feature flag
29
+ *
30
+ * @param key - The flag key
31
+ * @param defaultValue - Default value if flag is not found
32
+ *
33
+ * @example
34
+ * ```tsx
35
+ * function MyComponent() {
36
+ * const { enabled, isLoading } = useFlag('new-feature', false);
37
+ *
38
+ * if (isLoading) return <div>Loading...</div>;
39
+ *
40
+ * return enabled ? <NewFeature /> : <OldFeature />;
41
+ * }
42
+ * ```
43
+ */
44
+ export declare function useFlag(key: string, defaultValue?: boolean): {
45
+ enabled: boolean;
46
+ isLoading: boolean;
47
+ error: Error | null;
48
+ };
49
+ /**
50
+ * Hook to get a configuration by slug
51
+ *
52
+ * @param slug - The config slug
53
+ *
54
+ * @example
55
+ * ```tsx
56
+ * interface PricingConfig {
57
+ * plans: Array<{ name: string; price: number }>;
58
+ * }
59
+ *
60
+ * function PricingPage() {
61
+ * const { config, isLoading, error, refresh } = useConfig<PricingConfig>('pricing');
62
+ *
63
+ * if (isLoading) return <div>Loading...</div>;
64
+ * if (error) return <div>Error: {error.message}</div>;
65
+ *
66
+ * return (
67
+ * <div>
68
+ * {config?.plans.map(plan => (
69
+ * <div key={plan.name}>{plan.name}: ${plan.price}</div>
70
+ * ))}
71
+ * </div>
72
+ * );
73
+ * }
74
+ * ```
75
+ */
76
+ export declare function useConfig<T = Record<string, unknown>>(slug: string): {
77
+ config: T | null;
78
+ isLoading: boolean;
79
+ error: Error | null;
80
+ refresh: () => Promise<void>;
81
+ };
82
+ /**
83
+ * Hook to manage customer identification
84
+ *
85
+ * @example
86
+ * ```tsx
87
+ * function UserProfile() {
88
+ * const { customer, signIn, signOut } = useCustomer();
89
+ *
90
+ * useEffect(() => {
91
+ * // Sign in when user is authenticated
92
+ * signIn({ externalId: 'user-123', metadata: { plan: 'pro' } });
93
+ * }, []);
94
+ *
95
+ * return (
96
+ * <div>
97
+ * {customer ? (
98
+ * <>
99
+ * <p>Signed in as: {customer.externalId}</p>
100
+ * <button onClick={signOut}>Sign Out</button>
101
+ * </>
102
+ * ) : (
103
+ * <p>Not signed in</p>
104
+ * )}
105
+ * </div>
106
+ * );
107
+ * }
108
+ * ```
109
+ */
110
+ export declare function useCustomer(): {
111
+ customer: CustomerInfo | null;
112
+ signIn: (options: SignInOptions) => Promise<CustomerInfo>;
113
+ signOut: () => void;
114
+ };
115
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/react/hooks.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAkB,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE5E;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,QAAQ;;;;;EAGvB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,GAAE,OAAe;;;;EAKjE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,SAAS,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM;;;;;EA0ClE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,WAAW,IAUpB;IACH,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;IAC9B,MAAM,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IAC1D,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CACF"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * ShipOS SDK React Integration
3
+ *
4
+ * React hooks and components for ShipOS feature flags, configs, and customer identification.
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * import { ShipOSProvider, useFlag, useCustomer, Feature } from '@shipos/sdk/react';
9
+ *
10
+ * function App() {
11
+ * return (
12
+ * <ShipOSProvider config={{ apiKey: 'your-api-key' }}>
13
+ * <Feature flag="new-feature">
14
+ * <NewFeature />
15
+ * </Feature>
16
+ * </ShipOSProvider>
17
+ * );
18
+ * }
19
+ * ```
20
+ */
21
+ export { ShipOSProvider, useShipOS } from "./provider";
22
+ export { useFlags, useFlag, useConfig, useCustomer } from "./hooks";
23
+ export { Feature, FeatureEnabled, FeatureDisabled } from "./components";
24
+ export type { ShipOSConfig, FlagsResponse, ConfigResponse, CustomerInfo, SignInOptions, } from "../types";
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAGpE,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAGxE,YAAY,EACV,YAAY,EACZ,aAAa,EACb,cAAc,EACd,YAAY,EACZ,aAAa,GACd,MAAM,UAAU,CAAC"}
@@ -0,0 +1,388 @@
1
+ // src/cache.ts
2
+ class Cache {
3
+ store = new Map;
4
+ defaultTTL;
5
+ constructor(defaultTTL = 60000) {
6
+ this.defaultTTL = defaultTTL;
7
+ }
8
+ get(key) {
9
+ const entry = this.store.get(key);
10
+ if (!entry) {
11
+ return null;
12
+ }
13
+ if (Date.now() - entry.timestamp > this.defaultTTL) {
14
+ this.store.delete(key);
15
+ return null;
16
+ }
17
+ return entry.data;
18
+ }
19
+ set(key, data) {
20
+ this.store.set(key, {
21
+ data,
22
+ timestamp: Date.now()
23
+ });
24
+ }
25
+ delete(key) {
26
+ this.store.delete(key);
27
+ }
28
+ clear() {
29
+ this.store.clear();
30
+ }
31
+ setTTL(ttl) {
32
+ this.defaultTTL = ttl;
33
+ }
34
+ }
35
+
36
+ // src/modules/feature-flags.ts
37
+ class FeatureFlagsModule {
38
+ ctx;
39
+ constructor(ctx) {
40
+ this.ctx = ctx;
41
+ }
42
+ async getAll() {
43
+ const cacheKey = `flags:${this.ctx.getEnvironment()}`;
44
+ const cached = this.ctx.cache.get(cacheKey);
45
+ if (cached) {
46
+ this.ctx.log("Returning cached flags");
47
+ return cached;
48
+ }
49
+ const flags = await this.ctx.request("/v1/flags");
50
+ this.ctx.cache.set(cacheKey, flags);
51
+ return flags;
52
+ }
53
+ async get(key, defaultValue = false) {
54
+ const flags = await this.getAll();
55
+ return flags[key] ?? defaultValue;
56
+ }
57
+ async refresh() {
58
+ const cacheKey = `flags:${this.ctx.getEnvironment()}`;
59
+ this.ctx.cache.delete(cacheKey);
60
+ return this.getAll();
61
+ }
62
+ }
63
+
64
+ // src/modules/configs.ts
65
+ class ConfigsModule {
66
+ ctx;
67
+ constructor(ctx) {
68
+ this.ctx = ctx;
69
+ }
70
+ async get(slug) {
71
+ const cacheKey = `config:${this.ctx.getEnvironment()}:${slug}`;
72
+ const cached = this.ctx.cache.get(cacheKey);
73
+ if (cached) {
74
+ this.ctx.log(`Returning cached config: ${slug}`);
75
+ return cached;
76
+ }
77
+ const config = await this.ctx.request(`/v1/config/${slug}`);
78
+ this.ctx.cache.set(cacheKey, config);
79
+ return config;
80
+ }
81
+ async refresh(slug) {
82
+ const cacheKey = `config:${this.ctx.getEnvironment()}:${slug}`;
83
+ this.ctx.cache.delete(cacheKey);
84
+ return this.get(slug);
85
+ }
86
+ }
87
+
88
+ // src/modules/customers.ts
89
+ class CustomersModule {
90
+ ctx;
91
+ customer = null;
92
+ constructor(ctx) {
93
+ this.ctx = ctx;
94
+ }
95
+ async signIn(options) {
96
+ if (!options.externalId) {
97
+ throw new Error("ShipOS: externalId is required for signIn");
98
+ }
99
+ this.ctx.log(`Signing in customer: ${options.externalId}`);
100
+ this.customer = {
101
+ externalId: options.externalId,
102
+ metadata: options.metadata
103
+ };
104
+ this.ctx.setCustomerId(options.externalId);
105
+ await this.ctx.request("/v1/customers/identify", {
106
+ method: "POST",
107
+ body: {
108
+ externalId: options.externalId,
109
+ metadata: options.metadata ?? {}
110
+ }
111
+ });
112
+ this.ctx.log(`Customer signed in: ${options.externalId}`);
113
+ return this.customer;
114
+ }
115
+ signOut() {
116
+ this.ctx.log("Signing out customer");
117
+ this.customer = null;
118
+ this.ctx.setCustomerId(null);
119
+ }
120
+ get() {
121
+ return this.customer;
122
+ }
123
+ }
124
+
125
+ // src/client.ts
126
+ var DEFAULT_BASE_URL = "https://api.shipos.app";
127
+ var DEFAULT_CACHE_TTL = 60000;
128
+ var SDK_VERSION = "0.1.0";
129
+
130
+ class ShipOS {
131
+ apiKey;
132
+ baseUrl;
133
+ environment;
134
+ cache;
135
+ debug;
136
+ customerId = null;
137
+ featureFlags;
138
+ configs;
139
+ customers;
140
+ constructor(config) {
141
+ if (!config.apiKey) {
142
+ throw new Error("ShipOS: apiKey is required");
143
+ }
144
+ this.apiKey = config.apiKey;
145
+ this.baseUrl = config.baseUrl || DEFAULT_BASE_URL;
146
+ this.environment = config.environment || "production";
147
+ this.cache = new Cache(config.cacheTTL || DEFAULT_CACHE_TTL);
148
+ this.debug = config.debug || false;
149
+ const ctx = {
150
+ request: this.request.bind(this),
151
+ cache: this.cache,
152
+ getEnvironment: this.getEnvironment.bind(this),
153
+ log: this.log.bind(this),
154
+ setCustomerId: (id) => {
155
+ this.customerId = id;
156
+ }
157
+ };
158
+ this.featureFlags = new FeatureFlagsModule(ctx);
159
+ this.configs = new ConfigsModule(ctx);
160
+ this.customers = new CustomersModule(ctx);
161
+ }
162
+ log(...args) {
163
+ if (this.debug) {
164
+ console.log("[ShipOS]", ...args);
165
+ }
166
+ }
167
+ async request(endpoint, options = {}) {
168
+ const url = `${this.baseUrl}${endpoint}`;
169
+ const method = options.method || "GET";
170
+ this.log(`${method} ${url}`);
171
+ const headers = {
172
+ "x-shipos-key": this.apiKey,
173
+ "x-shipos-env": this.environment,
174
+ "x-shipos-sdk-version": SDK_VERSION,
175
+ "Content-Type": "application/json"
176
+ };
177
+ if (this.customerId) {
178
+ headers["x-shipos-customer-id"] = this.customerId;
179
+ }
180
+ const fetchOptions = { method, headers };
181
+ if (options.body && method !== "GET") {
182
+ fetchOptions.body = JSON.stringify(options.body);
183
+ }
184
+ const response = await fetch(url, fetchOptions);
185
+ if (!response.ok) {
186
+ const error = await response.json().catch(() => ({
187
+ error: `HTTP ${response.status}`,
188
+ code: "HTTP_ERROR"
189
+ }));
190
+ throw new Error(error.error || `Request failed: ${response.status}`);
191
+ }
192
+ return response.json();
193
+ }
194
+ clearCache() {
195
+ this.cache.clear();
196
+ this.log("Cache cleared");
197
+ }
198
+ setEnvironment(environment) {
199
+ this.environment = environment;
200
+ this.cache.clear();
201
+ this.log(`Environment changed to: ${environment}`);
202
+ }
203
+ getEnvironment() {
204
+ return this.environment;
205
+ }
206
+ }
207
+ function createShipOS(config) {
208
+ return new ShipOS(config);
209
+ }
210
+ // src/react/provider.tsx
211
+ import {
212
+ createContext,
213
+ useContext,
214
+ useState,
215
+ useEffect,
216
+ useCallback
217
+ } from "react";
218
+ import { jsxDEV } from "react/jsx-dev-runtime";
219
+ "use client";
220
+ var ShipOSContext = createContext(null);
221
+ function ShipOSProvider({
222
+ config,
223
+ children,
224
+ initialFlags = {}
225
+ }) {
226
+ const [client] = useState(() => new ShipOS(config));
227
+ const [flags, setFlags] = useState(initialFlags);
228
+ const [isLoading, setIsLoading] = useState(Object.keys(initialFlags).length === 0);
229
+ const [error, setError] = useState(null);
230
+ const [customer, setCustomer] = useState(null);
231
+ const fetchFlags = useCallback(async () => {
232
+ try {
233
+ setIsLoading(true);
234
+ setError(null);
235
+ const newFlags = await client.featureFlags.getAll();
236
+ setFlags(newFlags);
237
+ } catch (err) {
238
+ setError(err instanceof Error ? err : new Error("Failed to fetch flags"));
239
+ } finally {
240
+ setIsLoading(false);
241
+ }
242
+ }, [client]);
243
+ const refreshFlags = useCallback(async () => {
244
+ try {
245
+ setError(null);
246
+ const newFlags = await client.featureFlags.refresh();
247
+ setFlags(newFlags);
248
+ } catch (err) {
249
+ setError(err instanceof Error ? err : new Error("Failed to refresh flags"));
250
+ }
251
+ }, [client]);
252
+ const signIn = useCallback(async (options) => {
253
+ const customerInfo = await client.customers.signIn(options);
254
+ setCustomer(customerInfo);
255
+ return customerInfo;
256
+ }, [client]);
257
+ const signOut = useCallback(() => {
258
+ client.customers.signOut();
259
+ setCustomer(null);
260
+ }, [client]);
261
+ useEffect(() => {
262
+ if (Object.keys(initialFlags).length === 0) {
263
+ fetchFlags();
264
+ }
265
+ }, [fetchFlags, initialFlags]);
266
+ const value = {
267
+ client,
268
+ flags,
269
+ isLoading,
270
+ error,
271
+ refreshFlags,
272
+ customer,
273
+ signIn,
274
+ signOut
275
+ };
276
+ return /* @__PURE__ */ jsxDEV(ShipOSContext.Provider, {
277
+ value,
278
+ children
279
+ }, undefined, false, undefined, this);
280
+ }
281
+ function useShipOS() {
282
+ const context = useContext(ShipOSContext);
283
+ if (!context) {
284
+ throw new Error("useShipOS must be used within a ShipOSProvider");
285
+ }
286
+ return context;
287
+ }
288
+ // src/react/hooks.ts
289
+ import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2 } from "react";
290
+ "use client";
291
+ function useFlags() {
292
+ const { flags, isLoading, error, refreshFlags } = useShipOS();
293
+ return { flags, isLoading, error, refreshFlags };
294
+ }
295
+ function useFlag(key, defaultValue = false) {
296
+ const { flags, isLoading, error } = useShipOS();
297
+ const enabled = flags[key] ?? defaultValue;
298
+ return { enabled, isLoading, error };
299
+ }
300
+ function useConfig(slug) {
301
+ const { client } = useShipOS();
302
+ const [config, setConfig] = useState2(null);
303
+ const [isLoading, setIsLoading] = useState2(true);
304
+ const [error, setError] = useState2(null);
305
+ const fetchConfig = useCallback2(async () => {
306
+ try {
307
+ setIsLoading(true);
308
+ setError(null);
309
+ const data = await client.configs.get(slug);
310
+ setConfig(data);
311
+ } catch (err) {
312
+ setError(err instanceof Error ? err : new Error(`Failed to fetch config: ${slug}`));
313
+ } finally {
314
+ setIsLoading(false);
315
+ }
316
+ }, [client, slug]);
317
+ const refresh = useCallback2(async () => {
318
+ try {
319
+ setError(null);
320
+ const data = await client.configs.refresh(slug);
321
+ setConfig(data);
322
+ } catch (err) {
323
+ setError(err instanceof Error ? err : new Error(`Failed to refresh config: ${slug}`));
324
+ }
325
+ }, [client, slug]);
326
+ useEffect2(() => {
327
+ fetchConfig();
328
+ }, [fetchConfig]);
329
+ return { config, isLoading, error, refresh };
330
+ }
331
+ function useCustomer() {
332
+ const { customer, signIn, signOut } = useShipOS();
333
+ return {
334
+ customer,
335
+ signIn,
336
+ signOut
337
+ };
338
+ }
339
+ // src/react/components.tsx
340
+ import { jsxDEV as jsxDEV2, Fragment } from "react/jsx-dev-runtime";
341
+ "use client";
342
+ function Feature({
343
+ flag,
344
+ children,
345
+ fallback = null,
346
+ loading = null
347
+ }) {
348
+ const { enabled, isLoading } = useFlag(flag);
349
+ if (isLoading && loading) {
350
+ return /* @__PURE__ */ jsxDEV2(Fragment, {
351
+ children: loading
352
+ }, undefined, false, undefined, this);
353
+ }
354
+ return enabled ? /* @__PURE__ */ jsxDEV2(Fragment, {
355
+ children
356
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV2(Fragment, {
357
+ children: fallback
358
+ }, undefined, false, undefined, this);
359
+ }
360
+ function FeatureEnabled({ flag, children }) {
361
+ const { enabled, isLoading } = useFlag(flag);
362
+ if (isLoading || !enabled) {
363
+ return null;
364
+ }
365
+ return /* @__PURE__ */ jsxDEV2(Fragment, {
366
+ children
367
+ }, undefined, false, undefined, this);
368
+ }
369
+ function FeatureDisabled({ flag, children }) {
370
+ const { enabled, isLoading } = useFlag(flag);
371
+ if (isLoading || enabled) {
372
+ return null;
373
+ }
374
+ return /* @__PURE__ */ jsxDEV2(Fragment, {
375
+ children
376
+ }, undefined, false, undefined, this);
377
+ }
378
+ export {
379
+ useShipOS,
380
+ useFlags,
381
+ useFlag,
382
+ useCustomer,
383
+ useConfig,
384
+ ShipOSProvider,
385
+ FeatureEnabled,
386
+ FeatureDisabled,
387
+ Feature
388
+ };