@guidelytech/flags 0.7.0 → 0.8.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 +48 -1
- package/dist/chunk-2RZKAT37.js +1085 -0
- package/dist/chunk-2RZKAT37.js.map +1 -0
- package/dist/cli.cjs +186 -96
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +121 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -5
- package/dist/index.d.ts +46 -5
- package/dist/index.js +15 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -34,6 +34,11 @@ __export(index_exports, {
|
|
|
34
34
|
InMemoryStickyBucketStore: () => InMemoryStickyBucketStore,
|
|
35
35
|
LocalStorageStickyBucketStore: () => LocalStorageStickyBucketStore,
|
|
36
36
|
NUMBER_OPERATORS: () => NUMBER_OPERATORS,
|
|
37
|
+
OVERRIDES_ENV: () => OVERRIDES_ENV,
|
|
38
|
+
OVERRIDES_FILE_ENV: () => OVERRIDES_FILE_ENV,
|
|
39
|
+
OVERRIDES_GLOBAL: () => OVERRIDES_GLOBAL,
|
|
40
|
+
OVERRIDE_BEHAVIOUR_ENV: () => OVERRIDE_BEHAVIOUR_ENV,
|
|
41
|
+
OVERRIDE_BEHAVIOUR_GLOBAL: () => OVERRIDE_BEHAVIOUR_GLOBAL,
|
|
37
42
|
PRESENCE_OPERATORS: () => PRESENCE_OPERATORS,
|
|
38
43
|
SEMVER_OPERATORS: () => SEMVER_OPERATORS,
|
|
39
44
|
TEXT_OPERATORS: () => TEXT_OPERATORS,
|
|
@@ -56,8 +61,10 @@ __export(index_exports, {
|
|
|
56
61
|
jsonEquals: () => jsonEquals,
|
|
57
62
|
lookupAttribute: () => lookupAttribute,
|
|
58
63
|
numberOf: () => numberOf,
|
|
64
|
+
parseOverrides: () => parseOverrides,
|
|
59
65
|
parseSemver: () => parseSemver,
|
|
60
66
|
parseUrlOverrides: () => parseUrlOverrides,
|
|
67
|
+
readDevOverrides: () => readDevOverrides,
|
|
61
68
|
sha256Hex: () => sha256Hex,
|
|
62
69
|
stableStringify: () => stableStringify,
|
|
63
70
|
textOf: () => textOf
|
|
@@ -893,14 +900,25 @@ function compact(attrs) {
|
|
|
893
900
|
return out;
|
|
894
901
|
}
|
|
895
902
|
function parseUrlOverrides(search, param = "guidely_flags") {
|
|
896
|
-
const out = {};
|
|
897
903
|
let raw = null;
|
|
898
904
|
try {
|
|
899
905
|
raw = new URLSearchParams(search.startsWith("?") ? search.slice(1) : search).get(param);
|
|
900
906
|
} catch {
|
|
901
|
-
return
|
|
907
|
+
return {};
|
|
908
|
+
}
|
|
909
|
+
return raw && parseOverrides(raw) || {};
|
|
910
|
+
}
|
|
911
|
+
function parseOverrides(text) {
|
|
912
|
+
const raw = text.trim();
|
|
913
|
+
if (raw.startsWith("{")) {
|
|
914
|
+
try {
|
|
915
|
+
const obj = JSON.parse(raw);
|
|
916
|
+
return obj && typeof obj === "object" && !Array.isArray(obj) ? obj : null;
|
|
917
|
+
} catch {
|
|
918
|
+
return null;
|
|
919
|
+
}
|
|
902
920
|
}
|
|
903
|
-
|
|
921
|
+
const out = {};
|
|
904
922
|
const items = [];
|
|
905
923
|
let depth = 0;
|
|
906
924
|
let quoted = false;
|
|
@@ -913,7 +931,7 @@ function parseUrlOverrides(search, param = "guidely_flags") {
|
|
|
913
931
|
} else if (c === '"') quoted = true;
|
|
914
932
|
else if (c === "[" || c === "{") depth++;
|
|
915
933
|
else if (c === "]" || c === "}") depth = Math.max(0, depth - 1);
|
|
916
|
-
else if (c === "," && depth === 0) {
|
|
934
|
+
else if ((c === "," || c === "\n") && depth === 0) {
|
|
917
935
|
items.push(raw.slice(start, i));
|
|
918
936
|
start = i + 1;
|
|
919
937
|
}
|
|
@@ -923,12 +941,12 @@ function parseUrlOverrides(search, param = "guidely_flags") {
|
|
|
923
941
|
const sep = item.indexOf(":");
|
|
924
942
|
if (sep <= 0) continue;
|
|
925
943
|
const key = item.slice(0, sep).trim();
|
|
926
|
-
const
|
|
944
|
+
const text2 = item.slice(sep + 1).trim();
|
|
927
945
|
if (!key) continue;
|
|
928
946
|
try {
|
|
929
|
-
out[key] = JSON.parse(
|
|
947
|
+
out[key] = JSON.parse(text2);
|
|
930
948
|
} catch {
|
|
931
|
-
out[key] =
|
|
949
|
+
out[key] = text2;
|
|
932
950
|
}
|
|
933
951
|
}
|
|
934
952
|
return out;
|
|
@@ -947,6 +965,74 @@ function timer(fn, ms) {
|
|
|
947
965
|
return t;
|
|
948
966
|
}
|
|
949
967
|
|
|
968
|
+
// src/dev-overrides.ts
|
|
969
|
+
var OVERRIDES_ENV = "GUIDELY_FLAG_OVERRIDES";
|
|
970
|
+
var OVERRIDES_FILE_ENV = "GUIDELY_FLAG_OVERRIDES_FILE";
|
|
971
|
+
var OVERRIDE_BEHAVIOUR_ENV = "GUIDELY_FLAG_OVERRIDE_BEHAVIOUR";
|
|
972
|
+
var OVERRIDES_GLOBAL = "__GUIDELY_FLAG_OVERRIDES__";
|
|
973
|
+
var OVERRIDE_BEHAVIOUR_GLOBAL = "__GUIDELY_FLAG_OVERRIDE_BEHAVIOUR__";
|
|
974
|
+
var BEHAVIOURS = ["localOnly", "localOverRemote", "remoteOverLocal"];
|
|
975
|
+
var runtime = () => globalThis;
|
|
976
|
+
function env(name) {
|
|
977
|
+
const g = runtime();
|
|
978
|
+
try {
|
|
979
|
+
const v = g.process?.env?.[name];
|
|
980
|
+
if (v !== void 0) return v;
|
|
981
|
+
} catch {
|
|
982
|
+
}
|
|
983
|
+
try {
|
|
984
|
+
return g.Deno?.env?.get(name);
|
|
985
|
+
} catch {
|
|
986
|
+
return void 0;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
function readFile(path) {
|
|
990
|
+
const g = runtime();
|
|
991
|
+
const fs = g.process?.getBuiltinModule?.("node:fs");
|
|
992
|
+
if (fs) return fs.readFileSync(path, "utf8");
|
|
993
|
+
if (g.Deno?.readTextFileSync) return g.Deno.readTextFileSync(path);
|
|
994
|
+
throw new Error("this runtime cannot read files");
|
|
995
|
+
}
|
|
996
|
+
function behaviourOf(raw, source, out) {
|
|
997
|
+
if (typeof raw !== "string" || !raw.trim()) return;
|
|
998
|
+
const b = raw.trim();
|
|
999
|
+
if (BEHAVIOURS.includes(b)) {
|
|
1000
|
+
out.behaviour = b;
|
|
1001
|
+
out.sources.push(source);
|
|
1002
|
+
} else out.errors.push(`${source} must be localOnly, localOverRemote or remoteOverLocal (got "${raw}"); ignoring it.`);
|
|
1003
|
+
}
|
|
1004
|
+
function merge(text, source, out) {
|
|
1005
|
+
if (text === void 0 || !text.trim()) return;
|
|
1006
|
+
const values = parseOverrides(text);
|
|
1007
|
+
if (!values) return void out.errors.push(`${source} is not a JSON object or a key:value list; ignoring it.`);
|
|
1008
|
+
if (!Object.keys(values).length) return;
|
|
1009
|
+
Object.assign(out.values, values);
|
|
1010
|
+
out.sources.push(source);
|
|
1011
|
+
}
|
|
1012
|
+
function readDevOverrides() {
|
|
1013
|
+
const out = { values: {}, sources: [], errors: [] };
|
|
1014
|
+
const file = env(OVERRIDES_FILE_ENV);
|
|
1015
|
+
if (file?.trim()) {
|
|
1016
|
+
try {
|
|
1017
|
+
merge(readFile(file.trim()), `${OVERRIDES_FILE_ENV} (${file.trim()})`, out);
|
|
1018
|
+
} catch (err) {
|
|
1019
|
+
out.errors.push(`Could not read ${OVERRIDES_FILE_ENV} (${file.trim()}): ${err instanceof Error ? err.message : String(err)}; ignoring it.`);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
merge(env(OVERRIDES_ENV), OVERRIDES_ENV, out);
|
|
1023
|
+
const g = runtime()[OVERRIDES_GLOBAL];
|
|
1024
|
+
if (g && typeof g === "object" && !Array.isArray(g)) {
|
|
1025
|
+
if (Object.keys(g).length) {
|
|
1026
|
+
Object.assign(out.values, g);
|
|
1027
|
+
out.sources.push(OVERRIDES_GLOBAL);
|
|
1028
|
+
}
|
|
1029
|
+
} else if (typeof g === "string") merge(g, OVERRIDES_GLOBAL, out);
|
|
1030
|
+
else if (g !== void 0 && g !== null) out.errors.push(`${OVERRIDES_GLOBAL} must be an object or overrides text; ignoring it.`);
|
|
1031
|
+
behaviourOf(env(OVERRIDE_BEHAVIOUR_ENV), OVERRIDE_BEHAVIOUR_ENV, out);
|
|
1032
|
+
behaviourOf(runtime()[OVERRIDE_BEHAVIOUR_GLOBAL], OVERRIDE_BEHAVIOUR_GLOBAL, out);
|
|
1033
|
+
return out;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
950
1036
|
// src/evaluator.ts
|
|
951
1037
|
function typeMatches(value, fallback) {
|
|
952
1038
|
if (value === null || value === void 0) return false;
|
|
@@ -1037,7 +1123,7 @@ function createSseParser(onEvent) {
|
|
|
1037
1123
|
|
|
1038
1124
|
// src/client.ts
|
|
1039
1125
|
var FLAGS_SDK_NAME = "@guidelytech/flags";
|
|
1040
|
-
var FLAGS_SDK_VERSION = "0.
|
|
1126
|
+
var FLAGS_SDK_VERSION = "0.8.0";
|
|
1041
1127
|
var DEFAULT_FLAGS_API_URL = "https://guidelytech.com";
|
|
1042
1128
|
var SECRET_PREFIX = "gly_sk_";
|
|
1043
1129
|
var MAX_RESULT_CACHE = 20;
|
|
@@ -1084,6 +1170,7 @@ var FlagsClient = class {
|
|
|
1084
1170
|
__publicField(this, "context");
|
|
1085
1171
|
__publicField(this, "attributes");
|
|
1086
1172
|
__publicField(this, "overrides");
|
|
1173
|
+
__publicField(this, "devOverrides");
|
|
1087
1174
|
__publicField(this, "urlOverrides");
|
|
1088
1175
|
__publicField(this, "offline");
|
|
1089
1176
|
__publicField(this, "destroyed", false);
|
|
@@ -1125,12 +1212,13 @@ var FlagsClient = class {
|
|
|
1125
1212
|
this.log = o.logger === null ? {} : o.logger ?? { warn: (...a) => console.warn("[Guidely flags]", ...a), error: (...a) => console.error("[Guidely flags]", ...a) };
|
|
1126
1213
|
if (this.secret && !o.environment) throw new Error("[Guidely flags] `environment` (development | staging | production) is required with a gly_sk_ key.");
|
|
1127
1214
|
if (this.secret && isBrowser()) this.log.warn?.("A gly_sk_ secret key is being used in a browser. Secret keys belong on servers; use the environment's public pk_ key here.");
|
|
1215
|
+
const dev = o.devOverrides === false ? null : readDevOverrides();
|
|
1128
1216
|
this.cfg = {
|
|
1129
1217
|
mode: o.pollingMode ?? "auto",
|
|
1130
1218
|
pollMs: Math.max(1, o.pollIntervalSeconds ?? 60) * 1e3,
|
|
1131
1219
|
ttlMs: Math.max(0, o.cacheTtlSeconds ?? 60) * 1e3,
|
|
1132
1220
|
debounceMs: Math.max(0, o.remoteEvalDebounceMs ?? 50),
|
|
1133
|
-
behaviour: o.overrideBehaviour ?? "localOverRemote",
|
|
1221
|
+
behaviour: dev?.behaviour ?? o.overrideBehaviour ?? "localOverRemote",
|
|
1134
1222
|
trackExposures: o.trackExposures ?? !o.trackingCallback,
|
|
1135
1223
|
reportUsage: o.reportUsage ?? true
|
|
1136
1224
|
};
|
|
@@ -1139,10 +1227,16 @@ var FlagsClient = class {
|
|
|
1139
1227
|
this.context = compact(o.contextAttributes ?? {});
|
|
1140
1228
|
this.attributes = compact(o.attributes ?? {});
|
|
1141
1229
|
this.overrides = { ...o.overrides ?? {} };
|
|
1230
|
+
this.devOverrides = dev?.values ?? {};
|
|
1142
1231
|
this.urlOverrides = o.enableUrlOverrides && isBrowser() ? parseUrlOverrides(location.search) : {};
|
|
1143
1232
|
this.storage = o.storage === false || o.storage === null ? null : o.storage ?? defaultStorage();
|
|
1144
1233
|
this.storageKey = `guidely:flags:${key}${o.environment && this.secret ? `:${o.environment}` : ""}`;
|
|
1145
1234
|
this.readyPromise = new Promise((r) => this.resolveReady = r);
|
|
1235
|
+
if (dev) {
|
|
1236
|
+
dev.errors.forEach((e) => this.reportError(new Error(e)));
|
|
1237
|
+
const n = Object.keys(dev.values).length;
|
|
1238
|
+
if (dev.sources.length) this.log.warn?.(`Dev overrides from ${dev.sources.join(", ")}: ${n} flag${n === 1 ? "" : "s"}, behaviour ${this.cfg.behaviour}.`);
|
|
1239
|
+
}
|
|
1146
1240
|
this.getters = makeEvaluator(
|
|
1147
1241
|
(k) => this.resolve(k, this.getAttributes(), true),
|
|
1148
1242
|
() => this.all(this.getAttributes()),
|
|
@@ -1224,7 +1318,7 @@ var FlagsClient = class {
|
|
|
1224
1318
|
getAttributes() {
|
|
1225
1319
|
return { ...this.context, ...this.attributes };
|
|
1226
1320
|
}
|
|
1227
|
-
/** Replaces the overrides (`null` clears them). URL overrides stay. */
|
|
1321
|
+
/** Replaces the overrides (`null` clears them). Dev and URL overrides stay. */
|
|
1228
1322
|
setOverrides(overrides) {
|
|
1229
1323
|
this.overrides = { ...overrides ?? {} };
|
|
1230
1324
|
this.notify();
|
|
@@ -1301,6 +1395,7 @@ var FlagsClient = class {
|
|
|
1301
1395
|
// ---------------------------------------------------------------------------
|
|
1302
1396
|
overrideFor(key) {
|
|
1303
1397
|
if (Object.prototype.hasOwnProperty.call(this.urlOverrides, key)) return this.urlOverrides[key];
|
|
1398
|
+
if (Object.prototype.hasOwnProperty.call(this.devOverrides, key)) return this.devOverrides[key];
|
|
1304
1399
|
if (Object.prototype.hasOwnProperty.call(this.overrides, key)) return this.overrides[key];
|
|
1305
1400
|
return void 0;
|
|
1306
1401
|
}
|
|
@@ -1312,7 +1407,6 @@ var FlagsClient = class {
|
|
|
1312
1407
|
const forced = Object.prototype.hasOwnProperty.call(this.urlOverrides, key);
|
|
1313
1408
|
const override = ov === void 0 ? null : { key, value: ov, reason: "OVERRIDE", inExperiment: false };
|
|
1314
1409
|
if (override && (forced || this.cfg.behaviour !== "remoteOverLocal")) res = override;
|
|
1315
|
-
else if (this.cfg.behaviour === "localOnly") res = notFound(key);
|
|
1316
1410
|
else {
|
|
1317
1411
|
res = this.evaluateRemoteOrLocal(key, attrs);
|
|
1318
1412
|
if (override && res.reason === "FLAG_NOT_FOUND") res = override;
|
|
@@ -1333,11 +1427,9 @@ var FlagsClient = class {
|
|
|
1333
1427
|
return evaluateFlag(this.payload, key, attrs, { now: this.now(), stickyBuckets: this.o.stickyBucketService });
|
|
1334
1428
|
}
|
|
1335
1429
|
all(attrs) {
|
|
1336
|
-
const keys = /* @__PURE__ */ new Set([...Object.keys(this.overrides), ...Object.keys(this.urlOverrides)]);
|
|
1337
|
-
if (this.
|
|
1338
|
-
|
|
1339
|
-
else for (const k of Object.keys(this.payload?.flags ?? {})) keys.add(k);
|
|
1340
|
-
}
|
|
1430
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(this.overrides), ...Object.keys(this.devOverrides), ...Object.keys(this.urlOverrides)]);
|
|
1431
|
+
if (this.remote) for (const k of Object.keys(this.results?.flags ?? {})) keys.add(k);
|
|
1432
|
+
else for (const k of Object.keys(this.payload?.flags ?? {})) keys.add(k);
|
|
1341
1433
|
const out = {};
|
|
1342
1434
|
for (const k of keys) {
|
|
1343
1435
|
const v = this.resolve(k, attrs, false).value;
|
|
@@ -1584,7 +1676,7 @@ var FlagsClient = class {
|
|
|
1584
1676
|
async start() {
|
|
1585
1677
|
const boot = this.o.payload;
|
|
1586
1678
|
if (isEncryptedPayload(boot)) await this.applyRaw(boot, null);
|
|
1587
|
-
if (!this.payload && !this.results && !this.remote) await this.loadStored();
|
|
1679
|
+
if (!this.payload && !this.results && !this.remote && this.cfg.behaviour !== "localOnly") await this.loadStored();
|
|
1588
1680
|
if (this.destroyed) return;
|
|
1589
1681
|
const bootstrapped = !!(boot || this.o.remoteResults) && !!(this.payload || this.results);
|
|
1590
1682
|
if (this.cfg.behaviour === "localOnly" || this.offline || this.cfg.mode === "manual") return this.settle();
|
|
@@ -1730,6 +1822,11 @@ var FlagsClient = class {
|
|
|
1730
1822
|
}
|
|
1731
1823
|
takeBatch() {
|
|
1732
1824
|
if (!this.usage.size && !this.exposures.length) return null;
|
|
1825
|
+
if (this.cfg.behaviour === "localOnly") {
|
|
1826
|
+
this.usage.clear();
|
|
1827
|
+
this.exposures = [];
|
|
1828
|
+
return null;
|
|
1829
|
+
}
|
|
1733
1830
|
const batch = { sdk: this.o.sdk ?? { name: FLAGS_SDK_NAME, version: FLAGS_SDK_VERSION } };
|
|
1734
1831
|
if (this.usage.size) batch.usage = Array.from(this.usage.values());
|
|
1735
1832
|
if (this.exposures.length) batch.exposures = this.exposures;
|
|
@@ -1853,6 +1950,11 @@ var LocalStorageStickyBucketStore = class {
|
|
|
1853
1950
|
InMemoryStickyBucketStore,
|
|
1854
1951
|
LocalStorageStickyBucketStore,
|
|
1855
1952
|
NUMBER_OPERATORS,
|
|
1953
|
+
OVERRIDES_ENV,
|
|
1954
|
+
OVERRIDES_FILE_ENV,
|
|
1955
|
+
OVERRIDES_GLOBAL,
|
|
1956
|
+
OVERRIDE_BEHAVIOUR_ENV,
|
|
1957
|
+
OVERRIDE_BEHAVIOUR_GLOBAL,
|
|
1856
1958
|
PRESENCE_OPERATORS,
|
|
1857
1959
|
SEMVER_OPERATORS,
|
|
1858
1960
|
TEXT_OPERATORS,
|
|
@@ -1875,8 +1977,10 @@ var LocalStorageStickyBucketStore = class {
|
|
|
1875
1977
|
jsonEquals,
|
|
1876
1978
|
lookupAttribute,
|
|
1877
1979
|
numberOf,
|
|
1980
|
+
parseOverrides,
|
|
1878
1981
|
parseSemver,
|
|
1879
1982
|
parseUrlOverrides,
|
|
1983
|
+
readDevOverrides,
|
|
1880
1984
|
sha256Hex,
|
|
1881
1985
|
stableStringify,
|
|
1882
1986
|
textOf
|