@govplane/runtime-sdk 0.2.4 → 0.5.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 +400 -216
- package/dist/index.cjs +187 -91
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -14
- package/dist/index.d.ts +65 -14
- package/dist/index.js +187 -91
- package/dist/index.js.map +1 -1
- package/docs/README.md +89 -0
- package/docs/SUMMARY.md +34 -0
- package/docs/installation/GettingStarted.md +474 -0
- package/docs/reference/Configuration.md +198 -0
- package/docs/reference/TypesAndInterfaces.md +373 -0
- package/docs/usage/BundleLifecycle.md +209 -0
- package/docs/usage/ConditionalRules.md +251 -0
- package/docs/usage/ContextPolicy.md +196 -0
- package/docs/usage/CustomEffect.md +217 -0
- package/docs/usage/DecisionTrace.md +289 -0
- package/docs/usage/Effects.md +213 -0
- package/docs/usage/Evaluate.md +164 -0
- package/docs/usage/PolicyDefaults.md +220 -0
- package/package.json +3 -5
package/dist/index.cjs
CHANGED
|
@@ -30,19 +30,16 @@ __export(index_exports, {
|
|
|
30
30
|
});
|
|
31
31
|
module.exports = __toCommonJS(index_exports);
|
|
32
32
|
|
|
33
|
-
// src/client/RuntimeClient.ts
|
|
34
|
-
var import_undici = require("undici");
|
|
35
|
-
|
|
36
33
|
// src/engine/context.ts
|
|
37
34
|
var DEFAULT_CONTEXT_POLICY = {
|
|
38
35
|
allowedKeys: [
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
36
|
+
"plan",
|
|
37
|
+
"country",
|
|
38
|
+
"requestTier",
|
|
39
|
+
"feature",
|
|
40
|
+
"amount",
|
|
41
|
+
"isAuthenticated",
|
|
42
|
+
"role"
|
|
46
43
|
],
|
|
47
44
|
maxStringLen: 64,
|
|
48
45
|
maxArrayLen: 10,
|
|
@@ -70,19 +67,18 @@ var DEFAULT_POLICY = {
|
|
|
70
67
|
blockLikelyPiiKeys: true
|
|
71
68
|
};
|
|
72
69
|
function validateContext(ctx, policy) {
|
|
73
|
-
const allowed = new Set(policy.allowedKeys);
|
|
70
|
+
const allowed = new Set(policy.allowedKeys.map((k) => k.startsWith("ctx.") ? k.slice(4) : k));
|
|
74
71
|
const maxStringLen = policy.maxStringLen ?? DEFAULT_POLICY.maxStringLen;
|
|
75
72
|
const maxArrayLen = policy.maxArrayLen ?? DEFAULT_POLICY.maxArrayLen;
|
|
76
73
|
const blockLikelyPiiKeys = policy.blockLikelyPiiKeys ?? DEFAULT_POLICY.blockLikelyPiiKeys;
|
|
77
74
|
for (const [k, v] of Object.entries(ctx)) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
throw new Error(`Context key not allowed: ${path}`);
|
|
75
|
+
if (!allowed.has(k)) {
|
|
76
|
+
throw new Error(`Context key not allowed: ${k}`);
|
|
81
77
|
}
|
|
82
78
|
if (blockLikelyPiiKeys) {
|
|
83
79
|
for (const re of PII_KEY_PATTERNS) {
|
|
84
80
|
if (re.test(k)) {
|
|
85
|
-
throw new Error(`Context key looks like PII and is blocked: ${
|
|
81
|
+
throw new Error(`Context key looks like PII and is blocked: ${k}`);
|
|
86
82
|
}
|
|
87
83
|
}
|
|
88
84
|
}
|
|
@@ -90,25 +86,26 @@ function validateContext(ctx, policy) {
|
|
|
90
86
|
const t = typeof v;
|
|
91
87
|
if (t === "boolean" || t === "number") continue;
|
|
92
88
|
if (t === "string") {
|
|
93
|
-
if (v.length > maxStringLen) throw new Error(`Context value too long: ${
|
|
89
|
+
if (v.length > maxStringLen) throw new Error(`Context value too long: ${k}`);
|
|
94
90
|
continue;
|
|
95
91
|
}
|
|
96
92
|
if (Array.isArray(v)) {
|
|
97
|
-
if (v.length > maxArrayLen) throw new Error(`Context array too long: ${
|
|
93
|
+
if (v.length > maxArrayLen) throw new Error(`Context array too long: ${k}`);
|
|
98
94
|
for (const it of v) {
|
|
99
|
-
if (typeof it !== "string") throw new Error(`Invalid array value type: ${
|
|
100
|
-
if (it.length > maxStringLen) throw new Error(`Invalid array value length: ${
|
|
95
|
+
if (typeof it !== "string") throw new Error(`Invalid array value type: ${k}`);
|
|
96
|
+
if (it.length > maxStringLen) throw new Error(`Invalid array value length: ${k}`);
|
|
101
97
|
}
|
|
102
98
|
continue;
|
|
103
99
|
}
|
|
104
|
-
throw new Error(`Invalid context type for ${
|
|
100
|
+
throw new Error(`Invalid context type for ${k}`);
|
|
105
101
|
}
|
|
106
102
|
}
|
|
107
103
|
|
|
108
104
|
// src/engine/when.ts
|
|
109
|
-
function getPath(
|
|
110
|
-
const
|
|
111
|
-
|
|
105
|
+
function getPath(ctx, path) {
|
|
106
|
+
const normalizedPath = path.startsWith("ctx.") ? path.slice(4) : path;
|
|
107
|
+
const parts = normalizedPath.split(".");
|
|
108
|
+
let cur = ctx;
|
|
112
109
|
for (const p of parts) {
|
|
113
110
|
if (!cur || typeof cur !== "object") return void 0;
|
|
114
111
|
cur = cur[p];
|
|
@@ -117,30 +114,40 @@ function getPath(obj, path) {
|
|
|
117
114
|
}
|
|
118
115
|
function evalWhen(node, ctx) {
|
|
119
116
|
switch (node.op) {
|
|
120
|
-
case "and":
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
case "
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
117
|
+
case "and": {
|
|
118
|
+
const children = node.conditions ?? node.args ?? [];
|
|
119
|
+
return children.every((n) => evalWhen(n, ctx));
|
|
120
|
+
}
|
|
121
|
+
case "or": {
|
|
122
|
+
const children = node.conditions ?? node.args ?? [];
|
|
123
|
+
return children.some((n) => evalWhen(n, ctx));
|
|
124
|
+
}
|
|
125
|
+
case "not": {
|
|
126
|
+
const child = node.condition ?? node.arg;
|
|
127
|
+
return !evalWhen(child, ctx);
|
|
128
|
+
}
|
|
129
|
+
case "exists": {
|
|
130
|
+
const v = getPath(ctx, node.path);
|
|
131
|
+
return v !== void 0 && v !== null;
|
|
132
|
+
}
|
|
128
133
|
case "in": {
|
|
129
|
-
const v = getPath(
|
|
134
|
+
const v = getPath(ctx, node.path);
|
|
130
135
|
return node.values.some((x) => x === v);
|
|
131
136
|
}
|
|
132
137
|
case "eq":
|
|
133
|
-
return getPath(
|
|
138
|
+
return getPath(ctx, node.path) === node.value;
|
|
139
|
+
// `neq` is the canonical spelling; `ne` is kept for legacy bundles
|
|
140
|
+
case "neq":
|
|
134
141
|
case "ne":
|
|
135
|
-
return getPath(
|
|
142
|
+
return getPath(ctx, node.path) !== node.value;
|
|
136
143
|
case "gt":
|
|
137
|
-
return Number(getPath(
|
|
144
|
+
return Number(getPath(ctx, node.path)) > Number(node.value);
|
|
138
145
|
case "gte":
|
|
139
|
-
return Number(getPath(
|
|
146
|
+
return Number(getPath(ctx, node.path)) >= Number(node.value);
|
|
140
147
|
case "lt":
|
|
141
|
-
return Number(getPath(
|
|
148
|
+
return Number(getPath(ctx, node.path)) < Number(node.value);
|
|
142
149
|
case "lte":
|
|
143
|
-
return Number(getPath(
|
|
150
|
+
return Number(getPath(ctx, node.path)) <= Number(node.value);
|
|
144
151
|
default:
|
|
145
152
|
return false;
|
|
146
153
|
}
|
|
@@ -350,7 +357,7 @@ function createPolicyEngine(opts) {
|
|
|
350
357
|
policiesSeen: 0,
|
|
351
358
|
rulesSeen: 0,
|
|
352
359
|
matched: 0,
|
|
353
|
-
considered: { kill_switch: 0, deny: 0, throttle: 0, allow: 0 }
|
|
360
|
+
considered: { kill_switch: 0, deny: 0, throttle: 0, allow: 0, custom: 0 }
|
|
354
361
|
},
|
|
355
362
|
rules: []
|
|
356
363
|
} : null;
|
|
@@ -369,23 +376,24 @@ function createPolicyEngine(opts) {
|
|
|
369
376
|
const denies = [];
|
|
370
377
|
const throttles = [];
|
|
371
378
|
const allows = [];
|
|
379
|
+
const customs = [];
|
|
372
380
|
const policies = bundle.policies ?? [];
|
|
373
381
|
if (wantTrace) traceBase.summary.policiesSeen = policies.length;
|
|
374
382
|
for (const p of policies) {
|
|
375
383
|
const policyKey = String(p.policyKey ?? "");
|
|
376
384
|
const rules = p.rules ?? [];
|
|
385
|
+
let policyHadMatch = false;
|
|
377
386
|
for (const r of rules) {
|
|
378
387
|
if (wantTrace) traceBase.summary.rulesSeen += 1;
|
|
379
388
|
const ruleId = String(r?.id ?? "");
|
|
380
389
|
const priority = Number(r?.priority ?? 0);
|
|
381
|
-
const effectType = safeEffectType(r?.effect);
|
|
382
390
|
if (r?.status !== "active") {
|
|
383
391
|
if (wantTrace) {
|
|
384
392
|
traceBase.rules.push({
|
|
385
393
|
policyKey,
|
|
386
394
|
ruleId,
|
|
387
395
|
priority,
|
|
388
|
-
effectType,
|
|
396
|
+
effectType: safeEffectType(r?.effect),
|
|
389
397
|
matched: false,
|
|
390
398
|
discardedReason: "disabled"
|
|
391
399
|
});
|
|
@@ -398,26 +406,39 @@ function createPolicyEngine(opts) {
|
|
|
398
406
|
policyKey,
|
|
399
407
|
ruleId,
|
|
400
408
|
priority,
|
|
401
|
-
effectType,
|
|
409
|
+
effectType: safeEffectType(r?.effect),
|
|
402
410
|
matched: false,
|
|
403
411
|
discardedReason: "target_mismatch"
|
|
404
412
|
});
|
|
405
413
|
}
|
|
406
414
|
continue;
|
|
407
415
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
416
|
+
let resolvedEffect;
|
|
417
|
+
if (r.when !== void 0) {
|
|
418
|
+
const whenResult = evalWhen(r.when, ctx);
|
|
419
|
+
if (whenResult) {
|
|
420
|
+
resolvedEffect = r.thenEffect ?? r.effect;
|
|
421
|
+
} else {
|
|
422
|
+
if (r.elseEffect) {
|
|
423
|
+
resolvedEffect = r.elseEffect;
|
|
424
|
+
} else {
|
|
425
|
+
if (wantTrace) {
|
|
426
|
+
traceBase.rules.push({
|
|
427
|
+
policyKey,
|
|
428
|
+
ruleId,
|
|
429
|
+
priority,
|
|
430
|
+
effectType: safeEffectType(r?.effect),
|
|
431
|
+
matched: false,
|
|
432
|
+
discardedReason: "when_false"
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
418
437
|
}
|
|
419
|
-
|
|
438
|
+
} else {
|
|
439
|
+
resolvedEffect = r.effect;
|
|
420
440
|
}
|
|
441
|
+
const effectType = safeEffectType(resolvedEffect);
|
|
421
442
|
if (!effectType) {
|
|
422
443
|
if (wantTrace) {
|
|
423
444
|
traceBase.rules.push({
|
|
@@ -431,6 +452,7 @@ function createPolicyEngine(opts) {
|
|
|
431
452
|
}
|
|
432
453
|
continue;
|
|
433
454
|
}
|
|
455
|
+
policyHadMatch = true;
|
|
434
456
|
if (wantTrace) {
|
|
435
457
|
traceBase.summary.matched += 1;
|
|
436
458
|
traceBase.rules.push({
|
|
@@ -441,7 +463,7 @@ function createPolicyEngine(opts) {
|
|
|
441
463
|
matched: true
|
|
442
464
|
});
|
|
443
465
|
}
|
|
444
|
-
const m = { policyKey, ruleId, priority, effect:
|
|
466
|
+
const m = { policyKey, ruleId, priority, effect: resolvedEffect };
|
|
445
467
|
if (effectType === "kill_switch") {
|
|
446
468
|
kills.push(m);
|
|
447
469
|
if (wantTrace) traceBase.summary.considered.kill_switch += 1;
|
|
@@ -454,16 +476,55 @@ function createPolicyEngine(opts) {
|
|
|
454
476
|
} else if (effectType === "allow") {
|
|
455
477
|
allows.push(m);
|
|
456
478
|
if (wantTrace) traceBase.summary.considered.allow += 1;
|
|
479
|
+
} else if (effectType === "custom") {
|
|
480
|
+
customs.push(m);
|
|
481
|
+
if (wantTrace) traceBase.summary.considered.custom += 1;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
if (!policyHadMatch) {
|
|
485
|
+
const defaults = p.defaults;
|
|
486
|
+
if (defaults?.effect) {
|
|
487
|
+
const defEffectType = String(defaults.effect);
|
|
488
|
+
let syntheticEffect;
|
|
489
|
+
if (defEffectType === "custom") {
|
|
490
|
+
syntheticEffect = { type: "custom", value: String(defaults.customEffect ?? "") };
|
|
491
|
+
} else if (defEffectType === "throttle" && defaults.throttle) {
|
|
492
|
+
syntheticEffect = { type: "throttle", throttle: defaults.throttle };
|
|
493
|
+
} else if (defEffectType === "kill_switch" && defaults.killSwitch) {
|
|
494
|
+
syntheticEffect = { type: "kill_switch", killSwitch: defaults.killSwitch };
|
|
495
|
+
} else if (defEffectType === "allow" || defEffectType === "deny") {
|
|
496
|
+
syntheticEffect = { type: defEffectType };
|
|
497
|
+
}
|
|
498
|
+
if (syntheticEffect) {
|
|
499
|
+
const dm = { policyKey, ruleId: "__default__", priority: -1, effect: syntheticEffect };
|
|
500
|
+
if (defEffectType === "kill_switch") {
|
|
501
|
+
kills.push(dm);
|
|
502
|
+
if (wantTrace) traceBase.summary.considered.kill_switch += 1;
|
|
503
|
+
} else if (defEffectType === "deny") {
|
|
504
|
+
denies.push(dm);
|
|
505
|
+
if (wantTrace) traceBase.summary.considered.deny += 1;
|
|
506
|
+
} else if (defEffectType === "throttle") {
|
|
507
|
+
throttles.push(dm);
|
|
508
|
+
if (wantTrace) traceBase.summary.considered.throttle += 1;
|
|
509
|
+
} else if (defEffectType === "allow") {
|
|
510
|
+
allows.push(dm);
|
|
511
|
+
if (wantTrace) traceBase.summary.considered.allow += 1;
|
|
512
|
+
} else if (defEffectType === "custom") {
|
|
513
|
+
customs.push(dm);
|
|
514
|
+
if (wantTrace) traceBase.summary.considered.custom += 1;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
457
517
|
}
|
|
458
518
|
}
|
|
459
519
|
}
|
|
460
520
|
if (kills.length) {
|
|
461
521
|
const w = kills.sort(byRuleOrder)[0];
|
|
522
|
+
const isDefault = w.ruleId === "__default__";
|
|
462
523
|
const decision2 = {
|
|
463
524
|
decision: "kill_switch",
|
|
464
|
-
reason: "rule",
|
|
525
|
+
reason: isDefault ? "default" : "rule",
|
|
465
526
|
policyKey: w.policyKey,
|
|
466
|
-
ruleId: w.ruleId,
|
|
527
|
+
ruleId: isDefault ? void 0 : w.ruleId,
|
|
467
528
|
killSwitch: w.effect.killSwitch
|
|
468
529
|
};
|
|
469
530
|
if (!wantTrace) return decision2;
|
|
@@ -477,11 +538,12 @@ function createPolicyEngine(opts) {
|
|
|
477
538
|
}
|
|
478
539
|
if (denies.length) {
|
|
479
540
|
const w = denies.sort(byRuleOrder)[0];
|
|
541
|
+
const isDefault = w.ruleId === "__default__";
|
|
480
542
|
const decision2 = {
|
|
481
543
|
decision: "deny",
|
|
482
|
-
reason: "rule",
|
|
544
|
+
reason: isDefault ? "default" : "rule",
|
|
483
545
|
policyKey: w.policyKey,
|
|
484
|
-
ruleId: w.ruleId
|
|
546
|
+
ruleId: isDefault ? void 0 : w.ruleId
|
|
485
547
|
};
|
|
486
548
|
if (!wantTrace) return decision2;
|
|
487
549
|
return {
|
|
@@ -501,11 +563,12 @@ function createPolicyEngine(opts) {
|
|
|
501
563
|
if (A.windowSeconds !== B.windowSeconds) return A.windowSeconds - B.windowSeconds;
|
|
502
564
|
return byRuleOrder(a, b);
|
|
503
565
|
})[0];
|
|
566
|
+
const isDefault = w.ruleId === "__default__";
|
|
504
567
|
const decision2 = {
|
|
505
568
|
decision: "throttle",
|
|
506
|
-
reason: "rule",
|
|
569
|
+
reason: isDefault ? "default" : "rule",
|
|
507
570
|
policyKey: w.policyKey,
|
|
508
|
-
ruleId: w.ruleId,
|
|
571
|
+
ruleId: isDefault ? void 0 : w.ruleId,
|
|
509
572
|
throttle: w.effect.throttle
|
|
510
573
|
};
|
|
511
574
|
if (!wantTrace) return decision2;
|
|
@@ -519,11 +582,12 @@ function createPolicyEngine(opts) {
|
|
|
519
582
|
}
|
|
520
583
|
if (allows.length) {
|
|
521
584
|
const w = allows.sort(byRuleOrder)[0];
|
|
585
|
+
const isDefault = w.ruleId === "__default__";
|
|
522
586
|
const decision2 = {
|
|
523
587
|
decision: "allow",
|
|
524
|
-
reason: "rule",
|
|
588
|
+
reason: isDefault ? "default" : "rule",
|
|
525
589
|
policyKey: w.policyKey,
|
|
526
|
-
ruleId: w.ruleId
|
|
590
|
+
ruleId: isDefault ? void 0 : w.ruleId
|
|
527
591
|
};
|
|
528
592
|
if (!wantTrace) return decision2;
|
|
529
593
|
return {
|
|
@@ -534,6 +598,34 @@ function createPolicyEngine(opts) {
|
|
|
534
598
|
}
|
|
535
599
|
};
|
|
536
600
|
}
|
|
601
|
+
if (customs.length) {
|
|
602
|
+
const w = customs.sort(byRuleOrder)[0];
|
|
603
|
+
const isDefault = w.ruleId === "__default__";
|
|
604
|
+
const rawValue = String(w.effect.value ?? "");
|
|
605
|
+
let parsedValue;
|
|
606
|
+
if (opts.parseCustomEffect) {
|
|
607
|
+
try {
|
|
608
|
+
parsedValue = JSON.parse(rawValue);
|
|
609
|
+
} catch {
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
const decision2 = {
|
|
613
|
+
decision: "custom",
|
|
614
|
+
reason: isDefault ? "default" : "rule",
|
|
615
|
+
policyKey: w.policyKey,
|
|
616
|
+
ruleId: isDefault ? void 0 : w.ruleId,
|
|
617
|
+
value: rawValue,
|
|
618
|
+
...parsedValue !== void 0 ? { parsedValue } : {}
|
|
619
|
+
};
|
|
620
|
+
if (!wantTrace) return decision2;
|
|
621
|
+
return {
|
|
622
|
+
...decision2,
|
|
623
|
+
trace: {
|
|
624
|
+
...traceBase,
|
|
625
|
+
winner: { policyKey: w.policyKey, ruleId: w.ruleId, effectType: "custom", priority: w.priority }
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
}
|
|
537
629
|
const decision = { decision: "deny", reason: "default" };
|
|
538
630
|
if (!wantTrace) return decision;
|
|
539
631
|
return {
|
|
@@ -637,6 +729,7 @@ var RuntimeClient = class {
|
|
|
637
729
|
installedSignalHandler = false;
|
|
638
730
|
sigHandler;
|
|
639
731
|
constructor(config) {
|
|
732
|
+
console.log("[RuntimeClient] initializing with config");
|
|
640
733
|
this.cfg = {
|
|
641
734
|
...config,
|
|
642
735
|
pollMs: config.pollMs ?? 5e3,
|
|
@@ -655,6 +748,7 @@ var RuntimeClient = class {
|
|
|
655
748
|
getBundle: () => this.cache.bundle,
|
|
656
749
|
validateContext: config.engine?.validateContext !== false,
|
|
657
750
|
contextPolicy: config.engine?.contextPolicy,
|
|
751
|
+
parseCustomEffect: config.engine?.parseCustomEffect,
|
|
658
752
|
traceDefaults: config.trace?.defaults,
|
|
659
753
|
traceSink: config.trace?.onDecisionTrace,
|
|
660
754
|
traceSinkAsync: config.trace?.onDecisionTraceAsync,
|
|
@@ -851,34 +945,38 @@ var RuntimeClient = class {
|
|
|
851
945
|
}
|
|
852
946
|
bundleUrl() {
|
|
853
947
|
const u = new URL(this.cfg.baseUrl);
|
|
854
|
-
u.pathname = "/v1/runtime/bundle";
|
|
855
|
-
u.searchParams.set("projectId", this.cfg.projectId);
|
|
856
|
-
u.searchParams.set("env", this.cfg.env);
|
|
857
948
|
return u.toString();
|
|
858
949
|
}
|
|
859
950
|
commonHeaders(extra) {
|
|
860
951
|
return {
|
|
861
|
-
Authorization:
|
|
952
|
+
Authorization: this.cfg.runtimeKey,
|
|
862
953
|
"User-Agent": this.cfg.userAgent ?? "govplane-runtime-sdk/0.x",
|
|
863
954
|
...extra
|
|
864
955
|
};
|
|
865
956
|
}
|
|
957
|
+
async fetchWithTimeout(url, init) {
|
|
958
|
+
const ac = new AbortController();
|
|
959
|
+
const t = setTimeout(() => ac.abort(), this.cfg.timeoutMs);
|
|
960
|
+
try {
|
|
961
|
+
return await fetch(url, { ...init, signal: ac.signal });
|
|
962
|
+
} finally {
|
|
963
|
+
clearTimeout(t);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
866
966
|
async headBundle() {
|
|
867
967
|
const url = this.bundleUrl();
|
|
868
|
-
const
|
|
968
|
+
const res = await this.fetchWithTimeout(url, {
|
|
869
969
|
method: "HEAD",
|
|
870
|
-
headers: this.commonHeaders()
|
|
871
|
-
bodyTimeout: this.cfg.timeoutMs,
|
|
872
|
-
headersTimeout: this.cfg.timeoutMs
|
|
970
|
+
headers: this.commonHeaders()
|
|
873
971
|
});
|
|
874
|
-
|
|
875
|
-
if (
|
|
876
|
-
if (
|
|
877
|
-
if (
|
|
878
|
-
const etag = headers
|
|
972
|
+
console.log(`[RuntimeClient] HEAD bundle status=${res.status}`);
|
|
973
|
+
if (res.status === 401 || res.status === 403) throw new Error(`Unauthorized (${res.status})`);
|
|
974
|
+
if (res.status >= 500) throw new Error(`Runtime server error (${res.status})`);
|
|
975
|
+
if (res.status !== 200 && res.status !== 304) return this.cache.meta;
|
|
976
|
+
const etag = res.headers.get("etag") ?? "";
|
|
879
977
|
if (!etag) return this.cache.meta;
|
|
880
|
-
const bundleVersionRaw = headers
|
|
881
|
-
const updatedAt = headers
|
|
978
|
+
const bundleVersionRaw = res.headers.get("x-gp-bundle-version") ?? void 0;
|
|
979
|
+
const updatedAt = res.headers.get("x-gp-updated-at") ?? void 0;
|
|
882
980
|
return {
|
|
883
981
|
etag,
|
|
884
982
|
bundleVersion: bundleVersionRaw ? Number(bundleVersionRaw) : void 0,
|
|
@@ -888,26 +986,24 @@ var RuntimeClient = class {
|
|
|
888
986
|
async getBundle() {
|
|
889
987
|
const url = this.bundleUrl();
|
|
890
988
|
const ifNoneMatch = this.cache.meta?.etag;
|
|
891
|
-
const
|
|
989
|
+
const res = await this.fetchWithTimeout(url, {
|
|
892
990
|
method: "GET",
|
|
893
|
-
headers: this.commonHeaders(ifNoneMatch ? { "If-None-Match": ifNoneMatch } : void 0)
|
|
894
|
-
bodyTimeout: this.cfg.timeoutMs,
|
|
895
|
-
headersTimeout: this.cfg.timeoutMs
|
|
991
|
+
headers: this.commonHeaders(ifNoneMatch ? { "If-None-Match": ifNoneMatch } : void 0)
|
|
896
992
|
});
|
|
897
|
-
const txt = await
|
|
898
|
-
if (
|
|
899
|
-
const etag2 = headers
|
|
900
|
-
const bundleVersionRaw2 = headers
|
|
901
|
-
const updatedAt2 = headers
|
|
993
|
+
const txt = await res.text();
|
|
994
|
+
if (res.status === 304) {
|
|
995
|
+
const etag2 = res.headers.get("etag") ?? ifNoneMatch ?? "";
|
|
996
|
+
const bundleVersionRaw2 = res.headers.get("x-gp-bundle-version") ?? void 0;
|
|
997
|
+
const updatedAt2 = res.headers.get("x-gp-updated-at") ?? void 0;
|
|
902
998
|
const meta2 = etag2 ? { etag: etag2, bundleVersion: bundleVersionRaw2 ? Number(bundleVersionRaw2) : void 0, updatedAt: updatedAt2 } : this.cache.meta;
|
|
903
999
|
if (meta2) this.cache.meta = meta2;
|
|
904
1000
|
return { changed: false, meta: meta2 };
|
|
905
1001
|
}
|
|
906
|
-
if (
|
|
907
|
-
if (
|
|
908
|
-
const etag = headers
|
|
909
|
-
const bundleVersionRaw = headers
|
|
910
|
-
const updatedAt = headers
|
|
1002
|
+
if (res.status === 401 || res.status === 403) throw new Error(`Unauthorized (${res.status})`);
|
|
1003
|
+
if (res.status >= 400) throw new Error(`Runtime HTTP error (${res.status}): ${txt.slice(0, 200)}`);
|
|
1004
|
+
const etag = res.headers.get("etag") ?? "";
|
|
1005
|
+
const bundleVersionRaw = res.headers.get("x-gp-bundle-version") ?? void 0;
|
|
1006
|
+
const updatedAt = res.headers.get("x-gp-updated-at") ?? void 0;
|
|
911
1007
|
const meta = {
|
|
912
1008
|
etag: etag || (ifNoneMatch ?? ""),
|
|
913
1009
|
bundleVersion: bundleVersionRaw ? Number(bundleVersionRaw) : void 0,
|