@shipeasy/sdk 6.0.0 → 6.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -351
- package/dist/client/index.d.mts +14 -0
- package/dist/client/index.d.ts +14 -0
- package/dist/client/index.js +18 -0
- package/dist/client/index.mjs +18 -0
- package/dist/openfeature-server/index.d.mts +15 -2
- package/dist/openfeature-server/index.d.ts +15 -2
- package/dist/openfeature-server/index.js +194 -4
- package/dist/openfeature-server/index.mjs +194 -4
- package/dist/server/index.d.mts +101 -1
- package/dist/server/index.d.ts +101 -1
- package/dist/server/index.js +101 -4
- package/dist/server/index.mjs +94 -4
- package/dist/skill-cli.js +56 -0
- package/docs/skill/SKILL.md +145 -0
- package/package.json +7 -1
|
@@ -25,6 +25,177 @@ __export(openfeature_server_exports, {
|
|
|
25
25
|
module.exports = __toCommonJS(openfeature_server_exports);
|
|
26
26
|
var import_server_sdk = require("@openfeature/server-sdk");
|
|
27
27
|
|
|
28
|
+
// src/server/index.ts
|
|
29
|
+
var import_node_async_hooks = require("async_hooks");
|
|
30
|
+
|
|
31
|
+
// src/see/core.ts
|
|
32
|
+
var SEE_MAX_SUBJECT = 200;
|
|
33
|
+
var SEE_MAX_EXTRA_VALUE = 200;
|
|
34
|
+
var SEE_MAX_EXTRA_KEYS = 20;
|
|
35
|
+
function causesThe(subject) {
|
|
36
|
+
return {
|
|
37
|
+
to(outcome) {
|
|
38
|
+
return {
|
|
39
|
+
__seConsequence: true,
|
|
40
|
+
subject: truncate(String(subject), SEE_MAX_SUBJECT),
|
|
41
|
+
outcome: truncate(String(outcome), SEE_MAX_SUBJECT)
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function violation(name) {
|
|
47
|
+
return { __seViolation: true, violationName: String(name) };
|
|
48
|
+
}
|
|
49
|
+
var EXPECTED_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-expected");
|
|
50
|
+
function readExpectedMark(err) {
|
|
51
|
+
if (typeof err !== "object" || err === null) return void 0;
|
|
52
|
+
const v = err[EXPECTED_SYM];
|
|
53
|
+
return v !== void 0 && v !== null && typeof v === "object" ? v : void 0;
|
|
54
|
+
}
|
|
55
|
+
function markExpected(err, because, extras) {
|
|
56
|
+
if (typeof err !== "object" || err === null) return;
|
|
57
|
+
const prev = readExpectedMark(err);
|
|
58
|
+
const clean = sanitizeExtras(extras);
|
|
59
|
+
const merged = prev?.extras || clean ? { ...prev?.extras, ...clean } : void 0;
|
|
60
|
+
const mark = {
|
|
61
|
+
because: String(because),
|
|
62
|
+
...merged ? { extras: merged } : {}
|
|
63
|
+
};
|
|
64
|
+
try {
|
|
65
|
+
Object.defineProperty(err, EXPECTED_SYM, {
|
|
66
|
+
value: mark,
|
|
67
|
+
enumerable: false,
|
|
68
|
+
configurable: true
|
|
69
|
+
});
|
|
70
|
+
} catch {
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function truncate(s, max) {
|
|
74
|
+
return s.length > max ? s.slice(0, max) : s;
|
|
75
|
+
}
|
|
76
|
+
function sanitizeExtras(extras) {
|
|
77
|
+
if (!extras || typeof extras !== "object") return void 0;
|
|
78
|
+
const out = {};
|
|
79
|
+
let n = 0;
|
|
80
|
+
for (const [k, v] of Object.entries(extras)) {
|
|
81
|
+
if (v === null || v === void 0) continue;
|
|
82
|
+
if (n >= SEE_MAX_EXTRA_KEYS) break;
|
|
83
|
+
if (typeof v === "string") out[k] = truncate(v, SEE_MAX_EXTRA_VALUE);
|
|
84
|
+
else if (typeof v === "number" && Number.isFinite(v)) out[k] = v;
|
|
85
|
+
else if (typeof v === "boolean") out[k] = v;
|
|
86
|
+
else continue;
|
|
87
|
+
n += 1;
|
|
88
|
+
}
|
|
89
|
+
return n > 0 ? out : void 0;
|
|
90
|
+
}
|
|
91
|
+
var scheduleMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : (cb) => {
|
|
92
|
+
void Promise.resolve().then(cb);
|
|
93
|
+
};
|
|
94
|
+
function startSeeChain(getProblem, dispatch) {
|
|
95
|
+
let subject;
|
|
96
|
+
let outcome;
|
|
97
|
+
let collected;
|
|
98
|
+
let flushed = false;
|
|
99
|
+
scheduleMicrotask(() => {
|
|
100
|
+
if (flushed) return;
|
|
101
|
+
flushed = true;
|
|
102
|
+
dispatch(
|
|
103
|
+
getProblem(),
|
|
104
|
+
// Bare noun phrase — titles render as "… causes the {subject} …", so a
|
|
105
|
+
// leading article would double up ("causes the the app").
|
|
106
|
+
causesThe(subject ?? "app").to(outcome ?? "hit an error"),
|
|
107
|
+
collected
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
const tail = {
|
|
111
|
+
extras(x) {
|
|
112
|
+
if (x && typeof x === "object") collected = { ...collected, ...x };
|
|
113
|
+
return tail;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const step = {
|
|
117
|
+
to(o) {
|
|
118
|
+
outcome = String(o);
|
|
119
|
+
return tail;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const start = (s) => {
|
|
123
|
+
subject = String(s);
|
|
124
|
+
return step;
|
|
125
|
+
};
|
|
126
|
+
return { causes_the: start, causesThe: start };
|
|
127
|
+
}
|
|
128
|
+
function startSeeViolationChain(name, dispatch) {
|
|
129
|
+
return startSeeChain(() => violation(name), dispatch);
|
|
130
|
+
}
|
|
131
|
+
function startControlFlowChain(err) {
|
|
132
|
+
return {
|
|
133
|
+
because(reason) {
|
|
134
|
+
markExpected(err, reason);
|
|
135
|
+
const tail = {
|
|
136
|
+
extras(x) {
|
|
137
|
+
markExpected(err, reason, x);
|
|
138
|
+
return tail;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
return tail;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/server/index.ts
|
|
147
|
+
var _I18N_SSR_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-i18n");
|
|
148
|
+
var _EDIT_MODE_SSR_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-edit-mode");
|
|
149
|
+
var _i18nALS = new import_node_async_hooks.AsyncLocalStorage();
|
|
150
|
+
var _I18N_CACHE_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-i18n-cache");
|
|
151
|
+
var _i18nCache = globalThis[_I18N_CACHE_SYM] ?? (globalThis[_I18N_CACHE_SYM] = /* @__PURE__ */ new Map());
|
|
152
|
+
globalThis[_I18N_SSR_SYM] = () => {
|
|
153
|
+
const fromALS = _i18nALS.getStore();
|
|
154
|
+
if (fromALS && Object.keys(fromALS.strings).length > 0) return fromALS;
|
|
155
|
+
for (const v of _i18nCache.values()) {
|
|
156
|
+
if (Object.keys(v.strings).length > 0) return v;
|
|
157
|
+
}
|
|
158
|
+
return fromALS ?? null;
|
|
159
|
+
};
|
|
160
|
+
var _EDIT_MODE_ALS_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-edit-mode-als");
|
|
161
|
+
var _editModeALS = globalThis[_EDIT_MODE_ALS_SYM] ?? (globalThis[_EDIT_MODE_ALS_SYM] = new import_node_async_hooks.AsyncLocalStorage());
|
|
162
|
+
var _EDIT_MODE_FALLBACK_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-edit-mode-fallback");
|
|
163
|
+
if (globalThis[_EDIT_MODE_FALLBACK_SYM] === void 0) {
|
|
164
|
+
globalThis[_EDIT_MODE_FALLBACK_SYM] = false;
|
|
165
|
+
}
|
|
166
|
+
Object.defineProperty(globalThis, _EDIT_MODE_SSR_SYM, {
|
|
167
|
+
get: () => _editModeALS.getStore() ?? globalThis[_EDIT_MODE_FALLBACK_SYM] ?? false,
|
|
168
|
+
set: (v) => {
|
|
169
|
+
const b = Boolean(v);
|
|
170
|
+
try {
|
|
171
|
+
_editModeALS.enterWith(b);
|
|
172
|
+
} catch {
|
|
173
|
+
}
|
|
174
|
+
globalThis[_EDIT_MODE_FALLBACK_SYM] = b;
|
|
175
|
+
},
|
|
176
|
+
configurable: true
|
|
177
|
+
});
|
|
178
|
+
var _SEE_CORR_ALS_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-correlation-als");
|
|
179
|
+
var seeContext = globalThis[_SEE_CORR_ALS_SYM] ?? (globalThis[_SEE_CORR_ALS_SYM] = new import_node_async_hooks.AsyncLocalStorage());
|
|
180
|
+
var _server = null;
|
|
181
|
+
function getShipeasyServerClient() {
|
|
182
|
+
return _server;
|
|
183
|
+
}
|
|
184
|
+
function dispatchSee(problem, consequence, extras, kind) {
|
|
185
|
+
if (!_server) {
|
|
186
|
+
console.warn("[shipeasy] see() called before shipeasy({ serverKey }) \u2014 error dropped");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
_server.reportError(problem, consequence, extras, kind);
|
|
190
|
+
}
|
|
191
|
+
var see = Object.assign(
|
|
192
|
+
(problem) => startSeeChain(() => problem, dispatchSee),
|
|
193
|
+
{
|
|
194
|
+
Violation: (name) => startSeeViolationChain(name, dispatchSee),
|
|
195
|
+
ControlFlowException: (err) => startControlFlowChain(err)
|
|
196
|
+
}
|
|
197
|
+
);
|
|
198
|
+
|
|
28
199
|
// src/openfeature/shared.ts
|
|
29
200
|
var REASON_MAP = {
|
|
30
201
|
RULE_MATCH: { reason: "TARGETING_MATCH" },
|
|
@@ -78,12 +249,31 @@ function resolveConfig(raw, defaultValue, type) {
|
|
|
78
249
|
return { value: r.value, reason: "TARGETING_MATCH" };
|
|
79
250
|
}
|
|
80
251
|
var ShipeasyProvider = class {
|
|
81
|
-
constructor(client) {
|
|
82
|
-
this.client = client;
|
|
83
|
-
}
|
|
84
|
-
client;
|
|
85
252
|
metadata = { name: "shipeasy" };
|
|
86
253
|
runsOn = "server";
|
|
254
|
+
client;
|
|
255
|
+
/**
|
|
256
|
+
* Construct the provider. The **global form** (no argument) resolves the
|
|
257
|
+
* engine built by `configure({ apiKey })`, so the docs build it after
|
|
258
|
+
* configuration without ever naming the `Engine`:
|
|
259
|
+
*
|
|
260
|
+
* ```ts
|
|
261
|
+
* configure({ apiKey: process.env.SHIPEASY_SERVER_KEY! });
|
|
262
|
+
* await OpenFeature.setProviderAndWait(new ShipeasyProvider());
|
|
263
|
+
* ```
|
|
264
|
+
*
|
|
265
|
+
* Passing an explicit `Engine` stays supported for advanced/back-compat use.
|
|
266
|
+
* Throws if no engine is passed and `configure()` has not run.
|
|
267
|
+
*/
|
|
268
|
+
constructor(client) {
|
|
269
|
+
const resolved = client ?? getShipeasyServerClient();
|
|
270
|
+
if (!resolved) {
|
|
271
|
+
throw new Error(
|
|
272
|
+
"[shipeasy] new ShipeasyProvider() resolves the configured global engine \u2014 call configure({ apiKey }) first, or pass an Engine explicitly."
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
this.client = resolved;
|
|
276
|
+
}
|
|
87
277
|
/** Fetch the rules blob once. The SDK fires `Ready` when this resolves. */
|
|
88
278
|
async initialize() {
|
|
89
279
|
await this.client.initOnce();
|
|
@@ -1,6 +1,177 @@
|
|
|
1
1
|
// src/openfeature-server/index.ts
|
|
2
2
|
import { ErrorCode } from "@openfeature/server-sdk";
|
|
3
3
|
|
|
4
|
+
// src/server/index.ts
|
|
5
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
6
|
+
|
|
7
|
+
// src/see/core.ts
|
|
8
|
+
var SEE_MAX_SUBJECT = 200;
|
|
9
|
+
var SEE_MAX_EXTRA_VALUE = 200;
|
|
10
|
+
var SEE_MAX_EXTRA_KEYS = 20;
|
|
11
|
+
function causesThe(subject) {
|
|
12
|
+
return {
|
|
13
|
+
to(outcome) {
|
|
14
|
+
return {
|
|
15
|
+
__seConsequence: true,
|
|
16
|
+
subject: truncate(String(subject), SEE_MAX_SUBJECT),
|
|
17
|
+
outcome: truncate(String(outcome), SEE_MAX_SUBJECT)
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function violation(name) {
|
|
23
|
+
return { __seViolation: true, violationName: String(name) };
|
|
24
|
+
}
|
|
25
|
+
var EXPECTED_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-expected");
|
|
26
|
+
function readExpectedMark(err) {
|
|
27
|
+
if (typeof err !== "object" || err === null) return void 0;
|
|
28
|
+
const v = err[EXPECTED_SYM];
|
|
29
|
+
return v !== void 0 && v !== null && typeof v === "object" ? v : void 0;
|
|
30
|
+
}
|
|
31
|
+
function markExpected(err, because, extras) {
|
|
32
|
+
if (typeof err !== "object" || err === null) return;
|
|
33
|
+
const prev = readExpectedMark(err);
|
|
34
|
+
const clean = sanitizeExtras(extras);
|
|
35
|
+
const merged = prev?.extras || clean ? { ...prev?.extras, ...clean } : void 0;
|
|
36
|
+
const mark = {
|
|
37
|
+
because: String(because),
|
|
38
|
+
...merged ? { extras: merged } : {}
|
|
39
|
+
};
|
|
40
|
+
try {
|
|
41
|
+
Object.defineProperty(err, EXPECTED_SYM, {
|
|
42
|
+
value: mark,
|
|
43
|
+
enumerable: false,
|
|
44
|
+
configurable: true
|
|
45
|
+
});
|
|
46
|
+
} catch {
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function truncate(s, max) {
|
|
50
|
+
return s.length > max ? s.slice(0, max) : s;
|
|
51
|
+
}
|
|
52
|
+
function sanitizeExtras(extras) {
|
|
53
|
+
if (!extras || typeof extras !== "object") return void 0;
|
|
54
|
+
const out = {};
|
|
55
|
+
let n = 0;
|
|
56
|
+
for (const [k, v] of Object.entries(extras)) {
|
|
57
|
+
if (v === null || v === void 0) continue;
|
|
58
|
+
if (n >= SEE_MAX_EXTRA_KEYS) break;
|
|
59
|
+
if (typeof v === "string") out[k] = truncate(v, SEE_MAX_EXTRA_VALUE);
|
|
60
|
+
else if (typeof v === "number" && Number.isFinite(v)) out[k] = v;
|
|
61
|
+
else if (typeof v === "boolean") out[k] = v;
|
|
62
|
+
else continue;
|
|
63
|
+
n += 1;
|
|
64
|
+
}
|
|
65
|
+
return n > 0 ? out : void 0;
|
|
66
|
+
}
|
|
67
|
+
var scheduleMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : (cb) => {
|
|
68
|
+
void Promise.resolve().then(cb);
|
|
69
|
+
};
|
|
70
|
+
function startSeeChain(getProblem, dispatch) {
|
|
71
|
+
let subject;
|
|
72
|
+
let outcome;
|
|
73
|
+
let collected;
|
|
74
|
+
let flushed = false;
|
|
75
|
+
scheduleMicrotask(() => {
|
|
76
|
+
if (flushed) return;
|
|
77
|
+
flushed = true;
|
|
78
|
+
dispatch(
|
|
79
|
+
getProblem(),
|
|
80
|
+
// Bare noun phrase — titles render as "… causes the {subject} …", so a
|
|
81
|
+
// leading article would double up ("causes the the app").
|
|
82
|
+
causesThe(subject ?? "app").to(outcome ?? "hit an error"),
|
|
83
|
+
collected
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
const tail = {
|
|
87
|
+
extras(x) {
|
|
88
|
+
if (x && typeof x === "object") collected = { ...collected, ...x };
|
|
89
|
+
return tail;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const step = {
|
|
93
|
+
to(o) {
|
|
94
|
+
outcome = String(o);
|
|
95
|
+
return tail;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const start = (s) => {
|
|
99
|
+
subject = String(s);
|
|
100
|
+
return step;
|
|
101
|
+
};
|
|
102
|
+
return { causes_the: start, causesThe: start };
|
|
103
|
+
}
|
|
104
|
+
function startSeeViolationChain(name, dispatch) {
|
|
105
|
+
return startSeeChain(() => violation(name), dispatch);
|
|
106
|
+
}
|
|
107
|
+
function startControlFlowChain(err) {
|
|
108
|
+
return {
|
|
109
|
+
because(reason) {
|
|
110
|
+
markExpected(err, reason);
|
|
111
|
+
const tail = {
|
|
112
|
+
extras(x) {
|
|
113
|
+
markExpected(err, reason, x);
|
|
114
|
+
return tail;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
return tail;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/server/index.ts
|
|
123
|
+
var _I18N_SSR_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-i18n");
|
|
124
|
+
var _EDIT_MODE_SSR_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-edit-mode");
|
|
125
|
+
var _i18nALS = new AsyncLocalStorage();
|
|
126
|
+
var _I18N_CACHE_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-i18n-cache");
|
|
127
|
+
var _i18nCache = globalThis[_I18N_CACHE_SYM] ?? (globalThis[_I18N_CACHE_SYM] = /* @__PURE__ */ new Map());
|
|
128
|
+
globalThis[_I18N_SSR_SYM] = () => {
|
|
129
|
+
const fromALS = _i18nALS.getStore();
|
|
130
|
+
if (fromALS && Object.keys(fromALS.strings).length > 0) return fromALS;
|
|
131
|
+
for (const v of _i18nCache.values()) {
|
|
132
|
+
if (Object.keys(v.strings).length > 0) return v;
|
|
133
|
+
}
|
|
134
|
+
return fromALS ?? null;
|
|
135
|
+
};
|
|
136
|
+
var _EDIT_MODE_ALS_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-edit-mode-als");
|
|
137
|
+
var _editModeALS = globalThis[_EDIT_MODE_ALS_SYM] ?? (globalThis[_EDIT_MODE_ALS_SYM] = new AsyncLocalStorage());
|
|
138
|
+
var _EDIT_MODE_FALLBACK_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-edit-mode-fallback");
|
|
139
|
+
if (globalThis[_EDIT_MODE_FALLBACK_SYM] === void 0) {
|
|
140
|
+
globalThis[_EDIT_MODE_FALLBACK_SYM] = false;
|
|
141
|
+
}
|
|
142
|
+
Object.defineProperty(globalThis, _EDIT_MODE_SSR_SYM, {
|
|
143
|
+
get: () => _editModeALS.getStore() ?? globalThis[_EDIT_MODE_FALLBACK_SYM] ?? false,
|
|
144
|
+
set: (v) => {
|
|
145
|
+
const b = Boolean(v);
|
|
146
|
+
try {
|
|
147
|
+
_editModeALS.enterWith(b);
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
globalThis[_EDIT_MODE_FALLBACK_SYM] = b;
|
|
151
|
+
},
|
|
152
|
+
configurable: true
|
|
153
|
+
});
|
|
154
|
+
var _SEE_CORR_ALS_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:see-correlation-als");
|
|
155
|
+
var seeContext = globalThis[_SEE_CORR_ALS_SYM] ?? (globalThis[_SEE_CORR_ALS_SYM] = new AsyncLocalStorage());
|
|
156
|
+
var _server = null;
|
|
157
|
+
function getShipeasyServerClient() {
|
|
158
|
+
return _server;
|
|
159
|
+
}
|
|
160
|
+
function dispatchSee(problem, consequence, extras, kind) {
|
|
161
|
+
if (!_server) {
|
|
162
|
+
console.warn("[shipeasy] see() called before shipeasy({ serverKey }) \u2014 error dropped");
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
_server.reportError(problem, consequence, extras, kind);
|
|
166
|
+
}
|
|
167
|
+
var see = Object.assign(
|
|
168
|
+
(problem) => startSeeChain(() => problem, dispatchSee),
|
|
169
|
+
{
|
|
170
|
+
Violation: (name) => startSeeViolationChain(name, dispatchSee),
|
|
171
|
+
ControlFlowException: (err) => startControlFlowChain(err)
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
|
|
4
175
|
// src/openfeature/shared.ts
|
|
5
176
|
var REASON_MAP = {
|
|
6
177
|
RULE_MATCH: { reason: "TARGETING_MATCH" },
|
|
@@ -54,12 +225,31 @@ function resolveConfig(raw, defaultValue, type) {
|
|
|
54
225
|
return { value: r.value, reason: "TARGETING_MATCH" };
|
|
55
226
|
}
|
|
56
227
|
var ShipeasyProvider = class {
|
|
57
|
-
constructor(client) {
|
|
58
|
-
this.client = client;
|
|
59
|
-
}
|
|
60
|
-
client;
|
|
61
228
|
metadata = { name: "shipeasy" };
|
|
62
229
|
runsOn = "server";
|
|
230
|
+
client;
|
|
231
|
+
/**
|
|
232
|
+
* Construct the provider. The **global form** (no argument) resolves the
|
|
233
|
+
* engine built by `configure({ apiKey })`, so the docs build it after
|
|
234
|
+
* configuration without ever naming the `Engine`:
|
|
235
|
+
*
|
|
236
|
+
* ```ts
|
|
237
|
+
* configure({ apiKey: process.env.SHIPEASY_SERVER_KEY! });
|
|
238
|
+
* await OpenFeature.setProviderAndWait(new ShipeasyProvider());
|
|
239
|
+
* ```
|
|
240
|
+
*
|
|
241
|
+
* Passing an explicit `Engine` stays supported for advanced/back-compat use.
|
|
242
|
+
* Throws if no engine is passed and `configure()` has not run.
|
|
243
|
+
*/
|
|
244
|
+
constructor(client) {
|
|
245
|
+
const resolved = client ?? getShipeasyServerClient();
|
|
246
|
+
if (!resolved) {
|
|
247
|
+
throw new Error(
|
|
248
|
+
"[shipeasy] new ShipeasyProvider() resolves the configured global engine \u2014 call configure({ apiKey }) first, or pass an Engine explicitly."
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
this.client = resolved;
|
|
252
|
+
}
|
|
63
253
|
/** Fetch the rules blob once. The SDK fires `Ready` when this resolves. */
|
|
64
254
|
async initialize() {
|
|
65
255
|
await this.client.initOnce();
|
package/dist/server/index.d.mts
CHANGED
|
@@ -591,6 +591,20 @@ interface ConfigureOptions<U = unknown> extends Omit<EngineOptions, "apiKey"> {
|
|
|
591
591
|
* verbatim, so it should carry `user_id`/`anonymous_id` + any targeting attrs).
|
|
592
592
|
*/
|
|
593
593
|
attributes?: AttributesFn<U>;
|
|
594
|
+
/**
|
|
595
|
+
* Long-running server: start the **background poll** internally (initial fetch
|
|
596
|
+
* + periodic refresh) so flags stay fresh without a redeploy. Default `false`.
|
|
597
|
+
* With `poll: false` (default) a one-shot fetch is kicked off fire-and-forget
|
|
598
|
+
* (serverless-friendly). You never need to call `engine.init()` yourself —
|
|
599
|
+
* configuration owns the lifecycle.
|
|
600
|
+
*/
|
|
601
|
+
poll?: boolean;
|
|
602
|
+
/**
|
|
603
|
+
* One-shot fetch on configure (fire-and-forget). Default `true`. Ignored when
|
|
604
|
+
* `poll: true` (the poll does the initial fetch). Set `false` only if you want
|
|
605
|
+
* to control the first fetch yourself.
|
|
606
|
+
*/
|
|
607
|
+
init?: boolean;
|
|
594
608
|
}
|
|
595
609
|
/**
|
|
596
610
|
* Configure the SDK once at app boot, then evaluate per user with
|
|
@@ -613,6 +627,77 @@ interface ConfigureOptions<U = unknown> extends Omit<EngineOptions, "apiKey"> {
|
|
|
613
627
|
declare function configure<U = unknown>(opts: ConfigureOptions<U>): Engine;
|
|
614
628
|
/** Test seam: reset the registered attribute transform. */
|
|
615
629
|
declare function _resetConfigureForTests(): void;
|
|
630
|
+
/** Seed shapes shared by {@link configureForTesting} / {@link configureForOffline}. */
|
|
631
|
+
interface ConfigureTestOptions<U = unknown> {
|
|
632
|
+
/** Same transform as {@link configure} (default identity). */
|
|
633
|
+
attributes?: AttributesFn<U>;
|
|
634
|
+
/** `{ name: bool }` — forced `getFlag` results. */
|
|
635
|
+
flags?: Record<string, boolean>;
|
|
636
|
+
/** `{ name: value }` — forced `getConfig` results. */
|
|
637
|
+
configs?: Record<string, unknown>;
|
|
638
|
+
/** `{ name: [group, params] }` — forced enrolments. */
|
|
639
|
+
experiments?: Record<string, [string, Record<string, unknown>]>;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Configure the SDK in **test mode** — a drop-in sibling of {@link configure}
|
|
643
|
+
* with no network, ever (no api key needed). Seed what your code under test
|
|
644
|
+
* should see, then read through the ordinary `new Client(user)`:
|
|
645
|
+
*
|
|
646
|
+
* ```ts
|
|
647
|
+
* configureForTesting({ flags: { new_checkout: true } });
|
|
648
|
+
* const flags = new Client({ user_id: "u_1" });
|
|
649
|
+
* flags.getFlag("new_checkout"); // true
|
|
650
|
+
* ```
|
|
651
|
+
*
|
|
652
|
+
* Replaces any previously-configured engine, so tests can reconfigure freely.
|
|
653
|
+
*/
|
|
654
|
+
declare function configureForTesting<U = unknown>(opts?: ConfigureTestOptions<U>): Engine;
|
|
655
|
+
/** Options for {@link configureForOffline} — exactly one of `snapshot` / `path`. */
|
|
656
|
+
interface ConfigureOfflineOptions<U = unknown> extends ConfigureTestOptions<U> {
|
|
657
|
+
/** In-memory `{ flags: <body of /sdk/flags>, experiments: <body of /sdk/experiments> }`. */
|
|
658
|
+
snapshot?: {
|
|
659
|
+
flags: FlagsBlob;
|
|
660
|
+
experiments: ExpsBlob;
|
|
661
|
+
};
|
|
662
|
+
/** Path to a JSON file `{ flags, experiments }`. */
|
|
663
|
+
path?: string;
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Configure the SDK **offline** — evaluate the REAL rules from an in-memory
|
|
667
|
+
* snapshot or a JSON file, with no network. A drop-in sibling of
|
|
668
|
+
* {@link configure} (no api key needed). Optional `flags`/`configs`/`experiments`
|
|
669
|
+
* overrides are layered on top (same shapes as {@link configureForTesting}).
|
|
670
|
+
* Replaces any previously-configured engine.
|
|
671
|
+
*/
|
|
672
|
+
declare function configureForOffline<U = unknown>(opts: ConfigureOfflineOptions<U>): Engine;
|
|
673
|
+
/**
|
|
674
|
+
* Force `getFlag(name)` → `value` on the spot, for the current config. A quick
|
|
675
|
+
* in-test override layered on top of whatever {@link configureForTesting} /
|
|
676
|
+
* {@link configureForOffline} (or {@link configure}) set up — wins over the blob
|
|
677
|
+
* until {@link clearOverrides}.
|
|
678
|
+
*/
|
|
679
|
+
declare function overrideFlag(name: string, value: boolean): void;
|
|
680
|
+
/** Force `getConfig(name)` → `value` on the spot (see {@link overrideFlag}). */
|
|
681
|
+
declare function overrideConfig(name: string, value: unknown): void;
|
|
682
|
+
/**
|
|
683
|
+
* Force `getExperiment(name)` to report enrolment in `group` with `params` on
|
|
684
|
+
* the spot (see {@link overrideFlag}).
|
|
685
|
+
*/
|
|
686
|
+
declare function overrideExperiment(name: string, group: string, params: Record<string, unknown>): void;
|
|
687
|
+
/**
|
|
688
|
+
* Drop every on-the-spot flag/config/experiment override — INCLUDING the seed
|
|
689
|
+
* from {@link configureForTesting} (test mode has no blob beneath, so everything
|
|
690
|
+
* reverts to the empty-blob defaults). Under {@link configureForOffline} the
|
|
691
|
+
* snapshot remains and evaluations revert to it.
|
|
692
|
+
*/
|
|
693
|
+
declare function clearOverrides(): void;
|
|
694
|
+
/**
|
|
695
|
+
* Register a listener fired after a background poll fetches NEW data (a 200, not
|
|
696
|
+
* a 304). Returns an unsubscribe callable. Requires `configure({ poll: true })`
|
|
697
|
+
* (no poll thread runs otherwise). Configuration owns the engine; you never
|
|
698
|
+
* touch it.
|
|
699
|
+
*/
|
|
700
|
+
declare function onChange(listener: () => void): () => void;
|
|
616
701
|
/**
|
|
617
702
|
* A user-bound evaluation handle. Construct one per user/request — it's cheap
|
|
618
703
|
* (it delegates to the {@link Engine} built by {@link configure}); it does NOT
|
|
@@ -637,6 +722,21 @@ declare class Client<U = unknown> {
|
|
|
637
722
|
getExperiment<P extends Record<string, unknown>>(name: string, defaultParams: P, decode?: (raw: unknown) => P): ExperimentResult<P>;
|
|
638
723
|
/** Read a killswitch (not user-bound; mirrors {@link Engine.getKillswitch}). */
|
|
639
724
|
getKillswitch(name: string, switchKey?: string): boolean;
|
|
725
|
+
/**
|
|
726
|
+
* Record a conversion/metric event for the bound user. Derives the unit from
|
|
727
|
+
* the resolved attribute bag (`user_id`, else `anonymous_id`) and delegates to
|
|
728
|
+
* {@link Engine.track} — so an experiment is end-to-end Client-only (no need to
|
|
729
|
+
* drop down to the Engine to log a conversion). Fire-and-forget; no-op in test
|
|
730
|
+
* mode.
|
|
731
|
+
*/
|
|
732
|
+
track(eventName: string, props?: Record<string, unknown>): void;
|
|
733
|
+
/**
|
|
734
|
+
* Emit an exposure event for `name` at this server-side decision point for the
|
|
735
|
+
* bound user. Delegates to {@link Engine.logExposure} with the resolved
|
|
736
|
+
* attribute bag (re-evaluates enrolment; no-op when the user isn't enrolled or
|
|
737
|
+
* in test mode).
|
|
738
|
+
*/
|
|
739
|
+
logExposure(name: string): void;
|
|
640
740
|
}
|
|
641
741
|
interface SeeApi {
|
|
642
742
|
/**
|
|
@@ -689,4 +789,4 @@ interface SeeApi {
|
|
|
689
789
|
*/
|
|
690
790
|
declare const see: SeeApi;
|
|
691
791
|
|
|
692
|
-
export { ANON_ID_COOKIE, type AttributesFn, type BootstrapData, type BootstrapEmitOptions, type BootstrapPayload, Client, type ConfigureOptions, type Consequence, Engine, type EngineEnv, type EngineOptions, type ExperimentResult, type ExpsBlob, FLAG_REASONS, type FetchLabelsOptions, type FlagDetail, type FlagReason, type FlagsBlob, type GetConfigOptions, type I18nForRequest, type LabelFile, type ScriptTagSpec, type SeeApi, type SeeChain, type SeeControlFlowChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyServerConfig, type ShipeasyServerHandle, type StickyBucketStore, type StickyEntry, type User, type Violation, _murmur3ForTests, _resetConfigureForTests, _resetShipeasyServerForTests, configure, configureShipeasyServer, createInMemoryStickyStore, fetchLabelsForSSR, flags, getBootstrapData, getBootstrapTags, getShipeasyServerClient, i18n, isExpected, see, seeContext, shipeasy, version };
|
|
792
|
+
export { ANON_ID_COOKIE, type AttributesFn, type BootstrapData, type BootstrapEmitOptions, type BootstrapPayload, Client, type ConfigureOfflineOptions, type ConfigureOptions, type ConfigureTestOptions, type Consequence, Engine, type EngineEnv, type EngineOptions, type ExperimentResult, type ExpsBlob, FLAG_REASONS, type FetchLabelsOptions, type FlagDetail, type FlagReason, type FlagsBlob, type GetConfigOptions, type I18nForRequest, type LabelFile, type ScriptTagSpec, type SeeApi, type SeeChain, type SeeControlFlowChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyServerConfig, type ShipeasyServerHandle, type StickyBucketStore, type StickyEntry, type User, type Violation, _murmur3ForTests, _resetConfigureForTests, _resetShipeasyServerForTests, clearOverrides, configure, configureForOffline, configureForTesting, configureShipeasyServer, createInMemoryStickyStore, fetchLabelsForSSR, flags, getBootstrapData, getBootstrapTags, getShipeasyServerClient, i18n, isExpected, onChange, overrideConfig, overrideExperiment, overrideFlag, see, seeContext, shipeasy, version };
|
package/dist/server/index.d.ts
CHANGED
|
@@ -591,6 +591,20 @@ interface ConfigureOptions<U = unknown> extends Omit<EngineOptions, "apiKey"> {
|
|
|
591
591
|
* verbatim, so it should carry `user_id`/`anonymous_id` + any targeting attrs).
|
|
592
592
|
*/
|
|
593
593
|
attributes?: AttributesFn<U>;
|
|
594
|
+
/**
|
|
595
|
+
* Long-running server: start the **background poll** internally (initial fetch
|
|
596
|
+
* + periodic refresh) so flags stay fresh without a redeploy. Default `false`.
|
|
597
|
+
* With `poll: false` (default) a one-shot fetch is kicked off fire-and-forget
|
|
598
|
+
* (serverless-friendly). You never need to call `engine.init()` yourself —
|
|
599
|
+
* configuration owns the lifecycle.
|
|
600
|
+
*/
|
|
601
|
+
poll?: boolean;
|
|
602
|
+
/**
|
|
603
|
+
* One-shot fetch on configure (fire-and-forget). Default `true`. Ignored when
|
|
604
|
+
* `poll: true` (the poll does the initial fetch). Set `false` only if you want
|
|
605
|
+
* to control the first fetch yourself.
|
|
606
|
+
*/
|
|
607
|
+
init?: boolean;
|
|
594
608
|
}
|
|
595
609
|
/**
|
|
596
610
|
* Configure the SDK once at app boot, then evaluate per user with
|
|
@@ -613,6 +627,77 @@ interface ConfigureOptions<U = unknown> extends Omit<EngineOptions, "apiKey"> {
|
|
|
613
627
|
declare function configure<U = unknown>(opts: ConfigureOptions<U>): Engine;
|
|
614
628
|
/** Test seam: reset the registered attribute transform. */
|
|
615
629
|
declare function _resetConfigureForTests(): void;
|
|
630
|
+
/** Seed shapes shared by {@link configureForTesting} / {@link configureForOffline}. */
|
|
631
|
+
interface ConfigureTestOptions<U = unknown> {
|
|
632
|
+
/** Same transform as {@link configure} (default identity). */
|
|
633
|
+
attributes?: AttributesFn<U>;
|
|
634
|
+
/** `{ name: bool }` — forced `getFlag` results. */
|
|
635
|
+
flags?: Record<string, boolean>;
|
|
636
|
+
/** `{ name: value }` — forced `getConfig` results. */
|
|
637
|
+
configs?: Record<string, unknown>;
|
|
638
|
+
/** `{ name: [group, params] }` — forced enrolments. */
|
|
639
|
+
experiments?: Record<string, [string, Record<string, unknown>]>;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Configure the SDK in **test mode** — a drop-in sibling of {@link configure}
|
|
643
|
+
* with no network, ever (no api key needed). Seed what your code under test
|
|
644
|
+
* should see, then read through the ordinary `new Client(user)`:
|
|
645
|
+
*
|
|
646
|
+
* ```ts
|
|
647
|
+
* configureForTesting({ flags: { new_checkout: true } });
|
|
648
|
+
* const flags = new Client({ user_id: "u_1" });
|
|
649
|
+
* flags.getFlag("new_checkout"); // true
|
|
650
|
+
* ```
|
|
651
|
+
*
|
|
652
|
+
* Replaces any previously-configured engine, so tests can reconfigure freely.
|
|
653
|
+
*/
|
|
654
|
+
declare function configureForTesting<U = unknown>(opts?: ConfigureTestOptions<U>): Engine;
|
|
655
|
+
/** Options for {@link configureForOffline} — exactly one of `snapshot` / `path`. */
|
|
656
|
+
interface ConfigureOfflineOptions<U = unknown> extends ConfigureTestOptions<U> {
|
|
657
|
+
/** In-memory `{ flags: <body of /sdk/flags>, experiments: <body of /sdk/experiments> }`. */
|
|
658
|
+
snapshot?: {
|
|
659
|
+
flags: FlagsBlob;
|
|
660
|
+
experiments: ExpsBlob;
|
|
661
|
+
};
|
|
662
|
+
/** Path to a JSON file `{ flags, experiments }`. */
|
|
663
|
+
path?: string;
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Configure the SDK **offline** — evaluate the REAL rules from an in-memory
|
|
667
|
+
* snapshot or a JSON file, with no network. A drop-in sibling of
|
|
668
|
+
* {@link configure} (no api key needed). Optional `flags`/`configs`/`experiments`
|
|
669
|
+
* overrides are layered on top (same shapes as {@link configureForTesting}).
|
|
670
|
+
* Replaces any previously-configured engine.
|
|
671
|
+
*/
|
|
672
|
+
declare function configureForOffline<U = unknown>(opts: ConfigureOfflineOptions<U>): Engine;
|
|
673
|
+
/**
|
|
674
|
+
* Force `getFlag(name)` → `value` on the spot, for the current config. A quick
|
|
675
|
+
* in-test override layered on top of whatever {@link configureForTesting} /
|
|
676
|
+
* {@link configureForOffline} (or {@link configure}) set up — wins over the blob
|
|
677
|
+
* until {@link clearOverrides}.
|
|
678
|
+
*/
|
|
679
|
+
declare function overrideFlag(name: string, value: boolean): void;
|
|
680
|
+
/** Force `getConfig(name)` → `value` on the spot (see {@link overrideFlag}). */
|
|
681
|
+
declare function overrideConfig(name: string, value: unknown): void;
|
|
682
|
+
/**
|
|
683
|
+
* Force `getExperiment(name)` to report enrolment in `group` with `params` on
|
|
684
|
+
* the spot (see {@link overrideFlag}).
|
|
685
|
+
*/
|
|
686
|
+
declare function overrideExperiment(name: string, group: string, params: Record<string, unknown>): void;
|
|
687
|
+
/**
|
|
688
|
+
* Drop every on-the-spot flag/config/experiment override — INCLUDING the seed
|
|
689
|
+
* from {@link configureForTesting} (test mode has no blob beneath, so everything
|
|
690
|
+
* reverts to the empty-blob defaults). Under {@link configureForOffline} the
|
|
691
|
+
* snapshot remains and evaluations revert to it.
|
|
692
|
+
*/
|
|
693
|
+
declare function clearOverrides(): void;
|
|
694
|
+
/**
|
|
695
|
+
* Register a listener fired after a background poll fetches NEW data (a 200, not
|
|
696
|
+
* a 304). Returns an unsubscribe callable. Requires `configure({ poll: true })`
|
|
697
|
+
* (no poll thread runs otherwise). Configuration owns the engine; you never
|
|
698
|
+
* touch it.
|
|
699
|
+
*/
|
|
700
|
+
declare function onChange(listener: () => void): () => void;
|
|
616
701
|
/**
|
|
617
702
|
* A user-bound evaluation handle. Construct one per user/request — it's cheap
|
|
618
703
|
* (it delegates to the {@link Engine} built by {@link configure}); it does NOT
|
|
@@ -637,6 +722,21 @@ declare class Client<U = unknown> {
|
|
|
637
722
|
getExperiment<P extends Record<string, unknown>>(name: string, defaultParams: P, decode?: (raw: unknown) => P): ExperimentResult<P>;
|
|
638
723
|
/** Read a killswitch (not user-bound; mirrors {@link Engine.getKillswitch}). */
|
|
639
724
|
getKillswitch(name: string, switchKey?: string): boolean;
|
|
725
|
+
/**
|
|
726
|
+
* Record a conversion/metric event for the bound user. Derives the unit from
|
|
727
|
+
* the resolved attribute bag (`user_id`, else `anonymous_id`) and delegates to
|
|
728
|
+
* {@link Engine.track} — so an experiment is end-to-end Client-only (no need to
|
|
729
|
+
* drop down to the Engine to log a conversion). Fire-and-forget; no-op in test
|
|
730
|
+
* mode.
|
|
731
|
+
*/
|
|
732
|
+
track(eventName: string, props?: Record<string, unknown>): void;
|
|
733
|
+
/**
|
|
734
|
+
* Emit an exposure event for `name` at this server-side decision point for the
|
|
735
|
+
* bound user. Delegates to {@link Engine.logExposure} with the resolved
|
|
736
|
+
* attribute bag (re-evaluates enrolment; no-op when the user isn't enrolled or
|
|
737
|
+
* in test mode).
|
|
738
|
+
*/
|
|
739
|
+
logExposure(name: string): void;
|
|
640
740
|
}
|
|
641
741
|
interface SeeApi {
|
|
642
742
|
/**
|
|
@@ -689,4 +789,4 @@ interface SeeApi {
|
|
|
689
789
|
*/
|
|
690
790
|
declare const see: SeeApi;
|
|
691
791
|
|
|
692
|
-
export { ANON_ID_COOKIE, type AttributesFn, type BootstrapData, type BootstrapEmitOptions, type BootstrapPayload, Client, type ConfigureOptions, type Consequence, Engine, type EngineEnv, type EngineOptions, type ExperimentResult, type ExpsBlob, FLAG_REASONS, type FetchLabelsOptions, type FlagDetail, type FlagReason, type FlagsBlob, type GetConfigOptions, type I18nForRequest, type LabelFile, type ScriptTagSpec, type SeeApi, type SeeChain, type SeeControlFlowChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyServerConfig, type ShipeasyServerHandle, type StickyBucketStore, type StickyEntry, type User, type Violation, _murmur3ForTests, _resetConfigureForTests, _resetShipeasyServerForTests, configure, configureShipeasyServer, createInMemoryStickyStore, fetchLabelsForSSR, flags, getBootstrapData, getBootstrapTags, getShipeasyServerClient, i18n, isExpected, see, seeContext, shipeasy, version };
|
|
792
|
+
export { ANON_ID_COOKIE, type AttributesFn, type BootstrapData, type BootstrapEmitOptions, type BootstrapPayload, Client, type ConfigureOfflineOptions, type ConfigureOptions, type ConfigureTestOptions, type Consequence, Engine, type EngineEnv, type EngineOptions, type ExperimentResult, type ExpsBlob, FLAG_REASONS, type FetchLabelsOptions, type FlagDetail, type FlagReason, type FlagsBlob, type GetConfigOptions, type I18nForRequest, type LabelFile, type ScriptTagSpec, type SeeApi, type SeeChain, type SeeControlFlowChain, type SeeErrorEvent, type SeeExtras, type SeeKind, type SeeViolationChain, type ShipeasyServerConfig, type ShipeasyServerHandle, type StickyBucketStore, type StickyEntry, type User, type Violation, _murmur3ForTests, _resetConfigureForTests, _resetShipeasyServerForTests, clearOverrides, configure, configureForOffline, configureForTesting, configureShipeasyServer, createInMemoryStickyStore, fetchLabelsForSSR, flags, getBootstrapData, getBootstrapTags, getShipeasyServerClient, i18n, isExpected, onChange, overrideConfig, overrideExperiment, overrideFlag, see, seeContext, shipeasy, version };
|