@avsbhq/vue 1.0.1 → 1.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.
package/dist/index.js CHANGED
@@ -1,217 +1,85 @@
1
- // src/AvsbProvider.ts
2
1
  import {
3
- defineComponent,
4
- ref,
5
- provide,
6
- onMounted,
7
- onUnmounted,
8
- h
9
- } from "vue";
10
- import { AvsbClient } from "@avsbhq/browser";
11
-
12
- // src/context.ts
13
- import { inject } from "vue";
14
- var AvsbInjectionKey = /* @__PURE__ */ Symbol("avsb-injection-key");
15
- function useAvsbContext() {
16
- const v = inject(AvsbInjectionKey);
17
- if (v === void 0) {
18
- throw new Error(
19
- '[avsb] composables must be used inside <AvsbProvider> or app.use(AvsbPlugin). Wrap your component tree with <AvsbProvider sdkKey="..." context={...}> or install the plugin.'
20
- );
21
- }
22
- return v;
23
- }
24
-
25
- // src/AvsbProvider.ts
26
- var AvsbProvider = defineComponent({
27
- name: "AvsbProvider",
28
- props: {
29
- // Mode B — caller passes a pre-built client
30
- client: {
31
- type: Object,
32
- default: void 0
33
- },
34
- // Mode A — sdkKey is the minimum required field from AvsbClientOptions
35
- sdkKey: {
36
- type: String,
37
- default: void 0
38
- }
39
- },
40
- // Accept any additional option props (pollingInterval, bootstrap, etc.)
41
- // via inheritAttrs + $attrs so callers can pass the full AvsbClientOptions
42
- // surface without enumerating every field as a prop.
43
- inheritAttrs: false,
44
- setup(props, { slots, attrs }) {
45
- const injectedClient = props.client ?? null;
46
- const isInjectionMode = injectedClient !== null;
47
- let ownedClient = null;
48
- if (!isInjectionMode) {
49
- if (!props.sdkKey) {
50
- throw new Error(
51
- "[avsb] <AvsbProvider> requires either a `client` prop (Mode B) or a `sdkKey` prop (Mode A)."
52
- );
53
- }
54
- const opts = {
55
- sdkKey: props.sdkKey,
56
- ...attrs
57
- };
58
- ownedClient = new AvsbClient(opts);
59
- }
60
- const activeClient = isInjectionMode ? injectedClient : ownedClient;
61
- const status = ref("loading");
62
- const error = ref(void 0);
63
- provide(AvsbInjectionKey, {
64
- get client() {
65
- return activeClient;
66
- },
67
- get status() {
68
- return status.value;
69
- },
70
- get error() {
71
- return error.value;
72
- }
73
- });
74
- let cancelled = false;
75
- let unsubscribeError;
76
- onMounted(() => {
77
- if (!activeClient) return;
78
- activeClient.onReady().then(() => {
79
- if (!cancelled) status.value = "ready";
80
- }).catch((err) => {
81
- if (!cancelled) {
82
- status.value = "error";
83
- error.value = err instanceof Error ? err : new Error(String(err));
84
- }
85
- });
86
- if (typeof activeClient.on === "function") {
87
- unsubscribeError = activeClient.on("error", (e) => {
88
- if (!cancelled) {
89
- status.value = "error";
90
- error.value = e;
91
- }
92
- });
93
- }
94
- });
95
- onUnmounted(() => {
96
- cancelled = true;
97
- unsubscribeError?.();
98
- if (!isInjectionMode && ownedClient) {
99
- ownedClient.close().catch(() => {
100
- });
101
- ownedClient = null;
102
- }
103
- });
104
- return () => slots.default?.();
105
- },
106
- render() {
107
- return h("div");
108
- }
109
- });
2
+ AvsbProvider,
3
+ createProviderState,
4
+ resolveClient
5
+ } from "./chunk-7A3YSHQG.js";
6
+ import {
7
+ AvsbInjectionKey,
8
+ useAvsbContext
9
+ } from "./chunk-4XBADQFO.js";
110
10
 
111
11
  // src/plugin.ts
112
- import { ref as ref2 } from "vue";
113
- import { AvsbClient as AvsbClient2 } from "@avsbhq/browser";
114
12
  var AvsbPlugin = {
115
13
  install(app, options) {
116
- const injectedClient = "client" in options && options.client ? options.client : null;
117
- const isInjectionMode = injectedClient !== null;
118
- let activeClient;
119
- if (isInjectionMode) {
120
- activeClient = injectedClient;
121
- } else {
122
- const opts = options;
123
- activeClient = new AvsbClient2(opts);
124
- }
125
- const status = ref2("loading");
126
- const error = ref2(void 0);
14
+ const { sdkKey, context, bootstrap, client: injected, ...rest } = options;
15
+ const { client, owned } = resolveClient({
16
+ ...injected ? { client: injected } : { sdkKey },
17
+ ...context !== void 0 ? { context } : {},
18
+ ...bootstrap !== void 0 ? { bootstrap } : {},
19
+ options: rest
20
+ });
21
+ const state = createProviderState(client);
127
22
  app.provide(AvsbInjectionKey, {
128
- get client() {
129
- return activeClient;
130
- },
23
+ client,
131
24
  get status() {
132
- return status.value;
25
+ return state.status.value;
133
26
  },
134
27
  get error() {
135
- return error.value;
28
+ return state.error.value;
29
+ },
30
+ get degraded() {
31
+ return state.degraded.value;
136
32
  }
137
33
  });
138
- activeClient.onReady().then(() => {
139
- status.value = "ready";
140
- }).catch((err) => {
141
- status.value = "error";
142
- error.value = err instanceof Error ? err : new Error(String(err));
143
- });
144
- if (typeof activeClient.on === "function") {
145
- activeClient.on("error", (e) => {
146
- status.value = "error";
147
- error.value = e;
148
- });
149
- }
150
- if (!isInjectionMode) {
151
- app.unmount = new Proxy(app.unmount, {
152
- apply(target, thisArg, args) {
153
- activeClient.close().catch(() => {
154
- });
155
- return Reflect.apply(target, thisArg, args);
156
- }
157
- });
158
- }
34
+ const unmount = app.unmount.bind(app);
35
+ app.unmount = () => {
36
+ state.stop();
37
+ if (owned) void client.close();
38
+ unmount();
39
+ };
159
40
  }
160
41
  };
161
42
 
162
- // src/composables/useFlag.ts
43
+ // src/internal/bindFlagRef.ts
163
44
  import {
164
- ref as ref3,
165
45
  computed,
166
- onMounted as onMounted2,
167
- onUnmounted as onUnmounted2,
168
- watchEffect,
169
- isRef
46
+ isRef,
47
+ onScopeDispose,
48
+ shallowRef,
49
+ watch
170
50
  } from "vue";
171
- import { notFoundFlag } from "@avsbhq/core";
172
- function useFlag(flagKey, defaultValue) {
173
- const { client } = useAvsbContext();
174
- const noClientSentinel = notFoundFlag(defaultValue, "no client");
175
- const flagRef = ref3(
176
- client !== null && typeof client.getSnapshot === "function" ? client.getSnapshot(
177
- isRef(flagKey) ? flagKey.value : flagKey,
178
- defaultValue
179
- ) : noClientSentinel
180
- );
181
- let unsub;
182
- function resubscribe() {
183
- unsub?.();
184
- unsub = void 0;
185
- if (!client || typeof client.subscribe !== "function") return;
186
- const key = isRef(flagKey) ? flagKey.value : flagKey;
187
- if (typeof client.getSnapshot === "function") {
188
- flagRef.value = client.getSnapshot(key, defaultValue);
189
- }
190
- unsub = client.subscribe(key, () => {
191
- if (typeof client.getSnapshot === "function") {
192
- flagRef.value = client.getSnapshot(
193
- isRef(flagKey) ? flagKey.value : flagKey,
194
- defaultValue
195
- );
196
- }
51
+ var RENDER_SAFE = { readOnly: true };
52
+ function resolveFlagKey(flagKey) {
53
+ return isRef(flagKey) ? flagKey.value : flagKey;
54
+ }
55
+ function bindFlagRef(client, flagKey, read) {
56
+ const current = shallowRef(read(client, resolveFlagKey(flagKey)));
57
+ let unsubscribe;
58
+ const subscribe = (key) => {
59
+ unsubscribe?.();
60
+ current.value = read(client, key);
61
+ unsubscribe = client.subscribe(key, () => {
62
+ current.value = read(client, key);
197
63
  });
198
- }
199
- let stopWatcher;
200
- onMounted2(() => {
201
- if (isRef(flagKey)) {
202
- stopWatcher = watchEffect(() => {
203
- void flagKey.value;
204
- resubscribe();
205
- });
206
- } else {
207
- resubscribe();
208
- }
209
- });
210
- onUnmounted2(() => {
64
+ };
65
+ subscribe(resolveFlagKey(flagKey));
66
+ const stopWatcher = isRef(flagKey) ? watch(flagKey, (next) => subscribe(next), { flush: "sync" }) : void 0;
67
+ onScopeDispose(() => {
211
68
  stopWatcher?.();
212
- unsub?.();
69
+ unsubscribe?.();
70
+ unsubscribe = void 0;
213
71
  });
214
- return computed(() => flagRef.value);
72
+ return computed(() => current.value);
73
+ }
74
+
75
+ // src/composables/useFlag.ts
76
+ function useFlag(flagKey, defaultValue) {
77
+ const { client } = useAvsbContext();
78
+ return bindFlagRef(
79
+ client,
80
+ flagKey,
81
+ (c, key) => c.getSnapshot(key, defaultValue)
82
+ );
215
83
  }
216
84
 
217
85
  // src/composables/useFlagValue.ts
@@ -222,298 +90,194 @@ function useFlagValue(flagKey, defaultValue) {
222
90
  }
223
91
 
224
92
  // src/composables/useBoolFlag.ts
225
- import {
226
- ref as ref4,
227
- computed as computed3,
228
- onMounted as onMounted3,
229
- onUnmounted as onUnmounted3
230
- } from "vue";
231
- import { notFoundFlag as notFoundFlag2 } from "@avsbhq/core";
232
93
  function useBoolFlag(flagKey, defaultValue) {
233
94
  const { client } = useAvsbContext();
234
- const noClientSentinel = notFoundFlag2(defaultValue, "no client");
235
- const flagRef = ref4(
236
- client !== null && typeof client.getBoolFlag === "function" ? client.getBoolFlag(flagKey, defaultValue) : noClientSentinel
95
+ return bindFlagRef(
96
+ client,
97
+ flagKey,
98
+ (c, key) => c.getBoolFlag(key, defaultValue, RENDER_SAFE)
237
99
  );
238
- let unsub;
239
- onMounted3(() => {
240
- if (!client || typeof client.subscribe !== "function") return;
241
- if (typeof client.getBoolFlag === "function") {
242
- flagRef.value = client.getBoolFlag(flagKey, defaultValue);
243
- }
244
- unsub = client.subscribe(flagKey, () => {
245
- if (typeof client.getBoolFlag === "function") {
246
- flagRef.value = client.getBoolFlag(flagKey, defaultValue);
247
- }
248
- });
249
- });
250
- onUnmounted3(() => {
251
- unsub?.();
252
- });
253
- return computed3(() => flagRef.value);
254
100
  }
255
101
 
256
102
  // src/composables/useStringFlag.ts
257
- import {
258
- ref as ref5,
259
- computed as computed4,
260
- onMounted as onMounted4,
261
- onUnmounted as onUnmounted4
262
- } from "vue";
263
- import { notFoundFlag as notFoundFlag3 } from "@avsbhq/core";
264
103
  function useStringFlag(flagKey, defaultValue) {
265
104
  const { client } = useAvsbContext();
266
- const noClientSentinel = notFoundFlag3(defaultValue, "no client");
267
- const flagRef = ref5(
268
- client !== null && typeof client.getStringFlag === "function" ? client.getStringFlag(flagKey, defaultValue) : noClientSentinel
105
+ return bindFlagRef(
106
+ client,
107
+ flagKey,
108
+ (c, key) => c.getStringFlag(key, defaultValue, RENDER_SAFE)
269
109
  );
270
- let unsub;
271
- onMounted4(() => {
272
- if (!client || typeof client.subscribe !== "function") return;
273
- if (typeof client.getStringFlag === "function") {
274
- flagRef.value = client.getStringFlag(flagKey, defaultValue);
275
- }
276
- unsub = client.subscribe(flagKey, () => {
277
- if (typeof client.getStringFlag === "function") {
278
- flagRef.value = client.getStringFlag(flagKey, defaultValue);
279
- }
280
- });
281
- });
282
- onUnmounted4(() => {
283
- unsub?.();
284
- });
285
- return computed4(() => flagRef.value);
286
110
  }
287
111
 
288
112
  // src/composables/useNumberFlag.ts
289
- import {
290
- ref as ref6,
291
- computed as computed5,
292
- onMounted as onMounted5,
293
- onUnmounted as onUnmounted5
294
- } from "vue";
295
- import { notFoundFlag as notFoundFlag4 } from "@avsbhq/core";
296
113
  function useNumberFlag(flagKey, defaultValue) {
297
114
  const { client } = useAvsbContext();
298
- const noClientSentinel = notFoundFlag4(defaultValue, "no client");
299
- const flagRef = ref6(
300
- client !== null && typeof client.getNumberFlag === "function" ? client.getNumberFlag(flagKey, defaultValue) : noClientSentinel
115
+ return bindFlagRef(
116
+ client,
117
+ flagKey,
118
+ (c, key) => c.getNumberFlag(key, defaultValue, RENDER_SAFE)
301
119
  );
302
- let unsub;
303
- onMounted5(() => {
304
- if (!client || typeof client.subscribe !== "function") return;
305
- if (typeof client.getNumberFlag === "function") {
306
- flagRef.value = client.getNumberFlag(flagKey, defaultValue);
307
- }
308
- unsub = client.subscribe(flagKey, () => {
309
- if (typeof client.getNumberFlag === "function") {
310
- flagRef.value = client.getNumberFlag(flagKey, defaultValue);
311
- }
312
- });
313
- });
314
- onUnmounted5(() => {
315
- unsub?.();
316
- });
317
- return computed5(() => flagRef.value);
318
120
  }
319
121
 
320
122
  // src/composables/useJsonFlag.ts
321
- import {
322
- ref as ref7,
323
- computed as computed6,
324
- onMounted as onMounted6,
325
- onUnmounted as onUnmounted6
326
- } from "vue";
327
- import { notFoundFlag as notFoundFlag5 } from "@avsbhq/core";
328
123
  function useJsonFlag(flagKey, defaultValue) {
329
124
  const { client } = useAvsbContext();
330
- const noClientSentinel = notFoundFlag5(defaultValue, "no client");
331
- const flagRef = ref7(
332
- client !== null && typeof client.getJsonFlag === "function" ? client.getJsonFlag(flagKey, defaultValue) : noClientSentinel
125
+ return bindFlagRef(
126
+ client,
127
+ flagKey,
128
+ (c, key) => c.getJsonFlag(key, defaultValue, RENDER_SAFE)
333
129
  );
334
- let unsub;
335
- onMounted6(() => {
336
- if (!client || typeof client.subscribe !== "function") return;
337
- if (typeof client.getJsonFlag === "function") {
338
- flagRef.value = client.getJsonFlag(flagKey, defaultValue);
339
- }
340
- unsub = client.subscribe(flagKey, () => {
341
- if (typeof client.getJsonFlag === "function") {
342
- flagRef.value = client.getJsonFlag(flagKey, defaultValue);
343
- }
344
- });
345
- });
346
- onUnmounted6(() => {
347
- unsub?.();
348
- });
349
- return computed6(() => flagRef.value);
350
130
  }
351
131
 
352
132
  // src/composables/useAllFlags.ts
353
133
  import {
354
- ref as ref8,
355
- computed as computed7,
356
- onMounted as onMounted7,
357
- onUnmounted as onUnmounted7
134
+ computed as computed3,
135
+ onScopeDispose as onScopeDispose2,
136
+ shallowRef as shallowRef2
358
137
  } from "vue";
359
138
  function useAllFlags() {
360
139
  const { client } = useAvsbContext();
361
- const snapshotRef = ref8(
362
- client !== null && typeof client.getAllFlags === "function" ? client.getAllFlags() : {}
363
- );
364
- let unsub;
365
- onMounted7(() => {
366
- if (!client || typeof client.on !== "function") return;
367
- if (typeof client.getAllFlags === "function") {
368
- snapshotRef.value = client.getAllFlags();
369
- }
370
- unsub = client.on("flagChange", () => {
371
- if (typeof client.getAllFlags === "function") {
372
- snapshotRef.value = client.getAllFlags();
373
- }
374
- });
140
+ const current = shallowRef2(client.getAllFlags());
141
+ const unsubscribe = client.on("flagChange", () => {
142
+ current.value = client.getAllFlags();
375
143
  });
376
- onUnmounted7(() => {
377
- unsub?.();
378
- });
379
- return computed7(() => snapshotRef.value);
144
+ onScopeDispose2(unsubscribe);
145
+ return computed3(() => current.value);
380
146
  }
381
147
 
382
148
  // src/composables/useFlagReady.ts
383
- import { computed as computed8 } from "vue";
149
+ import { computed as computed4 } from "vue";
384
150
  function useFlagReady() {
385
151
  const context = useAvsbContext();
386
- return computed8(() => context.status === "ready");
152
+ return computed4(() => context.status === "ready");
153
+ }
154
+
155
+ // src/composables/useAvsbStatus.ts
156
+ import { computed as computed5 } from "vue";
157
+ function useAvsbStatus() {
158
+ const context = useAvsbContext();
159
+ return {
160
+ status: computed5(() => context.status),
161
+ error: computed5(() => context.error),
162
+ degraded: computed5(() => context.degraded)
163
+ };
387
164
  }
388
165
 
389
166
  // src/composables/useAvsbClient.ts
390
167
  function useAvsbClient() {
391
- const { client } = useAvsbContext();
392
- return client;
168
+ return useAvsbContext().client;
393
169
  }
394
170
 
395
171
  // src/composables/useIdentify.ts
396
- import { ref as ref9 } from "vue";
397
172
  function useIdentify() {
398
173
  const { client } = useAvsbContext();
399
- const clientRef = ref9(client);
400
- clientRef.value = client;
401
- function identify(ctx) {
402
- const c = clientRef.value;
403
- if (!c || typeof c.identify !== "function") return;
404
- c.identify(ctx);
405
- }
406
- return identify;
174
+ return (context) => {
175
+ client.identify(context);
176
+ };
407
177
  }
408
178
 
409
179
  // src/composables/useAlias.ts
410
- import { ref as ref10 } from "vue";
411
180
  function useAlias() {
412
181
  const { client } = useAvsbContext();
413
- const clientRef = ref10(client);
414
- clientRef.value = client;
415
- async function alias(prev, next) {
416
- const c = clientRef.value;
417
- if (!c || typeof c.alias !== "function") return;
418
- await c.alias(prev, next);
419
- }
420
- return alias;
182
+ return (previousContext, newContext) => {
183
+ client.alias(previousContext, newContext);
184
+ };
185
+ }
186
+
187
+ // src/composables/useReset.ts
188
+ function useReset() {
189
+ const { client } = useAvsbContext();
190
+ return () => {
191
+ client.reset();
192
+ };
421
193
  }
422
194
 
423
195
  // src/composables/useTrack.ts
424
- import { ref as ref11 } from "vue";
425
196
  function useTrack() {
426
197
  const { client } = useAvsbContext();
427
- const clientRef = ref11(client);
428
- clientRef.value = client;
429
- function track(eventKey, payload) {
430
- const c = clientRef.value;
431
- if (!c || typeof c.track !== "function") return;
432
- c.track(eventKey, payload);
198
+ return (eventKey, payload) => {
199
+ client.track(eventKey, payload);
200
+ };
201
+ }
202
+
203
+ // src/composables/useExposure.ts
204
+ import { onMounted, onUnmounted, watch as watch2 } from "vue";
205
+
206
+ // src/internal/fireExposure.ts
207
+ function fireExposureWhenReady(client, flagKey) {
208
+ if (client.isReady()) {
209
+ client.getFlag(flagKey, null);
210
+ return () => {
211
+ };
433
212
  }
434
- return track;
213
+ const off = client.on("ready", () => {
214
+ client.getFlag(flagKey, null);
215
+ off();
216
+ });
217
+ return off;
435
218
  }
436
219
 
437
- // src/pinia.ts
438
- import { defineStore } from "pinia";
439
- import { ref as ref12, computed as computed9 } from "vue";
440
- import { inject as inject2 } from "vue";
441
- var useAvsbStore = defineStore("avsb", () => {
442
- const injectedContext = inject2(AvsbInjectionKey);
443
- const _client = ref12(injectedContext?.client ?? null);
444
- const _status = ref12(
445
- injectedContext?.status ?? "loading"
446
- );
447
- const _error = ref12(injectedContext?.error);
448
- if (injectedContext) {
220
+ // src/composables/useExposure.ts
221
+ function useExposure(flagKey) {
222
+ const { client } = useAvsbContext();
223
+ let cancel;
224
+ const fire = (key) => {
225
+ cancel?.();
226
+ cancel = fireExposureWhenReady(client, key);
227
+ };
228
+ onMounted(() => {
229
+ fire(resolveFlagKey(flagKey));
230
+ });
231
+ if (typeof flagKey !== "string") {
232
+ watch2(flagKey, (next) => fire(next));
449
233
  }
450
- const client = computed9(
451
- () => injectedContext ? injectedContext.client : _client.value
452
- );
453
- const status = computed9(
454
- () => injectedContext ? injectedContext.status : _status.value
455
- );
456
- const error = computed9(
457
- () => injectedContext ? injectedContext.error : _error.value
458
- );
459
- function setClient(c) {
460
- _client.value = c;
461
- _status.value = "loading";
462
- _error.value = void 0;
463
- if (!c) return;
464
- c.onReady().then(() => {
465
- _status.value = "ready";
466
- }).catch((err) => {
467
- _status.value = "error";
468
- _error.value = err instanceof Error ? err : new Error(String(err));
234
+ onUnmounted(() => {
235
+ cancel?.();
236
+ cancel = void 0;
237
+ });
238
+ }
239
+
240
+ // src/composables/useFlagSubscription.ts
241
+ import { onScopeDispose as onScopeDispose3, watch as watch3 } from "vue";
242
+ function useFlagSubscription(flagKey, callback) {
243
+ const { client } = useAvsbContext();
244
+ let unsubscribe;
245
+ const subscribe = (key) => {
246
+ unsubscribe?.();
247
+ unsubscribe = client.subscribe(key, () => {
248
+ callback(client.getSnapshot(key, void 0));
469
249
  });
470
- }
471
- function identify(ctx) {
472
- const c = client.value;
473
- if (!c || typeof c.identify !== "function") return;
474
- c.identify(ctx);
475
- }
476
- function track(eventKey, payload) {
477
- const c = client.value;
478
- if (!c || typeof c.track !== "function") return;
479
- c.track(eventKey, payload);
480
- }
481
- async function alias(prev, next) {
482
- const c = client.value;
483
- if (!c || typeof c.alias !== "function") return;
484
- await c.alias(prev, next);
485
- }
486
- function reset() {
487
- const c = client.value;
488
- if (!c || typeof c.reset !== "function") return;
489
- c.reset();
490
- }
491
- return {
492
- client,
493
- status,
494
- error,
495
- setClient,
496
- identify,
497
- track,
498
- alias,
499
- reset
500
250
  };
501
- });
251
+ subscribe(resolveFlagKey(flagKey));
252
+ const stopWatcher = typeof flagKey === "string" ? void 0 : watch3(flagKey, (next) => subscribe(next), { flush: "sync" });
253
+ onScopeDispose3(() => {
254
+ stopWatcher?.();
255
+ unsubscribe?.();
256
+ unsubscribe = void 0;
257
+ });
258
+ }
259
+
260
+ // src/index.ts
261
+ import { DecideOption } from "@avsbhq/core";
502
262
  export {
503
263
  AvsbInjectionKey,
504
264
  AvsbPlugin,
505
265
  AvsbProvider,
266
+ DecideOption,
506
267
  useAlias,
507
268
  useAllFlags,
508
269
  useAvsbClient,
509
- useAvsbStore,
270
+ useAvsbStatus,
510
271
  useBoolFlag,
272
+ useExposure,
511
273
  useFlag,
512
274
  useFlagReady,
275
+ useFlagSubscription,
513
276
  useFlagValue,
514
277
  useIdentify,
515
278
  useJsonFlag,
516
279
  useNumberFlag,
280
+ useReset,
517
281
  useStringFlag,
518
282
  useTrack
519
283
  };