@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.
@@ -37,7 +37,10 @@ __export(server_exports, {
37
37
  _murmur3ForTests: () => _murmur3ForTests,
38
38
  _resetConfigureForTests: () => _resetConfigureForTests,
39
39
  _resetShipeasyServerForTests: () => _resetShipeasyServerForTests,
40
+ clearOverrides: () => clearOverrides,
40
41
  configure: () => configure,
42
+ configureForOffline: () => configureForOffline,
43
+ configureForTesting: () => configureForTesting,
41
44
  configureShipeasyServer: () => configureShipeasyServer,
42
45
  createInMemoryStickyStore: () => createInMemoryStickyStore,
43
46
  fetchLabelsForSSR: () => fetchLabelsForSSR,
@@ -47,6 +50,10 @@ __export(server_exports, {
47
50
  getShipeasyServerClient: () => getShipeasyServerClient,
48
51
  i18n: () => i18n,
49
52
  isExpected: () => isExpected,
53
+ onChange: () => onChange,
54
+ overrideConfig: () => overrideConfig,
55
+ overrideExperiment: () => overrideExperiment,
56
+ overrideFlag: () => overrideFlag,
50
57
  see: () => see,
51
58
  seeContext: () => seeContext,
52
59
  shipeasy: () => shipeasy,
@@ -981,7 +988,11 @@ var Engine = class _Engine {
981
988
  const ks = this.flagsBlob?.killswitches?.[name];
982
989
  if (!ks) return false;
983
990
  if (switchKey === void 0) return isEnabled(ks.killed);
984
- return isEnabled(ks.switches?.[switchKey]);
991
+ const switches = ks.switches ?? {};
992
+ if (Object.prototype.hasOwnProperty.call(switches, switchKey)) {
993
+ return isEnabled(switches[switchKey]);
994
+ }
995
+ return isEnabled(ks.killed);
985
996
  }
986
997
  };
987
998
  var _I18N_SSR_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-i18n");
@@ -1293,16 +1304,74 @@ var flags = {
1293
1304
  var _identityAttributes = (user) => user && typeof user === "object" ? user : {};
1294
1305
  var _attributes = _identityAttributes;
1295
1306
  function configure(opts) {
1296
- const { attributes, ...engineOpts } = opts;
1307
+ const { attributes, poll = false, init = true, ...engineOpts } = opts;
1297
1308
  _attributes = attributes ?? _identityAttributes;
1298
1309
  const engine = configureShipeasyServer(engineOpts);
1299
- void engine.initOnce().catch(() => {
1300
- });
1310
+ if (poll) {
1311
+ void engine.init().catch(() => {
1312
+ });
1313
+ } else if (init) {
1314
+ void engine.initOnce().catch(() => {
1315
+ });
1316
+ }
1301
1317
  return engine;
1302
1318
  }
1303
1319
  function _resetConfigureForTests() {
1304
1320
  _attributes = _identityAttributes;
1305
1321
  }
1322
+ function _installGlobalEngine(engine, attributes) {
1323
+ _server?.destroy();
1324
+ _server = engine;
1325
+ _attributes = attributes ?? _identityAttributes;
1326
+ return engine;
1327
+ }
1328
+ function _applyOverrides(engine, flags2, configs, experiments) {
1329
+ for (const [name, value] of Object.entries(flags2 ?? {})) engine.overrideFlag(name, value);
1330
+ for (const [name, value] of Object.entries(configs ?? {})) engine.overrideConfig(name, value);
1331
+ for (const [name, [group, params]] of Object.entries(experiments ?? {}))
1332
+ engine.overrideExperiment(name, group, params);
1333
+ }
1334
+ function configureForTesting(opts = {}) {
1335
+ const engine = Engine.forTesting();
1336
+ _applyOverrides(engine, opts.flags, opts.configs, opts.experiments);
1337
+ return _installGlobalEngine(engine, opts.attributes);
1338
+ }
1339
+ function configureForOffline(opts) {
1340
+ let engine;
1341
+ if (opts.path !== void 0) {
1342
+ engine = Engine.fromFile(opts.path);
1343
+ } else if (opts.snapshot !== void 0) {
1344
+ engine = Engine.fromSnapshot(opts.snapshot);
1345
+ } else {
1346
+ throw new Error("[shipeasy] configureForOffline requires either { snapshot } or { path }");
1347
+ }
1348
+ _applyOverrides(engine, opts.flags, opts.configs, opts.experiments);
1349
+ return _installGlobalEngine(engine, opts.attributes);
1350
+ }
1351
+ function _requireGlobal(fn) {
1352
+ const engine = getShipeasyServerClient();
1353
+ if (!engine) {
1354
+ throw new Error(
1355
+ `[shipeasy] ${fn}(...) called before configure({ apiKey }) (or a configureFor* sibling).`
1356
+ );
1357
+ }
1358
+ return engine;
1359
+ }
1360
+ function overrideFlag(name, value) {
1361
+ _requireGlobal("overrideFlag").overrideFlag(name, value);
1362
+ }
1363
+ function overrideConfig(name, value) {
1364
+ _requireGlobal("overrideConfig").overrideConfig(name, value);
1365
+ }
1366
+ function overrideExperiment(name, group, params) {
1367
+ _requireGlobal("overrideExperiment").overrideExperiment(name, group, params);
1368
+ }
1369
+ function clearOverrides() {
1370
+ _requireGlobal("clearOverrides").clearOverrides();
1371
+ }
1372
+ function onChange(listener) {
1373
+ return _requireGlobal("onChange").onChange(listener);
1374
+ }
1306
1375
  var Client = class {
1307
1376
  engine;
1308
1377
  /** The resolved attribute bag this handle evaluates against. */
@@ -1333,6 +1402,27 @@ var Client = class {
1333
1402
  getKillswitch(name, switchKey) {
1334
1403
  return this.engine.getKillswitch(name, switchKey);
1335
1404
  }
1405
+ /**
1406
+ * Record a conversion/metric event for the bound user. Derives the unit from
1407
+ * the resolved attribute bag (`user_id`, else `anonymous_id`) and delegates to
1408
+ * {@link Engine.track} — so an experiment is end-to-end Client-only (no need to
1409
+ * drop down to the Engine to log a conversion). Fire-and-forget; no-op in test
1410
+ * mode.
1411
+ */
1412
+ track(eventName, props) {
1413
+ const id = this.attributes.user_id ?? this.attributes.anonymous_id;
1414
+ if (id === void 0) return;
1415
+ this.engine.track(String(id), eventName, props);
1416
+ }
1417
+ /**
1418
+ * Emit an exposure event for `name` at this server-side decision point for the
1419
+ * bound user. Delegates to {@link Engine.logExposure} with the resolved
1420
+ * attribute bag (re-evaluates enrolment; no-op when the user isn't enrolled or
1421
+ * in test mode).
1422
+ */
1423
+ logExposure(name) {
1424
+ this.engine.logExposure(this.attributes, name);
1425
+ }
1336
1426
  };
1337
1427
  function dispatchSee(problem, consequence, extras, kind) {
1338
1428
  if (!_server) {
@@ -1357,7 +1447,10 @@ var see = Object.assign(
1357
1447
  _murmur3ForTests,
1358
1448
  _resetConfigureForTests,
1359
1449
  _resetShipeasyServerForTests,
1450
+ clearOverrides,
1360
1451
  configure,
1452
+ configureForOffline,
1453
+ configureForTesting,
1361
1454
  configureShipeasyServer,
1362
1455
  createInMemoryStickyStore,
1363
1456
  fetchLabelsForSSR,
@@ -1367,6 +1460,10 @@ var see = Object.assign(
1367
1460
  getShipeasyServerClient,
1368
1461
  i18n,
1369
1462
  isExpected,
1463
+ onChange,
1464
+ overrideConfig,
1465
+ overrideExperiment,
1466
+ overrideFlag,
1370
1467
  see,
1371
1468
  seeContext,
1372
1469
  shipeasy,
@@ -934,7 +934,11 @@ var Engine = class _Engine {
934
934
  const ks = this.flagsBlob?.killswitches?.[name];
935
935
  if (!ks) return false;
936
936
  if (switchKey === void 0) return isEnabled(ks.killed);
937
- return isEnabled(ks.switches?.[switchKey]);
937
+ const switches = ks.switches ?? {};
938
+ if (Object.prototype.hasOwnProperty.call(switches, switchKey)) {
939
+ return isEnabled(switches[switchKey]);
940
+ }
941
+ return isEnabled(ks.killed);
938
942
  }
939
943
  };
940
944
  var _I18N_SSR_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-i18n");
@@ -1246,16 +1250,74 @@ var flags = {
1246
1250
  var _identityAttributes = (user) => user && typeof user === "object" ? user : {};
1247
1251
  var _attributes = _identityAttributes;
1248
1252
  function configure(opts) {
1249
- const { attributes, ...engineOpts } = opts;
1253
+ const { attributes, poll = false, init = true, ...engineOpts } = opts;
1250
1254
  _attributes = attributes ?? _identityAttributes;
1251
1255
  const engine = configureShipeasyServer(engineOpts);
1252
- void engine.initOnce().catch(() => {
1253
- });
1256
+ if (poll) {
1257
+ void engine.init().catch(() => {
1258
+ });
1259
+ } else if (init) {
1260
+ void engine.initOnce().catch(() => {
1261
+ });
1262
+ }
1254
1263
  return engine;
1255
1264
  }
1256
1265
  function _resetConfigureForTests() {
1257
1266
  _attributes = _identityAttributes;
1258
1267
  }
1268
+ function _installGlobalEngine(engine, attributes) {
1269
+ _server?.destroy();
1270
+ _server = engine;
1271
+ _attributes = attributes ?? _identityAttributes;
1272
+ return engine;
1273
+ }
1274
+ function _applyOverrides(engine, flags2, configs, experiments) {
1275
+ for (const [name, value] of Object.entries(flags2 ?? {})) engine.overrideFlag(name, value);
1276
+ for (const [name, value] of Object.entries(configs ?? {})) engine.overrideConfig(name, value);
1277
+ for (const [name, [group, params]] of Object.entries(experiments ?? {}))
1278
+ engine.overrideExperiment(name, group, params);
1279
+ }
1280
+ function configureForTesting(opts = {}) {
1281
+ const engine = Engine.forTesting();
1282
+ _applyOverrides(engine, opts.flags, opts.configs, opts.experiments);
1283
+ return _installGlobalEngine(engine, opts.attributes);
1284
+ }
1285
+ function configureForOffline(opts) {
1286
+ let engine;
1287
+ if (opts.path !== void 0) {
1288
+ engine = Engine.fromFile(opts.path);
1289
+ } else if (opts.snapshot !== void 0) {
1290
+ engine = Engine.fromSnapshot(opts.snapshot);
1291
+ } else {
1292
+ throw new Error("[shipeasy] configureForOffline requires either { snapshot } or { path }");
1293
+ }
1294
+ _applyOverrides(engine, opts.flags, opts.configs, opts.experiments);
1295
+ return _installGlobalEngine(engine, opts.attributes);
1296
+ }
1297
+ function _requireGlobal(fn) {
1298
+ const engine = getShipeasyServerClient();
1299
+ if (!engine) {
1300
+ throw new Error(
1301
+ `[shipeasy] ${fn}(...) called before configure({ apiKey }) (or a configureFor* sibling).`
1302
+ );
1303
+ }
1304
+ return engine;
1305
+ }
1306
+ function overrideFlag(name, value) {
1307
+ _requireGlobal("overrideFlag").overrideFlag(name, value);
1308
+ }
1309
+ function overrideConfig(name, value) {
1310
+ _requireGlobal("overrideConfig").overrideConfig(name, value);
1311
+ }
1312
+ function overrideExperiment(name, group, params) {
1313
+ _requireGlobal("overrideExperiment").overrideExperiment(name, group, params);
1314
+ }
1315
+ function clearOverrides() {
1316
+ _requireGlobal("clearOverrides").clearOverrides();
1317
+ }
1318
+ function onChange(listener) {
1319
+ return _requireGlobal("onChange").onChange(listener);
1320
+ }
1259
1321
  var Client = class {
1260
1322
  engine;
1261
1323
  /** The resolved attribute bag this handle evaluates against. */
@@ -1286,6 +1348,27 @@ var Client = class {
1286
1348
  getKillswitch(name, switchKey) {
1287
1349
  return this.engine.getKillswitch(name, switchKey);
1288
1350
  }
1351
+ /**
1352
+ * Record a conversion/metric event for the bound user. Derives the unit from
1353
+ * the resolved attribute bag (`user_id`, else `anonymous_id`) and delegates to
1354
+ * {@link Engine.track} — so an experiment is end-to-end Client-only (no need to
1355
+ * drop down to the Engine to log a conversion). Fire-and-forget; no-op in test
1356
+ * mode.
1357
+ */
1358
+ track(eventName, props) {
1359
+ const id = this.attributes.user_id ?? this.attributes.anonymous_id;
1360
+ if (id === void 0) return;
1361
+ this.engine.track(String(id), eventName, props);
1362
+ }
1363
+ /**
1364
+ * Emit an exposure event for `name` at this server-side decision point for the
1365
+ * bound user. Delegates to {@link Engine.logExposure} with the resolved
1366
+ * attribute bag (re-evaluates enrolment; no-op when the user isn't enrolled or
1367
+ * in test mode).
1368
+ */
1369
+ logExposure(name) {
1370
+ this.engine.logExposure(this.attributes, name);
1371
+ }
1289
1372
  };
1290
1373
  function dispatchSee(problem, consequence, extras, kind) {
1291
1374
  if (!_server) {
@@ -1309,7 +1392,10 @@ export {
1309
1392
  _murmur3ForTests,
1310
1393
  _resetConfigureForTests,
1311
1394
  _resetShipeasyServerForTests,
1395
+ clearOverrides,
1312
1396
  configure,
1397
+ configureForOffline,
1398
+ configureForTesting,
1313
1399
  configureShipeasyServer,
1314
1400
  createInMemoryStickyStore,
1315
1401
  fetchLabelsForSSR,
@@ -1319,6 +1405,10 @@ export {
1319
1405
  getShipeasyServerClient,
1320
1406
  i18n,
1321
1407
  isExpected,
1408
+ onChange,
1409
+ overrideConfig,
1410
+ overrideExperiment,
1411
+ overrideFlag,
1322
1412
  see,
1323
1413
  seeContext,
1324
1414
  shipeasy,
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // src/skill-cli.ts
5
+ var import_node_fs = require("fs");
6
+ var import_node_path = require("path");
7
+ var DEFAULT_DEST = ".claude/skills/shipeasy-typescript/SKILL.md";
8
+ function skillText() {
9
+ const candidates = [
10
+ (0, import_node_path.join)(__dirname, "..", "docs", "skill", "SKILL.md"),
11
+ (0, import_node_path.join)(__dirname, "..", "..", "docs", "skill", "SKILL.md")
12
+ ];
13
+ for (const p of candidates) {
14
+ try {
15
+ if ((0, import_node_fs.existsSync)(p)) return (0, import_node_fs.readFileSync)(p, "utf8");
16
+ } catch {
17
+ }
18
+ }
19
+ throw new Error("shipeasy-skill: bundled SKILL.md not found in the package.");
20
+ }
21
+ function install(dir, force) {
22
+ let dest = (0, import_node_path.resolve)(dir);
23
+ const looksLikeDir = (0, import_node_fs.existsSync)(dest) && (0, import_node_fs.statSync)(dest).isDirectory() || !/\.[^/]+$/.test(dest);
24
+ if (looksLikeDir) dest = (0, import_node_path.join)(dest, "SKILL.md");
25
+ if ((0, import_node_fs.existsSync)(dest) && !force) {
26
+ process.stderr.write(`shipeasy-skill: refusing to overwrite ${dest} \u2014 pass --force
27
+ `);
28
+ return 1;
29
+ }
30
+ (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(dest), { recursive: true });
31
+ (0, import_node_fs.writeFileSync)(dest, skillText(), "utf8");
32
+ process.stdout.write(`shipeasy-skill: installed the Shipeasy agent skill \u2192 ${dest}
33
+ `);
34
+ return 0;
35
+ }
36
+ function main(argv) {
37
+ const [cmd, ...rest] = argv;
38
+ if (cmd === "print") {
39
+ process.stdout.write(skillText());
40
+ return 0;
41
+ }
42
+ if (cmd === "install") {
43
+ let dir = DEFAULT_DEST;
44
+ let force = false;
45
+ for (let i = 0; i < rest.length; i++) {
46
+ if (rest[i] === "--force") force = true;
47
+ else if (rest[i] === "--dir") dir = rest[++i] ?? DEFAULT_DEST;
48
+ }
49
+ return install(dir, force);
50
+ }
51
+ process.stdout.write(
52
+ "shipeasy-skill \u2014 install the Shipeasy agent skill.\n\n shipeasy-skill install [--dir <path>] [--force]\n shipeasy-skill print\n"
53
+ );
54
+ return cmd && cmd !== "--help" && cmd !== "-h" ? 1 : 0;
55
+ }
56
+ process.exit(main(process.argv.slice(2)));
@@ -0,0 +1,145 @@
1
+ ---
2
+ name: shipeasy-typescript
3
+ description: Use Shipeasy (feature flags, configs, kill switches, A/B experiments, i18n) from TypeScript / JavaScript. Covers configure() + Client(user), getFlag/getConfig/getKillswitch/getExperiment, track, testing, OpenFeature, and the see() error reporter — server (@shipeasy/sdk/server) and browser (@shipeasy/sdk/client).
4
+ ---
5
+
6
+ # Shipeasy TypeScript SDK
7
+
8
+ `@shipeasy/sdk` — one package, two entrypoints: `@shipeasy/sdk/server` (Node /
9
+ Cloudflare Worker / Deno, **server** key) and `@shipeasy/sdk/client` (browser,
10
+ public **client** key). Everything works from vanilla JS.
11
+
12
+ > The documented surface is exactly **`configure()`** (setup) and the bound
13
+ > **`new Client(user)`** (use), plus the package-level helpers below. For deeper
14
+ > docs, fetch any page/snippet from the manifest at
15
+ > <https://shipeasy-ai.github.io/sdk/manifest.json> (raw page/snippet URLs below).
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ npm install @shipeasy/sdk
21
+ ```
22
+
23
+ ## Configure once, evaluate per user
24
+
25
+ ```ts
26
+ import { configure, Client } from "@shipeasy/sdk/server"; // or "@shipeasy/sdk/client"
27
+
28
+ configure({
29
+ apiKey: process.env.SHIPEASY_SERVER_KEY!, // browser: clientKey: process.env.NEXT_PUBLIC_SHIPEASY_CLIENT_KEY
30
+ attributes: (u: MyUser) => ({ user_id: u.id, plan: u.plan }), // optional; omit = identity
31
+ // poll: true // long-running server: keep flags fresh with a background poll (no engine.init() needed)
32
+ });
33
+
34
+ const flags = new Client(currentUser); // browser: await flags.ready() before first read
35
+
36
+ flags.getFlag("new_checkout"); // boolean (2nd arg = default if not-ready/not-found)
37
+ flags.getConfig<{ max: number }>("limits", { defaultValue: { max: 50 } });
38
+ flags.getKillswitch("payments"); // global on/off (not user-bound)
39
+ flags.getFlagDetail("new_checkout"); // { value, reason }
40
+ ```
41
+
42
+ `configure()` is first-config-wins and owns the fetch lifecycle (one-shot by
43
+ default; `poll: true` for a background refresh). Construct `new Client(user)` once
44
+ per user/request — it binds the user, so no method takes a user argument.
45
+ Full reference: <https://shipeasy-ai.github.io/sdk/pages/configuration.md> ·
46
+ <https://shipeasy-ai.github.io/sdk/pages/flags.md> ·
47
+ snippets <https://shipeasy-ai.github.io/sdk/snippets/release/flags.md> ·
48
+ <https://shipeasy-ai.github.io/sdk/snippets/release/configs.md> ·
49
+ <https://shipeasy-ai.github.io/sdk/snippets/release/killswitches.md>
50
+
51
+ ## Experiments + track (Client-only, end to end)
52
+
53
+ ```ts
54
+ const flags = new Client(currentUser); // construct once per callsite
55
+ const { inExperiment, group, params } = flags.getExperiment("hero_cta", {
56
+ primary_label: "Sign up", // default params (control / not-enrolled)
57
+ });
58
+ render(params.primary_label);
59
+
60
+ flags.logExposure("hero_cta"); // record the exposure where you present it
61
+ flags.track("purchase", { value: 42 }); // record a conversion for the bound user
62
+ ```
63
+
64
+ `ExperimentResult = { inExperiment: boolean; group: string; params: P }`. Full
65
+ reference: <https://shipeasy-ai.github.io/sdk/pages/experiments.md> · track snippet
66
+ <https://shipeasy-ai.github.io/sdk/snippets/metrics/track.md>
67
+
68
+ ## i18n
69
+
70
+ Full i18n ships in this SDK. Wire the loader via the SSR bootstrap (no separate
71
+ init), then render with `i18n.t`:
72
+
73
+ ```ts
74
+ import { i18n } from "@shipeasy/sdk/client";
75
+ i18n.t("checkout.cta", "Place order");
76
+ i18n.t("cart.count", "{count} items", { count: cart.length });
77
+ ```
78
+
79
+ Full reference: <https://shipeasy-ai.github.io/sdk/pages/i18n.md> · snippets
80
+ <https://shipeasy-ai.github.io/sdk/snippets/i18n/setup.md> ·
81
+ <https://shipeasy-ai.github.io/sdk/snippets/i18n/render.md>
82
+
83
+ ## Error reporting (see)
84
+
85
+ ```ts
86
+ import { see } from "@shipeasy/sdk/server"; // or /client
87
+
88
+ try { await submitOrder(order); }
89
+ catch (e) { see(e).causes_the("checkout").to("use cached prices").extras({ order_id: order.id }); }
90
+
91
+ see.Violation("large query").causes_the("results").to("be trimmed").extras({ rows });
92
+ see.ControlFlowException(e).because("because it wasn't an encoded Foo"); // expected — reports nothing
93
+ ```
94
+
95
+ Fire-and-forget on the next microtask (no `.send()`). Don't catch what you can't
96
+ name a consequence for. You may `see()` then re-throw (links as `caused_by`). Full
97
+ reference: <https://shipeasy-ai.github.io/sdk/pages/error-reporting.md> · snippet
98
+ <https://shipeasy-ai.github.io/sdk/snippets/ops/see.md>
99
+
100
+ ## Testing — no network
101
+
102
+ ```ts
103
+ import { configureForTesting, configureForOffline, Client, overrideFlag, clearOverrides } from "@shipeasy/sdk/server";
104
+
105
+ // Seed values up front; reads go through the ordinary new Client(user). Replaces
106
+ // prior config, so each test can reconfigure freely.
107
+ configureForTesting({
108
+ flags: { new_checkout: true },
109
+ configs: { limits: { max: 50 } },
110
+ experiments: { hero_cta: ["treatment", { primary_label: "Buy now" }] },
111
+ });
112
+ const flags = new Client({ user_id: "u_1" });
113
+ flags.getFlag("new_checkout"); // true
114
+
115
+ overrideFlag("new_checkout", false); // flip on the spot
116
+ clearOverrides(); // drop every override (incl. the seed)
117
+
118
+ // Offline: evaluate the REAL rules from a snapshot or JSON file, no network.
119
+ configureForOffline({ path: "./shipeasy-snapshot.json" });
120
+ // or: configureForOffline({ snapshot: { flags, experiments }, flags: { new_checkout: true } });
121
+ ```
122
+
123
+ Full reference: <https://shipeasy-ai.github.io/sdk/pages/testing.md>
124
+
125
+ ## OpenFeature
126
+
127
+ ```ts
128
+ import { OpenFeature } from "@openfeature/server-sdk";
129
+ import { ShipeasyProvider } from "@shipeasy/sdk/openfeature-server"; // or /openfeature-web
130
+
131
+ // Assumes configure({ apiKey }) ran at startup — the no-arg provider resolves it.
132
+ await OpenFeature.setProviderAndWait(new ShipeasyProvider());
133
+ await OpenFeature.getClient().getBooleanValue("new_checkout", false, { targetingKey: "u1" });
134
+ ```
135
+
136
+ Full reference: <https://shipeasy-ai.github.io/sdk/pages/openfeature.md>
137
+
138
+ ## Advanced
139
+
140
+ `privateAttributes`, `bucketBy` (custom bucketing unit), `stickyBucketing`
141
+ (browser: on by default, `__se_sticky` cookie), manual exposure
142
+ (`getExperiment(..., { logExposure: false })` + `flags.logExposure(name)`),
143
+ `onChange(cb)` (requires `configure({ poll: true })`) / browser `subscribe()`.
144
+ Devtools overlay: `Shift+Alt+S` or `?se=1`. Full reference:
145
+ <https://shipeasy-ai.github.io/sdk/pages/advanced.md>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipeasy/sdk",
3
- "version": "6.0.0",
3
+ "version": "6.2.0",
4
4
  "description": "Shipeasy SDK — feature gates, runtime configs, experiments, and metrics for the Shipeasy hosted service.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://shipeasy.ai",
@@ -59,12 +59,17 @@
59
59
  },
60
60
  "./templates/*": "./templates/*.js"
61
61
  },
62
+ "bin": {
63
+ "shipeasy-skill": "./dist/skill-cli.js"
64
+ },
62
65
  "files": [
63
66
  "dist/server/",
64
67
  "dist/client/",
65
68
  "dist/next/",
66
69
  "dist/openfeature-server/",
67
70
  "dist/openfeature-web/",
71
+ "dist/skill-cli.js",
72
+ "docs/skill/SKILL.md",
68
73
  "templates/",
69
74
  "LICENSE",
70
75
  "README.md"
@@ -74,6 +79,7 @@
74
79
  "type-check": "tsc --noEmit",
75
80
  "test": "vitest run",
76
81
  "test:watch": "vitest",
82
+ "gen:readme": "node scripts/gen-readme.mjs",
77
83
  "publish-loader": "wrangler r2 object put shipeasy-sdk/loader-v$npm_package_version.js --file=dist/loader/loader.global.js --content-type 'application/javascript; charset=utf-8' --cache-control 'public, max-age=31536000, immutable' --remote && wrangler r2 object put shipeasy-sdk/loader.js --file=dist/loader/loader.global.js --content-type 'application/javascript; charset=utf-8' --cache-control 'public, max-age=300' --remote"
78
84
  },
79
85
  "dependencies": {