@avsbhq/vue 1.0.2 → 1.1.1
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/LICENSE +21 -0
- package/README.md +491 -182
- package/dist/chunk-4XBADQFO.js +21 -0
- package/dist/chunk-FUADAPSW.js +140 -0
- package/dist/clientInterface-BMlQ7Uhe.d.cts +69 -0
- package/dist/clientInterface-BMlQ7Uhe.d.ts +69 -0
- package/dist/index.cjs +283 -350
- package/dist/index.d.cts +202 -172
- package/dist/index.d.ts +202 -172
- package/dist/index.js +177 -413
- package/dist/pinia.cjs +74 -0
- package/dist/pinia.d.cts +23 -0
- package/dist/pinia.d.ts +23 -0
- package/dist/pinia.js +38 -0
- package/dist/testing.cjs +279 -0
- package/dist/testing.d.cts +50 -0
- package/dist/testing.d.ts +50 -0
- package/dist/testing.js +120 -0
- package/dist/types-DECMgjjk.d.cts +34 -0
- package/dist/types-Di6G-dj0.d.ts +34 -0
- package/package.json +69 -19
package/dist/pinia.cjs
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/pinia.ts
|
|
21
|
+
var pinia_exports = {};
|
|
22
|
+
__export(pinia_exports, {
|
|
23
|
+
useAvsbStore: () => useAvsbStore
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(pinia_exports);
|
|
26
|
+
var import_pinia = require("pinia");
|
|
27
|
+
var import_vue2 = require("vue");
|
|
28
|
+
|
|
29
|
+
// src/context.ts
|
|
30
|
+
var import_vue = require("vue");
|
|
31
|
+
var AvsbInjectionKey = /* @__PURE__ */ Symbol("avsb-injection-key");
|
|
32
|
+
function useAvsbContext() {
|
|
33
|
+
const value = (0, import_vue.inject)(AvsbInjectionKey);
|
|
34
|
+
if (value === void 0) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`[avsb] composables must be used inside <AvsbProvider> or app.use(AvsbPlugin). Wrap your component tree with <AvsbProvider sdk-key="sdk_production_..." :context="{ kind: 'user', key: userId }">, or install the plugin with app.use(AvsbPlugin, { sdkKey: 'sdk_production_...' }).`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/pinia.ts
|
|
43
|
+
var useAvsbStore = (0, import_pinia.defineStore)("avsb", () => {
|
|
44
|
+
const context = useAvsbContext();
|
|
45
|
+
const client = context.client;
|
|
46
|
+
const allFlags = (0, import_vue2.shallowRef)(client.getAllFlags());
|
|
47
|
+
const unsubscribe = client.on("flagChange", () => {
|
|
48
|
+
allFlags.value = client.getAllFlags();
|
|
49
|
+
});
|
|
50
|
+
(0, import_vue2.onScopeDispose)(unsubscribe);
|
|
51
|
+
return {
|
|
52
|
+
client: (0, import_vue2.computed)(() => client),
|
|
53
|
+
status: (0, import_vue2.computed)(() => context.status),
|
|
54
|
+
error: (0, import_vue2.computed)(() => context.error),
|
|
55
|
+
degraded: (0, import_vue2.computed)(() => context.degraded),
|
|
56
|
+
flags: (0, import_vue2.computed)(() => allFlags.value),
|
|
57
|
+
identify: (evalContext) => {
|
|
58
|
+
client.identify(evalContext);
|
|
59
|
+
},
|
|
60
|
+
alias: (previousContext, newContext) => {
|
|
61
|
+
client.alias(previousContext, newContext);
|
|
62
|
+
},
|
|
63
|
+
reset: () => {
|
|
64
|
+
client.reset();
|
|
65
|
+
},
|
|
66
|
+
track: (eventKey, payload) => {
|
|
67
|
+
client.track(eventKey, payload);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
72
|
+
0 && (module.exports = {
|
|
73
|
+
useAvsbStore
|
|
74
|
+
});
|
package/dist/pinia.d.cts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as pinia from 'pinia';
|
|
2
|
+
import { ComputedRef } from 'vue';
|
|
3
|
+
import { Flag, EvalContext, TrackPayload } from '@avsbhq/core';
|
|
4
|
+
import { V as VueAvsbClient } from './clientInterface-BMlQ7Uhe.cjs';
|
|
5
|
+
import { d as AvsbStatus } from './types-DECMgjjk.cjs';
|
|
6
|
+
import '@avsbhq/browser';
|
|
7
|
+
|
|
8
|
+
interface AvsbStoreState {
|
|
9
|
+
/** The client the provider built or adopted. */
|
|
10
|
+
client: ComputedRef<VueAvsbClient>;
|
|
11
|
+
status: ComputedRef<AvsbStatus>;
|
|
12
|
+
error: ComputedRef<Error | undefined>;
|
|
13
|
+
degraded: ComputedRef<boolean>;
|
|
14
|
+
/** Every flag, refreshed whenever any flag changes. */
|
|
15
|
+
flags: ComputedRef<Record<string, Flag>>;
|
|
16
|
+
identify: (context: EvalContext) => void;
|
|
17
|
+
alias: (previousContext: EvalContext, newContext: EvalContext) => void;
|
|
18
|
+
reset: () => void;
|
|
19
|
+
track: (eventKey: string, payload?: TrackPayload) => void;
|
|
20
|
+
}
|
|
21
|
+
declare const useAvsbStore: pinia.StoreDefinition<"avsb", Pick<AvsbStoreState, never>, Pick<AvsbStoreState, "flags" | "client" | "error" | "status" | "degraded">, Pick<AvsbStoreState, "reset" | "track" | "identify" | "alias">>;
|
|
22
|
+
|
|
23
|
+
export { type AvsbStoreState, useAvsbStore };
|
package/dist/pinia.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as pinia from 'pinia';
|
|
2
|
+
import { ComputedRef } from 'vue';
|
|
3
|
+
import { Flag, EvalContext, TrackPayload } from '@avsbhq/core';
|
|
4
|
+
import { V as VueAvsbClient } from './clientInterface-BMlQ7Uhe.js';
|
|
5
|
+
import { d as AvsbStatus } from './types-Di6G-dj0.js';
|
|
6
|
+
import '@avsbhq/browser';
|
|
7
|
+
|
|
8
|
+
interface AvsbStoreState {
|
|
9
|
+
/** The client the provider built or adopted. */
|
|
10
|
+
client: ComputedRef<VueAvsbClient>;
|
|
11
|
+
status: ComputedRef<AvsbStatus>;
|
|
12
|
+
error: ComputedRef<Error | undefined>;
|
|
13
|
+
degraded: ComputedRef<boolean>;
|
|
14
|
+
/** Every flag, refreshed whenever any flag changes. */
|
|
15
|
+
flags: ComputedRef<Record<string, Flag>>;
|
|
16
|
+
identify: (context: EvalContext) => void;
|
|
17
|
+
alias: (previousContext: EvalContext, newContext: EvalContext) => void;
|
|
18
|
+
reset: () => void;
|
|
19
|
+
track: (eventKey: string, payload?: TrackPayload) => void;
|
|
20
|
+
}
|
|
21
|
+
declare const useAvsbStore: pinia.StoreDefinition<"avsb", Pick<AvsbStoreState, never>, Pick<AvsbStoreState, "flags" | "client" | "error" | "status" | "degraded">, Pick<AvsbStoreState, "reset" | "track" | "identify" | "alias">>;
|
|
22
|
+
|
|
23
|
+
export { type AvsbStoreState, useAvsbStore };
|
package/dist/pinia.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useAvsbContext
|
|
3
|
+
} from "./chunk-4XBADQFO.js";
|
|
4
|
+
|
|
5
|
+
// src/pinia.ts
|
|
6
|
+
import { defineStore } from "pinia";
|
|
7
|
+
import { computed, onScopeDispose, shallowRef } from "vue";
|
|
8
|
+
var useAvsbStore = defineStore("avsb", () => {
|
|
9
|
+
const context = useAvsbContext();
|
|
10
|
+
const client = context.client;
|
|
11
|
+
const allFlags = shallowRef(client.getAllFlags());
|
|
12
|
+
const unsubscribe = client.on("flagChange", () => {
|
|
13
|
+
allFlags.value = client.getAllFlags();
|
|
14
|
+
});
|
|
15
|
+
onScopeDispose(unsubscribe);
|
|
16
|
+
return {
|
|
17
|
+
client: computed(() => client),
|
|
18
|
+
status: computed(() => context.status),
|
|
19
|
+
error: computed(() => context.error),
|
|
20
|
+
degraded: computed(() => context.degraded),
|
|
21
|
+
flags: computed(() => allFlags.value),
|
|
22
|
+
identify: (evalContext) => {
|
|
23
|
+
client.identify(evalContext);
|
|
24
|
+
},
|
|
25
|
+
alias: (previousContext, newContext) => {
|
|
26
|
+
client.alias(previousContext, newContext);
|
|
27
|
+
},
|
|
28
|
+
reset: () => {
|
|
29
|
+
client.reset();
|
|
30
|
+
},
|
|
31
|
+
track: (eventKey, payload) => {
|
|
32
|
+
client.track(eventKey, payload);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
export {
|
|
37
|
+
useAvsbStore
|
|
38
|
+
};
|
package/dist/testing.cjs
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/testing.ts
|
|
21
|
+
var testing_exports = {};
|
|
22
|
+
__export(testing_exports, {
|
|
23
|
+
AvsbTestProvider: () => AvsbTestProvider,
|
|
24
|
+
createTestClient: () => createTestClient,
|
|
25
|
+
createTestDatafile: () => createTestDatafile
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(testing_exports);
|
|
28
|
+
var import_vue4 = require("vue");
|
|
29
|
+
|
|
30
|
+
// ../avsb-test/dist/fixtures.js
|
|
31
|
+
var import_browser = require("@avsbhq/browser");
|
|
32
|
+
var TEST_SDK_KEY = "sdk_test_0000000000000000000";
|
|
33
|
+
function flagType(value) {
|
|
34
|
+
if (typeof value === "boolean") return "boolean";
|
|
35
|
+
if (typeof value === "string") return "string";
|
|
36
|
+
if (typeof value === "number") return "number";
|
|
37
|
+
return "json";
|
|
38
|
+
}
|
|
39
|
+
function entryFor(key, value, index) {
|
|
40
|
+
const variationId = `var_${index}`;
|
|
41
|
+
return {
|
|
42
|
+
id: `flag_${index}`,
|
|
43
|
+
key,
|
|
44
|
+
type: flagType(value),
|
|
45
|
+
enabled: true,
|
|
46
|
+
defaultVariationId: variationId,
|
|
47
|
+
variations: [{ id: variationId, key: "test", value }],
|
|
48
|
+
overrides: [],
|
|
49
|
+
rules: [
|
|
50
|
+
{
|
|
51
|
+
id: `rule_${index}`,
|
|
52
|
+
type: "ab_test",
|
|
53
|
+
enabled: true,
|
|
54
|
+
audienceIds: [],
|
|
55
|
+
hashAttribute: "user.key",
|
|
56
|
+
trafficAllocation: null,
|
|
57
|
+
// `percentage` is a 0..1 proportion (the evaluator multiplies it by
|
|
58
|
+
// 10000), so this is the whole audience.
|
|
59
|
+
variations: [{ variationId, percentage: 1 }]
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function createTestDatafile(flags, options) {
|
|
65
|
+
const mapped = Object.entries(flags ?? {}).map(
|
|
66
|
+
([key, value], index) => entryFor(key, value, index)
|
|
67
|
+
);
|
|
68
|
+
return {
|
|
69
|
+
version: 2,
|
|
70
|
+
projectType: "FEATURE_FLAG",
|
|
71
|
+
sdkKey: options?.sdkKey ?? TEST_SDK_KEY,
|
|
72
|
+
environmentKey: options?.environmentKey ?? "test",
|
|
73
|
+
publishedAt: options?.publishedAt ?? "2024-01-01T00:00:00.000Z",
|
|
74
|
+
// An ORIGIN, the way the publisher writes it: SDKs append the
|
|
75
|
+
// `/v1/collect/*` paths themselves through the core endpoint contract.
|
|
76
|
+
// The `.invalid` TLD is reserved and never resolves, so a stray flush in
|
|
77
|
+
// a test cannot reach a real host.
|
|
78
|
+
collectEndpoint: "https://collect.test.invalid",
|
|
79
|
+
flags: [...mapped, ...options?.entries ?? []],
|
|
80
|
+
audiences: [],
|
|
81
|
+
holdouts: [],
|
|
82
|
+
bandits: [],
|
|
83
|
+
// `bigSegments` deliberately absent: the field was deleted from the
|
|
84
|
+
// datafile type (breaking change B28) because nothing evaluated it.
|
|
85
|
+
contextKinds: ["user"],
|
|
86
|
+
exposureSampling: 1
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
var TEST_CONTEXT = { kind: "user", key: "test-user" };
|
|
90
|
+
function createTestClient(options) {
|
|
91
|
+
const datafile = options?.datafile ?? createTestDatafile(options?.flags ?? {});
|
|
92
|
+
const client = new import_browser.AvsbClient({
|
|
93
|
+
sdkKey: datafile.sdkKey || TEST_SDK_KEY,
|
|
94
|
+
context: options?.context ?? TEST_CONTEXT,
|
|
95
|
+
// No fetch at construction. The datafile arrives through the bootstrap
|
|
96
|
+
// handoff below, which is also what wires the tracker and settles
|
|
97
|
+
// `getInitResult()`, so `track` and `flush` behave as they do in
|
|
98
|
+
// production instead of queueing forever behind a tracker that a
|
|
99
|
+
// failed background fetch never created.
|
|
100
|
+
autoInit: false,
|
|
101
|
+
autoRefresh: false,
|
|
102
|
+
cache: false,
|
|
103
|
+
logLevel: "silent",
|
|
104
|
+
// Long enough that the automatic flush never fires mid-test. Assert on
|
|
105
|
+
// events with an explicit `await client.flush()`.
|
|
106
|
+
events: { flushInterval: 6e4 }
|
|
107
|
+
});
|
|
108
|
+
client.applyDatafileBootstrap(datafile);
|
|
109
|
+
return client;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/AvsbProvider.ts
|
|
113
|
+
var import_vue3 = require("vue");
|
|
114
|
+
var import_browser3 = require("@avsbhq/browser");
|
|
115
|
+
var import_core = require("@avsbhq/core");
|
|
116
|
+
|
|
117
|
+
// src/context.ts
|
|
118
|
+
var import_vue = require("vue");
|
|
119
|
+
var AvsbInjectionKey = /* @__PURE__ */ Symbol("avsb-injection-key");
|
|
120
|
+
function hasAvsbContext() {
|
|
121
|
+
return (0, import_vue.inject)(AvsbInjectionKey, null) !== null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/internal/providerState.ts
|
|
125
|
+
var import_vue2 = require("vue");
|
|
126
|
+
var UNEXPLAINED_FAILURE = "[avsb] the SDK could not load a datafile and nothing was cached or bootstrapped, so every flag is returning the default you passed. Check the SDK key on app.avsb.cloud under Environments in the project sidebar.";
|
|
127
|
+
function createProviderState(client) {
|
|
128
|
+
const status = (0, import_vue2.ref)(client.isReady() ? "ready" : "loading");
|
|
129
|
+
const error = (0, import_vue2.shallowRef)(void 0);
|
|
130
|
+
const degraded = (0, import_vue2.ref)(false);
|
|
131
|
+
let stopped = false;
|
|
132
|
+
const applyInitResult = (result) => {
|
|
133
|
+
if (stopped) return;
|
|
134
|
+
if (result.success) {
|
|
135
|
+
status.value = "ready";
|
|
136
|
+
degraded.value = result.degraded === true;
|
|
137
|
+
error.value = result.error;
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
status.value = "error";
|
|
141
|
+
error.value = result.error ?? new Error(UNEXPLAINED_FAILURE);
|
|
142
|
+
};
|
|
143
|
+
const settled = client.getInitResult();
|
|
144
|
+
if (settled) {
|
|
145
|
+
applyInitResult(settled);
|
|
146
|
+
} else {
|
|
147
|
+
void client.onReady().then(applyInitResult);
|
|
148
|
+
}
|
|
149
|
+
const offError = client.on("error", (e) => {
|
|
150
|
+
if (stopped) return;
|
|
151
|
+
error.value = e;
|
|
152
|
+
if (client.isReady()) {
|
|
153
|
+
degraded.value = true;
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
status.value = "error";
|
|
157
|
+
});
|
|
158
|
+
const offReady = client.on("ready", applyInitResult);
|
|
159
|
+
return {
|
|
160
|
+
status,
|
|
161
|
+
error,
|
|
162
|
+
degraded,
|
|
163
|
+
stop: () => {
|
|
164
|
+
stopped = true;
|
|
165
|
+
offError();
|
|
166
|
+
offReady();
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/internal/resolveClient.ts
|
|
172
|
+
var import_browser2 = require("@avsbhq/browser");
|
|
173
|
+
var MISUSE_MESSAGE = `[avsb] <AvsbProvider> needs either an sdk-key (Mode A) or a client (Mode B). Mode A: <AvsbProvider sdk-key="sdk_production_..." :context="{ kind: 'user', key: userId }">. Mode B: <AvsbProvider :client="myClient">. The key for each environment is on app.avsb.cloud under Environments in the project sidebar.`;
|
|
174
|
+
function resolveClient(props) {
|
|
175
|
+
const injected = props.client;
|
|
176
|
+
if (injected !== void 0 && injected !== null) {
|
|
177
|
+
return { client: injected, owned: false };
|
|
178
|
+
}
|
|
179
|
+
const { sdkKey, context, bootstrap, options } = props;
|
|
180
|
+
if (typeof sdkKey !== "string" || sdkKey.length === 0) {
|
|
181
|
+
throw new Error(MISUSE_MESSAGE);
|
|
182
|
+
}
|
|
183
|
+
const clientOptions = {
|
|
184
|
+
...options ?? {},
|
|
185
|
+
sdkKey,
|
|
186
|
+
...context !== void 0 ? { context } : {},
|
|
187
|
+
...bootstrap !== void 0 ? { bootstrap } : {}
|
|
188
|
+
};
|
|
189
|
+
return { client: new import_browser2.AvsbClient(clientOptions), owned: true };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/AvsbProvider.ts
|
|
193
|
+
var NESTED_PROVIDER_WARNING = "[avsb] <AvsbProvider> is nested inside another <AvsbProvider>. The inner one shadows the outer for everything below it, so two clients are now running, each with its own visitor id and its own event queue. Mount one provider at the root of the app unless you meant to isolate a subtree.";
|
|
194
|
+
var AvsbProvider = (0, import_vue3.defineComponent)({
|
|
195
|
+
name: "AvsbProvider",
|
|
196
|
+
props: {
|
|
197
|
+
/** Mode B: a client you built and own. */
|
|
198
|
+
client: {
|
|
199
|
+
type: Object,
|
|
200
|
+
default: void 0
|
|
201
|
+
},
|
|
202
|
+
/** Mode A: the environment's SDK key. */
|
|
203
|
+
sdkKey: {
|
|
204
|
+
type: String,
|
|
205
|
+
default: void 0
|
|
206
|
+
},
|
|
207
|
+
/** Mode A: evaluation context. Omit for an anonymous visitor. */
|
|
208
|
+
context: {
|
|
209
|
+
type: Object,
|
|
210
|
+
default: void 0
|
|
211
|
+
},
|
|
212
|
+
/** Mode A: a datafile fetched on the server, for a real first paint. */
|
|
213
|
+
bootstrap: {
|
|
214
|
+
type: Object,
|
|
215
|
+
default: void 0
|
|
216
|
+
},
|
|
217
|
+
/** Mode A: everything else AvsbClient accepts. */
|
|
218
|
+
options: {
|
|
219
|
+
type: Object,
|
|
220
|
+
default: void 0
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
setup(props, { slots }) {
|
|
224
|
+
if (hasAvsbContext() && (0, import_browser3.isDevEnvironment)()) {
|
|
225
|
+
(0, import_core.consoleLogger)("warn").warn(NESTED_PROVIDER_WARNING);
|
|
226
|
+
}
|
|
227
|
+
const { client, owned } = resolveClient(props);
|
|
228
|
+
const state = createProviderState(client);
|
|
229
|
+
(0, import_vue3.provide)(AvsbInjectionKey, {
|
|
230
|
+
client,
|
|
231
|
+
get status() {
|
|
232
|
+
return state.status.value;
|
|
233
|
+
},
|
|
234
|
+
get error() {
|
|
235
|
+
return state.error.value;
|
|
236
|
+
},
|
|
237
|
+
get degraded() {
|
|
238
|
+
return state.degraded.value;
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
(0, import_vue3.onUnmounted)(() => {
|
|
242
|
+
state.stop();
|
|
243
|
+
if (owned) void client.close();
|
|
244
|
+
});
|
|
245
|
+
return () => slots.default?.();
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// src/testing.ts
|
|
250
|
+
var AvsbTestProvider = (0, import_vue4.defineComponent)({
|
|
251
|
+
name: "AvsbTestProvider",
|
|
252
|
+
props: {
|
|
253
|
+
flags: {
|
|
254
|
+
type: Object,
|
|
255
|
+
default: void 0
|
|
256
|
+
},
|
|
257
|
+
context: {
|
|
258
|
+
type: Object,
|
|
259
|
+
default: void 0
|
|
260
|
+
},
|
|
261
|
+
client: {
|
|
262
|
+
type: Object,
|
|
263
|
+
default: void 0
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
setup(props, { slots }) {
|
|
267
|
+
const client = props.client ?? createTestClient({
|
|
268
|
+
...props.flags !== void 0 ? { flags: props.flags } : {},
|
|
269
|
+
...props.context !== void 0 ? { context: props.context } : {}
|
|
270
|
+
});
|
|
271
|
+
return () => (0, import_vue4.h)(AvsbProvider, { client }, { default: () => slots.default?.() });
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
275
|
+
0 && (module.exports = {
|
|
276
|
+
AvsbTestProvider,
|
|
277
|
+
createTestClient,
|
|
278
|
+
createTestDatafile
|
|
279
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as vue from 'vue';
|
|
2
|
+
import { PropType } from 'vue';
|
|
3
|
+
import { TestFlags } from '@avsbhq/test/fixtures';
|
|
4
|
+
export { CreateTestDatafileOptions, TestClientOptions, TestFlagValue, TestFlags, createTestClient, createTestDatafile } from '@avsbhq/test/fixtures';
|
|
5
|
+
import { EvalContext } from '@avsbhq/core';
|
|
6
|
+
import { V as VueAvsbClient } from './clientInterface-BMlQ7Uhe.cjs';
|
|
7
|
+
import '@avsbhq/browser';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* `<AvsbTestProvider :flags="{ 'checkout-v2': true }">` mounts the real
|
|
11
|
+
* provider around a real client, so the components under test use the
|
|
12
|
+
* composables they use in production.
|
|
13
|
+
*
|
|
14
|
+
* Pass `client` instead of `flags` to drive the client yourself.
|
|
15
|
+
*/
|
|
16
|
+
declare const AvsbTestProvider: vue.DefineComponent<vue.ExtractPropTypes<{
|
|
17
|
+
flags: {
|
|
18
|
+
type: PropType<TestFlags>;
|
|
19
|
+
default: undefined;
|
|
20
|
+
};
|
|
21
|
+
context: {
|
|
22
|
+
type: PropType<EvalContext>;
|
|
23
|
+
default: undefined;
|
|
24
|
+
};
|
|
25
|
+
client: {
|
|
26
|
+
type: PropType<VueAvsbClient>;
|
|
27
|
+
default: undefined;
|
|
28
|
+
};
|
|
29
|
+
}>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
|
|
30
|
+
[key: string]: any;
|
|
31
|
+
}>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
|
|
32
|
+
flags: {
|
|
33
|
+
type: PropType<TestFlags>;
|
|
34
|
+
default: undefined;
|
|
35
|
+
};
|
|
36
|
+
context: {
|
|
37
|
+
type: PropType<EvalContext>;
|
|
38
|
+
default: undefined;
|
|
39
|
+
};
|
|
40
|
+
client: {
|
|
41
|
+
type: PropType<VueAvsbClient>;
|
|
42
|
+
default: undefined;
|
|
43
|
+
};
|
|
44
|
+
}>> & Readonly<{}>, {
|
|
45
|
+
flags: TestFlags;
|
|
46
|
+
context: EvalContext;
|
|
47
|
+
client: VueAvsbClient;
|
|
48
|
+
}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
|
|
49
|
+
|
|
50
|
+
export { AvsbTestProvider };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as vue from 'vue';
|
|
2
|
+
import { PropType } from 'vue';
|
|
3
|
+
import { TestFlags } from '@avsbhq/test/fixtures';
|
|
4
|
+
export { CreateTestDatafileOptions, TestClientOptions, TestFlagValue, TestFlags, createTestClient, createTestDatafile } from '@avsbhq/test/fixtures';
|
|
5
|
+
import { EvalContext } from '@avsbhq/core';
|
|
6
|
+
import { V as VueAvsbClient } from './clientInterface-BMlQ7Uhe.js';
|
|
7
|
+
import '@avsbhq/browser';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* `<AvsbTestProvider :flags="{ 'checkout-v2': true }">` mounts the real
|
|
11
|
+
* provider around a real client, so the components under test use the
|
|
12
|
+
* composables they use in production.
|
|
13
|
+
*
|
|
14
|
+
* Pass `client` instead of `flags` to drive the client yourself.
|
|
15
|
+
*/
|
|
16
|
+
declare const AvsbTestProvider: vue.DefineComponent<vue.ExtractPropTypes<{
|
|
17
|
+
flags: {
|
|
18
|
+
type: PropType<TestFlags>;
|
|
19
|
+
default: undefined;
|
|
20
|
+
};
|
|
21
|
+
context: {
|
|
22
|
+
type: PropType<EvalContext>;
|
|
23
|
+
default: undefined;
|
|
24
|
+
};
|
|
25
|
+
client: {
|
|
26
|
+
type: PropType<VueAvsbClient>;
|
|
27
|
+
default: undefined;
|
|
28
|
+
};
|
|
29
|
+
}>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
|
|
30
|
+
[key: string]: any;
|
|
31
|
+
}>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
|
|
32
|
+
flags: {
|
|
33
|
+
type: PropType<TestFlags>;
|
|
34
|
+
default: undefined;
|
|
35
|
+
};
|
|
36
|
+
context: {
|
|
37
|
+
type: PropType<EvalContext>;
|
|
38
|
+
default: undefined;
|
|
39
|
+
};
|
|
40
|
+
client: {
|
|
41
|
+
type: PropType<VueAvsbClient>;
|
|
42
|
+
default: undefined;
|
|
43
|
+
};
|
|
44
|
+
}>> & Readonly<{}>, {
|
|
45
|
+
flags: TestFlags;
|
|
46
|
+
context: EvalContext;
|
|
47
|
+
client: VueAvsbClient;
|
|
48
|
+
}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
|
|
49
|
+
|
|
50
|
+
export { AvsbTestProvider };
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AvsbProvider
|
|
3
|
+
} from "./chunk-FUADAPSW.js";
|
|
4
|
+
import "./chunk-4XBADQFO.js";
|
|
5
|
+
|
|
6
|
+
// src/testing.ts
|
|
7
|
+
import { defineComponent, h } from "vue";
|
|
8
|
+
|
|
9
|
+
// ../avsb-test/dist/fixtures.js
|
|
10
|
+
import { AvsbClient } from "@avsbhq/browser";
|
|
11
|
+
var TEST_SDK_KEY = "sdk_test_0000000000000000000";
|
|
12
|
+
function flagType(value) {
|
|
13
|
+
if (typeof value === "boolean") return "boolean";
|
|
14
|
+
if (typeof value === "string") return "string";
|
|
15
|
+
if (typeof value === "number") return "number";
|
|
16
|
+
return "json";
|
|
17
|
+
}
|
|
18
|
+
function entryFor(key, value, index) {
|
|
19
|
+
const variationId = `var_${index}`;
|
|
20
|
+
return {
|
|
21
|
+
id: `flag_${index}`,
|
|
22
|
+
key,
|
|
23
|
+
type: flagType(value),
|
|
24
|
+
enabled: true,
|
|
25
|
+
defaultVariationId: variationId,
|
|
26
|
+
variations: [{ id: variationId, key: "test", value }],
|
|
27
|
+
overrides: [],
|
|
28
|
+
rules: [
|
|
29
|
+
{
|
|
30
|
+
id: `rule_${index}`,
|
|
31
|
+
type: "ab_test",
|
|
32
|
+
enabled: true,
|
|
33
|
+
audienceIds: [],
|
|
34
|
+
hashAttribute: "user.key",
|
|
35
|
+
trafficAllocation: null,
|
|
36
|
+
// `percentage` is a 0..1 proportion (the evaluator multiplies it by
|
|
37
|
+
// 10000), so this is the whole audience.
|
|
38
|
+
variations: [{ variationId, percentage: 1 }]
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function createTestDatafile(flags, options) {
|
|
44
|
+
const mapped = Object.entries(flags ?? {}).map(
|
|
45
|
+
([key, value], index) => entryFor(key, value, index)
|
|
46
|
+
);
|
|
47
|
+
return {
|
|
48
|
+
version: 2,
|
|
49
|
+
projectType: "FEATURE_FLAG",
|
|
50
|
+
sdkKey: options?.sdkKey ?? TEST_SDK_KEY,
|
|
51
|
+
environmentKey: options?.environmentKey ?? "test",
|
|
52
|
+
publishedAt: options?.publishedAt ?? "2024-01-01T00:00:00.000Z",
|
|
53
|
+
// An ORIGIN, the way the publisher writes it: SDKs append the
|
|
54
|
+
// `/v1/collect/*` paths themselves through the core endpoint contract.
|
|
55
|
+
// The `.invalid` TLD is reserved and never resolves, so a stray flush in
|
|
56
|
+
// a test cannot reach a real host.
|
|
57
|
+
collectEndpoint: "https://collect.test.invalid",
|
|
58
|
+
flags: [...mapped, ...options?.entries ?? []],
|
|
59
|
+
audiences: [],
|
|
60
|
+
holdouts: [],
|
|
61
|
+
bandits: [],
|
|
62
|
+
// `bigSegments` deliberately absent: the field was deleted from the
|
|
63
|
+
// datafile type (breaking change B28) because nothing evaluated it.
|
|
64
|
+
contextKinds: ["user"],
|
|
65
|
+
exposureSampling: 1
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
var TEST_CONTEXT = { kind: "user", key: "test-user" };
|
|
69
|
+
function createTestClient(options) {
|
|
70
|
+
const datafile = options?.datafile ?? createTestDatafile(options?.flags ?? {});
|
|
71
|
+
const client = new AvsbClient({
|
|
72
|
+
sdkKey: datafile.sdkKey || TEST_SDK_KEY,
|
|
73
|
+
context: options?.context ?? TEST_CONTEXT,
|
|
74
|
+
// No fetch at construction. The datafile arrives through the bootstrap
|
|
75
|
+
// handoff below, which is also what wires the tracker and settles
|
|
76
|
+
// `getInitResult()`, so `track` and `flush` behave as they do in
|
|
77
|
+
// production instead of queueing forever behind a tracker that a
|
|
78
|
+
// failed background fetch never created.
|
|
79
|
+
autoInit: false,
|
|
80
|
+
autoRefresh: false,
|
|
81
|
+
cache: false,
|
|
82
|
+
logLevel: "silent",
|
|
83
|
+
// Long enough that the automatic flush never fires mid-test. Assert on
|
|
84
|
+
// events with an explicit `await client.flush()`.
|
|
85
|
+
events: { flushInterval: 6e4 }
|
|
86
|
+
});
|
|
87
|
+
client.applyDatafileBootstrap(datafile);
|
|
88
|
+
return client;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/testing.ts
|
|
92
|
+
var AvsbTestProvider = defineComponent({
|
|
93
|
+
name: "AvsbTestProvider",
|
|
94
|
+
props: {
|
|
95
|
+
flags: {
|
|
96
|
+
type: Object,
|
|
97
|
+
default: void 0
|
|
98
|
+
},
|
|
99
|
+
context: {
|
|
100
|
+
type: Object,
|
|
101
|
+
default: void 0
|
|
102
|
+
},
|
|
103
|
+
client: {
|
|
104
|
+
type: Object,
|
|
105
|
+
default: void 0
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
setup(props, { slots }) {
|
|
109
|
+
const client = props.client ?? createTestClient({
|
|
110
|
+
...props.flags !== void 0 ? { flags: props.flags } : {},
|
|
111
|
+
...props.context !== void 0 ? { context: props.context } : {}
|
|
112
|
+
});
|
|
113
|
+
return () => h(AvsbProvider, { client }, { default: () => slots.default?.() });
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
export {
|
|
117
|
+
AvsbTestProvider,
|
|
118
|
+
createTestClient,
|
|
119
|
+
createTestDatafile
|
|
120
|
+
};
|