@holonograph/client 0.1.0 → 0.3.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/dist/index.d.ts +397 -212
- package/dist/index.js +795 -15
- package/package.json +2 -10
package/dist/index.js
CHANGED
|
@@ -4008,6 +4008,8 @@ var ALL_VISIBILITIES = ["transparent", "announced", "opaque"];
|
|
|
4008
4008
|
var VisibilityAttributeSchema = external_exports.enum(ALL_VISIBILITIES);
|
|
4009
4009
|
var ALL_SUBSTRATE_COLUMN_SEMANTICS = ["deployment-state", "diagnostic"];
|
|
4010
4010
|
var SubstrateColumnSemanticSchema = external_exports.enum(ALL_SUBSTRATE_COLUMN_SEMANTICS);
|
|
4011
|
+
var ALL_EMISSION_EXPECTATIONS = ["always", "conditional"];
|
|
4012
|
+
var EmissionExpectationSchema = external_exports.enum(ALL_EMISSION_EXPECTATIONS);
|
|
4011
4013
|
var SubstrateColumnSchema = external_exports.object({
|
|
4012
4014
|
id: external_exports.string().min(1).max(128),
|
|
4013
4015
|
control: ControlAttributeSchema,
|
|
@@ -4016,7 +4018,9 @@ var SubstrateColumnSchema = external_exports.object({
|
|
|
4016
4018
|
|
|
4017
4019
|
semantic: SubstrateColumnSemanticSchema.default("deployment-state"),
|
|
4018
4020
|
|
|
4019
|
-
values: external_exports.array(external_exports.string().min(1)).min(1).optional()
|
|
4021
|
+
values: external_exports.array(external_exports.string().min(1)).min(1).optional(),
|
|
4022
|
+
|
|
4023
|
+
emissionExpectation: EmissionExpectationSchema.optional()
|
|
4020
4024
|
});
|
|
4021
4025
|
var SubstrateSnapshotSchema = external_exports.object({
|
|
4022
4026
|
lensVersion: LensVersionIdSchema,
|
|
@@ -4585,7 +4589,9 @@ external_exports.object({
|
|
|
4585
4589
|
|
|
4586
4590
|
tools: external_exports.array(CapabilityToolDefSchema).default([]),
|
|
4587
4591
|
|
|
4588
|
-
proseRules: external_exports.array(external_exports.string().min(1)).default([])
|
|
4592
|
+
proseRules: external_exports.array(external_exports.string().min(1)).default([]),
|
|
4593
|
+
|
|
4594
|
+
availableColumns: external_exports.array(external_exports.string().min(1)).optional()
|
|
4589
4595
|
});
|
|
4590
4596
|
external_exports.object({
|
|
4591
4597
|
|
|
@@ -4685,7 +4691,9 @@ var LightSnapshotSchema = external_exports.object({
|
|
|
4685
4691
|
|
|
4686
4692
|
vendorMetadata: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
4687
4693
|
|
|
4688
|
-
rawRequestOmitted: external_exports.boolean().optional()
|
|
4694
|
+
rawRequestOmitted: external_exports.boolean().optional(),
|
|
4695
|
+
|
|
4696
|
+
rawResponsePresence: external_exports.enum(["present", "omitted-by-policy", "pending-attach", "failed-attach"]).optional()
|
|
4689
4697
|
});
|
|
4690
4698
|
var DimensionResultSchema = external_exports.object({
|
|
4691
4699
|
dimensionId: DimensionIdSchema,
|
|
@@ -4759,7 +4767,9 @@ var EvaluationEventKindSchema = external_exports.enum([
|
|
|
4759
4767
|
|
|
4760
4768
|
"analysis.eval-recommendation",
|
|
4761
4769
|
|
|
4762
|
-
"analysis.conformance-fixture"
|
|
4770
|
+
"analysis.conformance-fixture",
|
|
4771
|
+
|
|
4772
|
+
"analysis.emission-coverage"
|
|
4763
4773
|
]);
|
|
4764
4774
|
var AvailabilityTransitionDirectionSchema = external_exports.enum([
|
|
4765
4775
|
"reachable->unreachable",
|
|
@@ -4935,6 +4945,18 @@ var ConformanceFixtureRecordSchema = external_exports.object({
|
|
|
4935
4945
|
|
|
4936
4946
|
sourceSurfaceId: external_exports.string().min(1).optional()
|
|
4937
4947
|
});
|
|
4948
|
+
var EmissionCoverageSchema = external_exports.object({
|
|
4949
|
+
|
|
4950
|
+
streamId: external_exports.string().min(1),
|
|
4951
|
+
|
|
4952
|
+
observedOrdinals: external_exports.array(external_exports.number().int().nonnegative()),
|
|
4953
|
+
|
|
4954
|
+
sentinelHighWater: external_exports.number().int().nonnegative().nullable(),
|
|
4955
|
+
|
|
4956
|
+
finalSeen: external_exports.boolean(),
|
|
4957
|
+
|
|
4958
|
+
observedAtMs: external_exports.number().int().nonnegative()
|
|
4959
|
+
});
|
|
4938
4960
|
var EvaluationEventSchema = external_exports.object({
|
|
4939
4961
|
|
|
4940
4962
|
eventId: external_exports.string().min(1),
|
|
@@ -4965,31 +4987,36 @@ var EvaluationEventSchema = external_exports.object({
|
|
|
4965
4987
|
|
|
4966
4988
|
evalRecommendation: EvalRecommendationSchema.optional(),
|
|
4967
4989
|
|
|
4968
|
-
conformanceFixtureRecord: ConformanceFixtureRecordSchema.optional()
|
|
4990
|
+
conformanceFixtureRecord: ConformanceFixtureRecordSchema.optional(),
|
|
4991
|
+
|
|
4992
|
+
emissionCoverage: EmissionCoverageSchema.optional()
|
|
4969
4993
|
}).refine(
|
|
4970
4994
|
(e) => {
|
|
4971
4995
|
if (e.kind === "evaluation") {
|
|
4972
|
-
return e.light !== void 0 && e.substrate !== void 0 && e.surface !== void 0 && e.availability === void 0 && e.noiseFloorCalibration === void 0 && e.varianceDecomposition === void 0 && e.lifecycleTransition === void 0 && e.evalRecommendation === void 0 && e.conformanceFixtureRecord === void 0;
|
|
4996
|
+
return e.light !== void 0 && e.substrate !== void 0 && e.surface !== void 0 && e.availability === void 0 && e.noiseFloorCalibration === void 0 && e.varianceDecomposition === void 0 && e.lifecycleTransition === void 0 && e.evalRecommendation === void 0 && e.conformanceFixtureRecord === void 0 && e.emissionCoverage === void 0;
|
|
4973
4997
|
}
|
|
4974
4998
|
if (e.kind === "availability.transition") {
|
|
4975
|
-
return e.availability !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.noiseFloorCalibration === void 0 && e.varianceDecomposition === void 0 && e.lifecycleTransition === void 0 && e.evalRecommendation === void 0 && e.conformanceFixtureRecord === void 0;
|
|
4999
|
+
return e.availability !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.noiseFloorCalibration === void 0 && e.varianceDecomposition === void 0 && e.lifecycleTransition === void 0 && e.evalRecommendation === void 0 && e.conformanceFixtureRecord === void 0 && e.emissionCoverage === void 0;
|
|
4976
5000
|
}
|
|
4977
5001
|
if (e.kind === "analysis.noise-floor-calibration") {
|
|
4978
|
-
return e.noiseFloorCalibration !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.availability === void 0 && e.varianceDecomposition === void 0 && e.lifecycleTransition === void 0 && e.evalRecommendation === void 0 && e.conformanceFixtureRecord === void 0;
|
|
5002
|
+
return e.noiseFloorCalibration !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.availability === void 0 && e.varianceDecomposition === void 0 && e.lifecycleTransition === void 0 && e.evalRecommendation === void 0 && e.conformanceFixtureRecord === void 0 && e.emissionCoverage === void 0;
|
|
4979
5003
|
}
|
|
4980
5004
|
if (e.kind === "analysis.variance-decomposition") {
|
|
4981
|
-
return e.varianceDecomposition !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.availability === void 0 && e.noiseFloorCalibration === void 0 && e.lifecycleTransition === void 0 && e.evalRecommendation === void 0 && e.conformanceFixtureRecord === void 0;
|
|
5005
|
+
return e.varianceDecomposition !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.availability === void 0 && e.noiseFloorCalibration === void 0 && e.lifecycleTransition === void 0 && e.evalRecommendation === void 0 && e.conformanceFixtureRecord === void 0 && e.emissionCoverage === void 0;
|
|
4982
5006
|
}
|
|
4983
5007
|
if (e.kind === "analysis.lifecycle-transition") {
|
|
4984
|
-
return e.lifecycleTransition !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.availability === void 0 && e.noiseFloorCalibration === void 0 && e.varianceDecomposition === void 0 && e.evalRecommendation === void 0 && e.conformanceFixtureRecord === void 0;
|
|
5008
|
+
return e.lifecycleTransition !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.availability === void 0 && e.noiseFloorCalibration === void 0 && e.varianceDecomposition === void 0 && e.evalRecommendation === void 0 && e.conformanceFixtureRecord === void 0 && e.emissionCoverage === void 0;
|
|
4985
5009
|
}
|
|
4986
5010
|
if (e.kind === "analysis.eval-recommendation") {
|
|
4987
|
-
return e.evalRecommendation !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.availability === void 0 && e.noiseFloorCalibration === void 0 && e.varianceDecomposition === void 0 && e.lifecycleTransition === void 0 && e.conformanceFixtureRecord === void 0;
|
|
5011
|
+
return e.evalRecommendation !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.availability === void 0 && e.noiseFloorCalibration === void 0 && e.varianceDecomposition === void 0 && e.lifecycleTransition === void 0 && e.conformanceFixtureRecord === void 0 && e.emissionCoverage === void 0;
|
|
4988
5012
|
}
|
|
4989
|
-
|
|
5013
|
+
if (e.kind === "analysis.conformance-fixture") {
|
|
5014
|
+
return e.conformanceFixtureRecord !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.availability === void 0 && e.noiseFloorCalibration === void 0 && e.varianceDecomposition === void 0 && e.lifecycleTransition === void 0 && e.evalRecommendation === void 0 && e.emissionCoverage === void 0;
|
|
5015
|
+
}
|
|
5016
|
+
return e.emissionCoverage !== void 0 && e.light === void 0 && e.substrate === void 0 && e.surface === void 0 && e.availability === void 0 && e.noiseFloorCalibration === void 0 && e.varianceDecomposition === void 0 && e.lifecycleTransition === void 0 && e.evalRecommendation === void 0 && e.conformanceFixtureRecord === void 0;
|
|
4990
5017
|
},
|
|
4991
5018
|
{
|
|
4992
|
-
message: "EvaluationEvent shape invariant: each kind requires exactly its corresponding payload and forbids the others. kind='evaluation' \u2192 light+substrate+surface; kind='availability.transition' \u2192 availability; kind='analysis.noise-floor-calibration' \u2192 noiseFloorCalibration; kind='analysis.variance-decomposition' \u2192 varianceDecomposition; kind='analysis.lifecycle-transition' \u2192 lifecycleTransition; kind='analysis.eval-recommendation' \u2192 evalRecommendation; kind='analysis.conformance-fixture' \u2192 conformanceFixtureRecord."
|
|
5019
|
+
message: "EvaluationEvent shape invariant: each kind requires exactly its corresponding payload and forbids the others. kind='evaluation' \u2192 light+substrate+surface; kind='availability.transition' \u2192 availability; kind='analysis.noise-floor-calibration' \u2192 noiseFloorCalibration; kind='analysis.variance-decomposition' \u2192 varianceDecomposition; kind='analysis.lifecycle-transition' \u2192 lifecycleTransition; kind='analysis.eval-recommendation' \u2192 evalRecommendation; kind='analysis.conformance-fixture' \u2192 conformanceFixtureRecord; kind='analysis.emission-coverage' \u2192 emissionCoverage."
|
|
4993
5020
|
}
|
|
4994
5021
|
);
|
|
4995
5022
|
function newEventId(occurredAt = new Date()) {
|
|
@@ -5304,6 +5331,596 @@ var InProcessTransport = class {
|
|
|
5304
5331
|
}
|
|
5305
5332
|
};
|
|
5306
5333
|
|
|
5334
|
+
var BoundedEmitBuffer = class {
|
|
5335
|
+
capacity;
|
|
5336
|
+
items = [];
|
|
5337
|
+
_offeredTotal = 0;
|
|
5338
|
+
_droppedTotal = 0;
|
|
5339
|
+
|
|
5340
|
+
constructor(capacity) {
|
|
5341
|
+
if (!Number.isInteger(capacity) || capacity < 1) {
|
|
5342
|
+
throw new RangeError(
|
|
5343
|
+
`BoundedEmitBuffer capacity must be a positive integer, got ${String(capacity)}`
|
|
5344
|
+
);
|
|
5345
|
+
}
|
|
5346
|
+
this.capacity = capacity;
|
|
5347
|
+
}
|
|
5348
|
+
|
|
5349
|
+
offer(item) {
|
|
5350
|
+
this._offeredTotal += 1;
|
|
5351
|
+
if (this.items.length >= this.capacity) {
|
|
5352
|
+
this._droppedTotal += 1;
|
|
5353
|
+
return false;
|
|
5354
|
+
}
|
|
5355
|
+
this.items.push(item);
|
|
5356
|
+
return true;
|
|
5357
|
+
}
|
|
5358
|
+
|
|
5359
|
+
drain() {
|
|
5360
|
+
if (this.items.length === 0) return [];
|
|
5361
|
+
const out = this.items;
|
|
5362
|
+
this.items = [];
|
|
5363
|
+
return out;
|
|
5364
|
+
}
|
|
5365
|
+
|
|
5366
|
+
get size() {
|
|
5367
|
+
return this.items.length;
|
|
5368
|
+
}
|
|
5369
|
+
|
|
5370
|
+
get maxSize() {
|
|
5371
|
+
return this.capacity;
|
|
5372
|
+
}
|
|
5373
|
+
|
|
5374
|
+
get offeredTotal() {
|
|
5375
|
+
return this._offeredTotal;
|
|
5376
|
+
}
|
|
5377
|
+
|
|
5378
|
+
get droppedTotal() {
|
|
5379
|
+
return this._droppedTotal;
|
|
5380
|
+
}
|
|
5381
|
+
};
|
|
5382
|
+
|
|
5383
|
+
var CompletenessAccounting = class {
|
|
5384
|
+
constructor(lensVersion, config, hooks = {}) {
|
|
5385
|
+
this.lensVersion = lensVersion;
|
|
5386
|
+
this.config = config;
|
|
5387
|
+
this.hooks = hooks;
|
|
5388
|
+
}
|
|
5389
|
+
lensVersion;
|
|
5390
|
+
config;
|
|
5391
|
+
hooks;
|
|
5392
|
+
windowStartMs = null;
|
|
5393
|
+
offered = 0;
|
|
5394
|
+
dropped = 0;
|
|
5395
|
+
delivered = 0;
|
|
5396
|
+
failed = 0;
|
|
5397
|
+
|
|
5398
|
+
channels = new Map();
|
|
5399
|
+
|
|
5400
|
+
sustainedSinceMs = null;
|
|
5401
|
+
|
|
5402
|
+
alerted = false;
|
|
5403
|
+
|
|
5404
|
+
recordOffer(nowMs, accepted) {
|
|
5405
|
+
this.openWindowIfNeeded(nowMs);
|
|
5406
|
+
this.offered += 1;
|
|
5407
|
+
if (!accepted) this.dropped += 1;
|
|
5408
|
+
}
|
|
5409
|
+
|
|
5410
|
+
recordFlush(nowMs, channel, delivered, failed) {
|
|
5411
|
+
this.openWindowIfNeeded(nowMs);
|
|
5412
|
+
this.delivered += delivered;
|
|
5413
|
+
this.failed += failed;
|
|
5414
|
+
const entry = this.channels.get(channel) ?? { attempted: 0, delivered: 0, failed: 0 };
|
|
5415
|
+
entry.attempted += delivered + failed;
|
|
5416
|
+
entry.delivered += delivered;
|
|
5417
|
+
entry.failed += failed;
|
|
5418
|
+
this.channels.set(channel, entry);
|
|
5419
|
+
}
|
|
5420
|
+
|
|
5421
|
+
maybeRollover(nowMs) {
|
|
5422
|
+
if (this.windowStartMs === null) return;
|
|
5423
|
+
if (nowMs - this.windowStartMs < this.config.windowMs) return;
|
|
5424
|
+
this.finalize(this.windowStartMs + this.config.windowMs);
|
|
5425
|
+
}
|
|
5426
|
+
|
|
5427
|
+
snapshot(nowMs) {
|
|
5428
|
+
if (this.windowStartMs === null) return null;
|
|
5429
|
+
return this.buildWindow(this.windowStartMs, nowMs);
|
|
5430
|
+
}
|
|
5431
|
+
|
|
5432
|
+
flushOpenWindow(nowMs) {
|
|
5433
|
+
if (this.windowStartMs === null) return;
|
|
5434
|
+
this.finalize(Math.max(nowMs, this.windowStartMs));
|
|
5435
|
+
}
|
|
5436
|
+
openWindowIfNeeded(nowMs) {
|
|
5437
|
+
this.windowStartMs ??= nowMs;
|
|
5438
|
+
}
|
|
5439
|
+
finalize(windowEndMs) {
|
|
5440
|
+
const start = this.windowStartMs;
|
|
5441
|
+
if (start === null) return;
|
|
5442
|
+
const window = this.buildWindow(start, windowEndMs);
|
|
5443
|
+
this.windowStartMs = null;
|
|
5444
|
+
this.offered = 0;
|
|
5445
|
+
this.dropped = 0;
|
|
5446
|
+
this.delivered = 0;
|
|
5447
|
+
this.failed = 0;
|
|
5448
|
+
this.channels.clear();
|
|
5449
|
+
this.hooks.onWindow?.(window);
|
|
5450
|
+
this.evaluateSustainedLoad(window);
|
|
5451
|
+
}
|
|
5452
|
+
buildWindow(startMs, endMs) {
|
|
5453
|
+
const completenessLowerBound = this.offered === 0 ? 1 : (this.offered - this.dropped) / this.offered;
|
|
5454
|
+
const channels = {};
|
|
5455
|
+
for (const [channel, entry] of this.channels) {
|
|
5456
|
+
channels[channel] = { ...entry };
|
|
5457
|
+
}
|
|
5458
|
+
return {
|
|
5459
|
+
lensVersion: this.lensVersion,
|
|
5460
|
+
windowStartMs: startMs,
|
|
5461
|
+
windowEndMs: endMs,
|
|
5462
|
+
offered: this.offered,
|
|
5463
|
+
dropped: this.dropped,
|
|
5464
|
+
delivered: this.delivered,
|
|
5465
|
+
failed: this.failed,
|
|
5466
|
+
channels,
|
|
5467
|
+
completenessLowerBound,
|
|
5468
|
+
biasFlag: this.dropped > 0
|
|
5469
|
+
};
|
|
5470
|
+
}
|
|
5471
|
+
evaluateSustainedLoad(window) {
|
|
5472
|
+
const rate = window.offered === 0 ? 0 : window.dropped / window.offered;
|
|
5473
|
+
if (rate >= this.config.dropRateThreshold && window.dropped > 0) {
|
|
5474
|
+
this.sustainedSinceMs ??= window.windowStartMs;
|
|
5475
|
+
const sustainedForMs = window.windowEndMs - this.sustainedSinceMs;
|
|
5476
|
+
if (sustainedForMs >= this.config.sustainedForMs && !this.alerted) {
|
|
5477
|
+
this.alerted = true;
|
|
5478
|
+
this.hooks.onExtremeSustainedLoad?.({
|
|
5479
|
+
lensVersion: this.lensVersion,
|
|
5480
|
+
sinceMs: this.sustainedSinceMs,
|
|
5481
|
+
firedAtMs: window.windowEndMs,
|
|
5482
|
+
sustainedForMs,
|
|
5483
|
+
dropRate: rate,
|
|
5484
|
+
dropRateThreshold: this.config.dropRateThreshold
|
|
5485
|
+
});
|
|
5486
|
+
}
|
|
5487
|
+
} else {
|
|
5488
|
+
this.sustainedSinceMs = null;
|
|
5489
|
+
this.alerted = false;
|
|
5490
|
+
}
|
|
5491
|
+
}
|
|
5492
|
+
};
|
|
5493
|
+
|
|
5494
|
+
var DEFAULT_BUFFER_SIZE = 2048;
|
|
5495
|
+
var DEFAULT_FLUSH_INTERVAL_MS = 1e3;
|
|
5496
|
+
var DEFAULT_WINDOW_MS = 1e4;
|
|
5497
|
+
var DEFAULT_DROP_RATE_THRESHOLD = 0.01;
|
|
5498
|
+
var DEFAULT_SUSTAINED_FOR_MS = 6e4;
|
|
5499
|
+
var DEFAULT_SENTINEL_INTERVAL_MS = 3e4;
|
|
5500
|
+
var streamIdFallbackCounter = 0;
|
|
5501
|
+
function generateStreamId(nowMs) {
|
|
5502
|
+
const cryptoObj = globalThis.crypto;
|
|
5503
|
+
if (typeof cryptoObj?.randomUUID === "function") return cryptoObj.randomUUID();
|
|
5504
|
+
streamIdFallbackCounter += 1;
|
|
5505
|
+
return `stream-${String(nowMs)}-${String(streamIdFallbackCounter)}`;
|
|
5506
|
+
}
|
|
5507
|
+
function defaultScheduleInterval(fn, ms) {
|
|
5508
|
+
const handle = setInterval(fn, ms);
|
|
5509
|
+
handle.unref?.();
|
|
5510
|
+
return () => {
|
|
5511
|
+
clearInterval(handle);
|
|
5512
|
+
};
|
|
5513
|
+
}
|
|
5514
|
+
var EvaluationEventEmitter = class {
|
|
5515
|
+
buffer;
|
|
5516
|
+
flushTargets;
|
|
5517
|
+
|
|
5518
|
+
sentinelTargets;
|
|
5519
|
+
accounting;
|
|
5520
|
+
now;
|
|
5521
|
+
onFlushError;
|
|
5522
|
+
stopInterval;
|
|
5523
|
+
stopSentinelInterval;
|
|
5524
|
+
|
|
5525
|
+
streamId;
|
|
5526
|
+
nextSeq = 0;
|
|
5527
|
+
|
|
5528
|
+
lastSeq = -1;
|
|
5529
|
+
|
|
5530
|
+
inFlight = null;
|
|
5531
|
+
stopped = false;
|
|
5532
|
+
constructor(opts) {
|
|
5533
|
+
this.buffer = new BoundedEmitBuffer(opts.bufferSize ?? DEFAULT_BUFFER_SIZE);
|
|
5534
|
+
if (opts.flushTargets.length === 0) {
|
|
5535
|
+
throw new Error("EvaluationEventEmitter requires at least one flush target");
|
|
5536
|
+
}
|
|
5537
|
+
this.flushTargets = [...opts.flushTargets];
|
|
5538
|
+
this.sentinelTargets = this.flushTargets.filter((t) => typeof t.emitSentinel === "function");
|
|
5539
|
+
this.now = opts.now ?? Date.now;
|
|
5540
|
+
this.streamId = opts.streamId ?? generateStreamId(this.now());
|
|
5541
|
+
this.onFlushError = opts.onFlushError ?? ((error) => {
|
|
5542
|
+
console.warn("[holonograph] emit flush error:", error);
|
|
5543
|
+
});
|
|
5544
|
+
const config = {
|
|
5545
|
+
windowMs: opts.windowMs ?? DEFAULT_WINDOW_MS,
|
|
5546
|
+
dropRateThreshold: opts.extremeSustainedLoad?.dropRateThreshold ?? DEFAULT_DROP_RATE_THRESHOLD,
|
|
5547
|
+
sustainedForMs: opts.extremeSustainedLoad?.sustainedForMs ?? DEFAULT_SUSTAINED_FOR_MS
|
|
5548
|
+
};
|
|
5549
|
+
this.accounting = new CompletenessAccounting(opts.lensVersion, config, {
|
|
5550
|
+
...opts.onWindow !== void 0 ? { onWindow: opts.onWindow } : {},
|
|
5551
|
+
...opts.onExtremeSustainedLoad !== void 0 ? { onExtremeSustainedLoad: opts.onExtremeSustainedLoad } : {}
|
|
5552
|
+
});
|
|
5553
|
+
const schedule = opts.scheduleInterval ?? defaultScheduleInterval;
|
|
5554
|
+
this.stopInterval = schedule(() => {
|
|
5555
|
+
this.tick();
|
|
5556
|
+
}, opts.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS);
|
|
5557
|
+
this.stopSentinelInterval = schedule(() => {
|
|
5558
|
+
this.sentinelTick();
|
|
5559
|
+
}, opts.sentinelIntervalMs ?? DEFAULT_SENTINEL_INTERVAL_MS);
|
|
5560
|
+
}
|
|
5561
|
+
|
|
5562
|
+
enqueue(event) {
|
|
5563
|
+
const nowMs = this.now();
|
|
5564
|
+
const seq = this.nextSeq;
|
|
5565
|
+
this.nextSeq += 1;
|
|
5566
|
+
this.lastSeq = seq;
|
|
5567
|
+
const accepted = this.buffer.offer({ event, seq, streamId: this.streamId });
|
|
5568
|
+
this.accounting.recordOffer(nowMs, accepted);
|
|
5569
|
+
this.accounting.maybeRollover(nowMs);
|
|
5570
|
+
return event.eventId;
|
|
5571
|
+
}
|
|
5572
|
+
|
|
5573
|
+
tick() {
|
|
5574
|
+
void this.flushNow();
|
|
5575
|
+
this.accounting.maybeRollover(this.now());
|
|
5576
|
+
}
|
|
5577
|
+
|
|
5578
|
+
sentinelTick() {
|
|
5579
|
+
if (this.lastSeq < 0) return;
|
|
5580
|
+
void this.emitSentinelNow(false);
|
|
5581
|
+
}
|
|
5582
|
+
|
|
5583
|
+
async emitSentinelNow(final) {
|
|
5584
|
+
if (this.sentinelTargets.length === 0) return;
|
|
5585
|
+
const sentinel = { streamId: this.streamId, seq: this.lastSeq, final };
|
|
5586
|
+
await Promise.all(
|
|
5587
|
+
this.sentinelTargets.map(
|
|
5588
|
+
(t) => (t.emitSentinel?.(sentinel) ?? Promise.resolve()).catch((error) => {
|
|
5589
|
+
this.onFlushError(error);
|
|
5590
|
+
})
|
|
5591
|
+
)
|
|
5592
|
+
);
|
|
5593
|
+
}
|
|
5594
|
+
|
|
5595
|
+
flushNow() {
|
|
5596
|
+
if (this.inFlight) return this.inFlight;
|
|
5597
|
+
const batch = this.buffer.drain();
|
|
5598
|
+
if (batch.length === 0) return Promise.resolve();
|
|
5599
|
+
const flush = this.doFlush(batch).finally(() => {
|
|
5600
|
+
this.inFlight = null;
|
|
5601
|
+
});
|
|
5602
|
+
this.inFlight = flush;
|
|
5603
|
+
return flush;
|
|
5604
|
+
}
|
|
5605
|
+
async doFlush(batch) {
|
|
5606
|
+
await Promise.all(this.flushTargets.map((target) => this.flushToTarget(target, batch)));
|
|
5607
|
+
}
|
|
5608
|
+
async flushToTarget(target, batch) {
|
|
5609
|
+
const channel = target.channel;
|
|
5610
|
+
try {
|
|
5611
|
+
const result = await target.flushBatch(batch);
|
|
5612
|
+
this.accounting.recordFlush(this.now(), channel, result.delivered, result.failed);
|
|
5613
|
+
for (const error of result.errors) this.onFlushError(error);
|
|
5614
|
+
} catch (error) {
|
|
5615
|
+
this.accounting.recordFlush(this.now(), channel, 0, batch.length);
|
|
5616
|
+
this.onFlushError(error);
|
|
5617
|
+
}
|
|
5618
|
+
}
|
|
5619
|
+
|
|
5620
|
+
snapshotWindow() {
|
|
5621
|
+
return this.accounting.snapshot(this.now());
|
|
5622
|
+
}
|
|
5623
|
+
|
|
5624
|
+
async shutdown() {
|
|
5625
|
+
if (this.stopped) return;
|
|
5626
|
+
this.stopped = true;
|
|
5627
|
+
this.stopInterval();
|
|
5628
|
+
this.stopSentinelInterval();
|
|
5629
|
+
while (this.buffer.size > 0 || this.inFlight) {
|
|
5630
|
+
await this.flushNow();
|
|
5631
|
+
}
|
|
5632
|
+
if (this.lastSeq >= 0) await this.emitSentinelNow(true);
|
|
5633
|
+
await Promise.all(this.flushTargets.map((t) => t.shutdown?.() ?? Promise.resolve()));
|
|
5634
|
+
this.accounting.flushOpenWindow(this.now());
|
|
5635
|
+
}
|
|
5636
|
+
};
|
|
5637
|
+
|
|
5638
|
+
var HttpEventFlushTarget = class {
|
|
5639
|
+
|
|
5640
|
+
constructor(postEvent) {
|
|
5641
|
+
this.postEvent = postEvent;
|
|
5642
|
+
}
|
|
5643
|
+
postEvent;
|
|
5644
|
+
|
|
5645
|
+
channel = "http";
|
|
5646
|
+
async flushBatch(items) {
|
|
5647
|
+
let delivered = 0;
|
|
5648
|
+
let failed = 0;
|
|
5649
|
+
const errors = [];
|
|
5650
|
+
for (const item of items) {
|
|
5651
|
+
try {
|
|
5652
|
+
await this.postEvent(item.event);
|
|
5653
|
+
delivered += 1;
|
|
5654
|
+
} catch (error) {
|
|
5655
|
+
failed += 1;
|
|
5656
|
+
errors.push(error);
|
|
5657
|
+
}
|
|
5658
|
+
}
|
|
5659
|
+
return { delivered, failed, errors };
|
|
5660
|
+
}
|
|
5661
|
+
};
|
|
5662
|
+
|
|
5663
|
+
var EVALUATION_SPAN_NAME = "holonograph.evaluation";
|
|
5664
|
+
var DIMENSION_EVENT_NAME = "holonograph.dimension";
|
|
5665
|
+
var SENTINEL_SPAN_NAME = "holonograph.session.sentinel";
|
|
5666
|
+
var SPAN_ATTR = {
|
|
5667
|
+
eventId: "holonograph.event.id",
|
|
5668
|
+
correlationId: "holonograph.correlation_id",
|
|
5669
|
+
surfaceId: "holonograph.surface.id",
|
|
5670
|
+
fixtureId: "holonograph.surface.fixture_id",
|
|
5671
|
+
passed: "holonograph.surface.passed",
|
|
5672
|
+
cohortTags: "holonograph.surface.cohort_tags",
|
|
5673
|
+
lensVersion: "holonograph.lens.version",
|
|
5674
|
+
lightSourceId: "holonograph.light_source.id",
|
|
5675
|
+
runMode: "holonograph.run_mode",
|
|
5676
|
+
provenance: "holonograph.provenance",
|
|
5677
|
+
latencyMs: "holonograph.light.latency_ms",
|
|
5678
|
+
tokensInput: "holonograph.light.tokens.input",
|
|
5679
|
+
tokensOutput: "holonograph.light.tokens.output",
|
|
5680
|
+
ok: "holonograph.light.ok",
|
|
5681
|
+
|
|
5682
|
+
rawResponseLocation: "holonograph.raw_response.location",
|
|
5683
|
+
|
|
5684
|
+
substratePrefix: "holonograph.substrate.",
|
|
5685
|
+
dimensionId: "holonograph.dimension.id",
|
|
5686
|
+
dimensionPassed: "holonograph.dimension.passed",
|
|
5687
|
+
dimensionScore: "holonograph.dimension.score",
|
|
5688
|
+
|
|
5689
|
+
emissionSeq: "holonograph.emission.seq",
|
|
5690
|
+
emissionStream: "holonograph.emission.stream",
|
|
5691
|
+
|
|
5692
|
+
emissionFinal: "holonograph.emission.final"
|
|
5693
|
+
};
|
|
5694
|
+
function buildEvaluationSpanData(event, emission, options) {
|
|
5695
|
+
const light = event.light;
|
|
5696
|
+
const surface = event.surface;
|
|
5697
|
+
const substrate = event.substrate;
|
|
5698
|
+
const startTimeMs = Date.parse(event.occurredAt);
|
|
5699
|
+
const latencyMs = typeof light?.latencyMs === "number" ? light.latencyMs : 0;
|
|
5700
|
+
const endTimeMs = startTimeMs + latencyMs;
|
|
5701
|
+
const isError = light?.error !== void 0;
|
|
5702
|
+
const attributes = {
|
|
5703
|
+
[SPAN_ATTR.eventId]: event.eventId,
|
|
5704
|
+
[SPAN_ATTR.correlationId]: event.correlationId,
|
|
5705
|
+
[SPAN_ATTR.surfaceId]: event.surfaceId,
|
|
5706
|
+
[SPAN_ATTR.lensVersion]: event.lensVersion,
|
|
5707
|
+
[SPAN_ATTR.ok]: !isError,
|
|
5708
|
+
|
|
5709
|
+
[SPAN_ATTR.rawResponseLocation]: options?.rawResponseLocation ?? "store"
|
|
5710
|
+
};
|
|
5711
|
+
if (surface !== void 0) {
|
|
5712
|
+
attributes[SPAN_ATTR.fixtureId] = surface.fixtureId;
|
|
5713
|
+
attributes[SPAN_ATTR.passed] = surface.passed;
|
|
5714
|
+
attributes[SPAN_ATTR.cohortTags] = [...surface.cohortTags];
|
|
5715
|
+
}
|
|
5716
|
+
if (substrate !== void 0) {
|
|
5717
|
+
attributes[SPAN_ATTR.lightSourceId] = substrate.lightSourceIdentifier;
|
|
5718
|
+
attributes[SPAN_ATTR.runMode] = substrate.runMode;
|
|
5719
|
+
attributes[SPAN_ATTR.provenance] = substrate.provenance;
|
|
5720
|
+
for (const [key, value] of Object.entries(substrate.columns)) {
|
|
5721
|
+
attributes[`${SPAN_ATTR.substratePrefix}${key}`] = value;
|
|
5722
|
+
}
|
|
5723
|
+
}
|
|
5724
|
+
if (typeof light?.latencyMs === "number") attributes[SPAN_ATTR.latencyMs] = light.latencyMs;
|
|
5725
|
+
if (light?.tokens?.input !== void 0) attributes[SPAN_ATTR.tokensInput] = light.tokens.input;
|
|
5726
|
+
if (light?.tokens?.output !== void 0) attributes[SPAN_ATTR.tokensOutput] = light.tokens.output;
|
|
5727
|
+
if (emission !== void 0) {
|
|
5728
|
+
attributes[SPAN_ATTR.emissionSeq] = emission.seq;
|
|
5729
|
+
attributes[SPAN_ATTR.emissionStream] = emission.streamId;
|
|
5730
|
+
}
|
|
5731
|
+
const dimensionEvents = (surface?.dimensions ?? []).map((d) => {
|
|
5732
|
+
const attrs = {
|
|
5733
|
+
[SPAN_ATTR.dimensionId]: d.dimensionId,
|
|
5734
|
+
[SPAN_ATTR.dimensionPassed]: d.passed
|
|
5735
|
+
};
|
|
5736
|
+
if (d.score !== void 0) attrs[SPAN_ATTR.dimensionScore] = d.score;
|
|
5737
|
+
return { name: DIMENSION_EVENT_NAME, attributes: attrs };
|
|
5738
|
+
});
|
|
5739
|
+
return {
|
|
5740
|
+
name: EVALUATION_SPAN_NAME,
|
|
5741
|
+
startTimeMs,
|
|
5742
|
+
endTimeMs,
|
|
5743
|
+
attributes,
|
|
5744
|
+
dimensionEvents,
|
|
5745
|
+
isError,
|
|
5746
|
+
errorMessage: light?.error?.message
|
|
5747
|
+
};
|
|
5748
|
+
}
|
|
5749
|
+
function writeEvaluationSpan(tracer, event, otel, emission, options) {
|
|
5750
|
+
const data = buildEvaluationSpanData(event, emission, options);
|
|
5751
|
+
const span = tracer.startSpan(data.name, {
|
|
5752
|
+
startTime: data.startTimeMs,
|
|
5753
|
+
kind: otel.SpanKindClient,
|
|
5754
|
+
attributes: data.attributes
|
|
5755
|
+
});
|
|
5756
|
+
for (const ev of data.dimensionEvents) span.addEvent(ev.name, ev.attributes);
|
|
5757
|
+
if (data.isError) {
|
|
5758
|
+
span.setStatus(
|
|
5759
|
+
data.errorMessage !== void 0 ? { code: otel.statusError, message: data.errorMessage } : { code: otel.statusError }
|
|
5760
|
+
);
|
|
5761
|
+
} else {
|
|
5762
|
+
span.setStatus({ code: otel.statusOk });
|
|
5763
|
+
}
|
|
5764
|
+
span.end(data.endTimeMs);
|
|
5765
|
+
}
|
|
5766
|
+
function writeSentinelSpan(tracer, sentinel, otel) {
|
|
5767
|
+
const span = tracer.startSpan(SENTINEL_SPAN_NAME, {
|
|
5768
|
+
kind: otel.SpanKindClient,
|
|
5769
|
+
attributes: {
|
|
5770
|
+
[SPAN_ATTR.emissionStream]: sentinel.streamId,
|
|
5771
|
+
[SPAN_ATTR.emissionSeq]: sentinel.seq,
|
|
5772
|
+
[SPAN_ATTR.emissionFinal]: sentinel.final
|
|
5773
|
+
}
|
|
5774
|
+
});
|
|
5775
|
+
span.setStatus({ code: otel.statusOk });
|
|
5776
|
+
span.end();
|
|
5777
|
+
}
|
|
5778
|
+
|
|
5779
|
+
function reconcileBatchDelivery(batchSize, delivered, exportFailed) {
|
|
5780
|
+
const shed = Math.max(0, batchSize - delivered - exportFailed);
|
|
5781
|
+
return { delivered, failed: exportFailed + shed };
|
|
5782
|
+
}
|
|
5783
|
+
var CountingExporter = class {
|
|
5784
|
+
constructor(inner, tally, successCode) {
|
|
5785
|
+
this.inner = inner;
|
|
5786
|
+
this.tally = tally;
|
|
5787
|
+
this.successCode = successCode;
|
|
5788
|
+
}
|
|
5789
|
+
inner;
|
|
5790
|
+
tally;
|
|
5791
|
+
successCode;
|
|
5792
|
+
export(spans, resultCallback) {
|
|
5793
|
+
this.inner.export(spans, (result) => {
|
|
5794
|
+
if (result.code === this.successCode) {
|
|
5795
|
+
this.tally.delivered += spans.length;
|
|
5796
|
+
} else {
|
|
5797
|
+
this.tally.failed += spans.length;
|
|
5798
|
+
if (result.error) this.tally.errors.push(result.error);
|
|
5799
|
+
}
|
|
5800
|
+
resultCallback(result);
|
|
5801
|
+
});
|
|
5802
|
+
}
|
|
5803
|
+
shutdown() {
|
|
5804
|
+
return this.inner.shutdown();
|
|
5805
|
+
}
|
|
5806
|
+
forceFlush() {
|
|
5807
|
+
return this.inner.forceFlush?.() ?? Promise.resolve();
|
|
5808
|
+
}
|
|
5809
|
+
};
|
|
5810
|
+
var OtlpEventFlushTarget = class {
|
|
5811
|
+
channel = "otlp";
|
|
5812
|
+
options;
|
|
5813
|
+
tally = { delivered: 0, failed: 0, errors: [] };
|
|
5814
|
+
provider;
|
|
5815
|
+
tracer;
|
|
5816
|
+
otel;
|
|
5817
|
+
initPromise;
|
|
5818
|
+
|
|
5819
|
+
opLock = Promise.resolve();
|
|
5820
|
+
constructor(options) {
|
|
5821
|
+
this.options = options;
|
|
5822
|
+
}
|
|
5823
|
+
|
|
5824
|
+
serialize(fn) {
|
|
5825
|
+
const run = this.opLock.then(fn, fn);
|
|
5826
|
+
this.opLock = run.then(
|
|
5827
|
+
() => void 0,
|
|
5828
|
+
() => void 0
|
|
5829
|
+
);
|
|
5830
|
+
return run;
|
|
5831
|
+
}
|
|
5832
|
+
flushBatch(items) {
|
|
5833
|
+
return this.serialize(() => this.doFlushBatch(items));
|
|
5834
|
+
}
|
|
5835
|
+
async doFlushBatch(items) {
|
|
5836
|
+
await this.ensureInit();
|
|
5837
|
+
const tracer = this.tracer;
|
|
5838
|
+
const otel = this.otel;
|
|
5839
|
+
const provider = this.provider;
|
|
5840
|
+
if (!tracer || !otel || !provider) {
|
|
5841
|
+
return {
|
|
5842
|
+
delivered: 0,
|
|
5843
|
+
failed: items.length,
|
|
5844
|
+
errors: [new Error("OTLP target not initialized")]
|
|
5845
|
+
};
|
|
5846
|
+
}
|
|
5847
|
+
const beforeDelivered = this.tally.delivered;
|
|
5848
|
+
const beforeFailed = this.tally.failed;
|
|
5849
|
+
const beforeErrors = this.tally.errors.length;
|
|
5850
|
+
const spanOptions = {
|
|
5851
|
+
rawResponseLocation: this.options.rawResponseLocation ?? "store"
|
|
5852
|
+
};
|
|
5853
|
+
for (const item of items) {
|
|
5854
|
+
writeEvaluationSpan(
|
|
5855
|
+
tracer,
|
|
5856
|
+
item.event,
|
|
5857
|
+
otel,
|
|
5858
|
+
{ seq: item.seq, streamId: item.streamId },
|
|
5859
|
+
spanOptions
|
|
5860
|
+
);
|
|
5861
|
+
}
|
|
5862
|
+
await provider.forceFlush();
|
|
5863
|
+
const delivered = this.tally.delivered - beforeDelivered;
|
|
5864
|
+
const exportFailed = this.tally.failed - beforeFailed;
|
|
5865
|
+
const reconciled = reconcileBatchDelivery(items.length, delivered, exportFailed);
|
|
5866
|
+
return { ...reconciled, errors: this.tally.errors.slice(beforeErrors) };
|
|
5867
|
+
}
|
|
5868
|
+
|
|
5869
|
+
emitSentinel(sentinel) {
|
|
5870
|
+
return this.serialize(async () => {
|
|
5871
|
+
await this.ensureInit();
|
|
5872
|
+
if (!this.tracer || !this.otel || !this.provider) return;
|
|
5873
|
+
writeSentinelSpan(this.tracer, sentinel, this.otel);
|
|
5874
|
+
await this.provider.forceFlush();
|
|
5875
|
+
});
|
|
5876
|
+
}
|
|
5877
|
+
async shutdown() {
|
|
5878
|
+
if (this.provider) await this.provider.shutdown();
|
|
5879
|
+
}
|
|
5880
|
+
|
|
5881
|
+
ensureInit() {
|
|
5882
|
+
this.initPromise ??= this.doInit();
|
|
5883
|
+
return this.initPromise;
|
|
5884
|
+
}
|
|
5885
|
+
async doInit() {
|
|
5886
|
+
const [api, sdk, resources, core] = await Promise.all([
|
|
5887
|
+
import('@opentelemetry/api'),
|
|
5888
|
+
import('@opentelemetry/sdk-trace-base'),
|
|
5889
|
+
import('@opentelemetry/resources'),
|
|
5890
|
+
import('@opentelemetry/core')
|
|
5891
|
+
]);
|
|
5892
|
+
let exporter;
|
|
5893
|
+
if (this.options.exporter) {
|
|
5894
|
+
exporter = this.options.exporter;
|
|
5895
|
+
} else {
|
|
5896
|
+
const otlp = await import('@opentelemetry/exporter-trace-otlp-http');
|
|
5897
|
+
const exporterConfig = {};
|
|
5898
|
+
if (this.options.endpoint !== void 0) exporterConfig.url = this.options.endpoint;
|
|
5899
|
+
if (this.options.headers !== void 0) exporterConfig.headers = this.options.headers;
|
|
5900
|
+
exporter = new otlp.OTLPTraceExporter(exporterConfig);
|
|
5901
|
+
}
|
|
5902
|
+
const counting = new CountingExporter(exporter, this.tally, core.ExportResultCode.SUCCESS);
|
|
5903
|
+
const resource = resources.resourceFromAttributes({
|
|
5904
|
+
"service.name": "holonograph-client",
|
|
5905
|
+
"holonograph.lens.version": this.options.lensVersion
|
|
5906
|
+
});
|
|
5907
|
+
this.provider = new sdk.BasicTracerProvider({
|
|
5908
|
+
resource,
|
|
5909
|
+
spanProcessors: [
|
|
5910
|
+
new sdk.BatchSpanProcessor(counting, {
|
|
5911
|
+
maxQueueSize: this.options.maxQueueSize ?? 4096
|
|
5912
|
+
})
|
|
5913
|
+
]
|
|
5914
|
+
});
|
|
5915
|
+
this.tracer = this.provider.getTracer("holonograph-client");
|
|
5916
|
+
this.otel = {
|
|
5917
|
+
SpanKindClient: api.SpanKind.CLIENT,
|
|
5918
|
+
statusOk: api.SpanStatusCode.OK,
|
|
5919
|
+
statusError: api.SpanStatusCode.ERROR
|
|
5920
|
+
};
|
|
5921
|
+
}
|
|
5922
|
+
};
|
|
5923
|
+
|
|
5307
5924
|
var HolonographHttpError = class extends Error {
|
|
5308
5925
|
constructor(status, code, message, details) {
|
|
5309
5926
|
super(message);
|
|
@@ -5321,11 +5938,46 @@ var HttpTransport = class {
|
|
|
5321
5938
|
token;
|
|
5322
5939
|
fetchImpl;
|
|
5323
5940
|
runMode;
|
|
5941
|
+
|
|
5942
|
+
emitter;
|
|
5324
5943
|
constructor(opts) {
|
|
5325
5944
|
this.endpoint = opts.endpoint.replace(/\/$/, "");
|
|
5326
5945
|
this.token = opts.token;
|
|
5327
5946
|
this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
|
|
5328
5947
|
this.runMode = opts.runMode;
|
|
5948
|
+
const e = opts.emit;
|
|
5949
|
+
const mode = e?.mode;
|
|
5950
|
+
if (e !== void 0 && (mode === "buffered" || mode === "otlp" || mode === "dual")) {
|
|
5951
|
+
const lensVersion = opts.lensVersion ?? "unknown";
|
|
5952
|
+
const flushTargets = [];
|
|
5953
|
+
if (mode === "buffered" || mode === "dual") {
|
|
5954
|
+
flushTargets.push(new HttpEventFlushTarget((event) => this.postEvent(event)));
|
|
5955
|
+
}
|
|
5956
|
+
if (mode === "otlp" || mode === "dual") {
|
|
5957
|
+
const otlpOpts = { lensVersion };
|
|
5958
|
+
if (e.otlp?.endpoint !== void 0) otlpOpts.endpoint = e.otlp.endpoint;
|
|
5959
|
+
if (e.otlp?.headers !== void 0) otlpOpts.headers = e.otlp.headers;
|
|
5960
|
+
otlpOpts.maxQueueSize = e.otlp?.maxQueueSize ?? (e.bufferSize ?? 2048) * 2;
|
|
5961
|
+
otlpOpts.rawResponseLocation = mode === "dual" ? "http-channel" : "store";
|
|
5962
|
+
flushTargets.push(new OtlpEventFlushTarget(otlpOpts));
|
|
5963
|
+
}
|
|
5964
|
+
const emitterOpts = { flushTargets, lensVersion };
|
|
5965
|
+
if (e.bufferSize !== void 0) emitterOpts.bufferSize = e.bufferSize;
|
|
5966
|
+
if (e.flushIntervalMs !== void 0) emitterOpts.flushIntervalMs = e.flushIntervalMs;
|
|
5967
|
+
if (e.sentinelIntervalMs !== void 0) emitterOpts.sentinelIntervalMs = e.sentinelIntervalMs;
|
|
5968
|
+
if (e.windowMs !== void 0) emitterOpts.windowMs = e.windowMs;
|
|
5969
|
+
if (e.extremeSustainedLoad !== void 0) {
|
|
5970
|
+
emitterOpts.extremeSustainedLoad = e.extremeSustainedLoad;
|
|
5971
|
+
}
|
|
5972
|
+
if (e.onWindow !== void 0) emitterOpts.onWindow = e.onWindow;
|
|
5973
|
+
if (e.onExtremeSustainedLoad !== void 0) {
|
|
5974
|
+
emitterOpts.onExtremeSustainedLoad = e.onExtremeSustainedLoad;
|
|
5975
|
+
}
|
|
5976
|
+
if (e.onFlushError !== void 0) emitterOpts.onFlushError = e.onFlushError;
|
|
5977
|
+
this.emitter = new EvaluationEventEmitter(emitterOpts);
|
|
5978
|
+
} else {
|
|
5979
|
+
this.emitter = void 0;
|
|
5980
|
+
}
|
|
5329
5981
|
}
|
|
5330
5982
|
async dispatch(req) {
|
|
5331
5983
|
const wire = await this.post(
|
|
@@ -5360,12 +6012,30 @@ var HttpTransport = class {
|
|
|
5360
6012
|
return result;
|
|
5361
6013
|
}
|
|
5362
6014
|
async appendEvent(event) {
|
|
6015
|
+
if (this.emitter) {
|
|
6016
|
+
return this.emitter.enqueue(event);
|
|
6017
|
+
}
|
|
5363
6018
|
const result = await this.post("/lens/events", event, event.correlationId);
|
|
5364
6019
|
return result.eventId;
|
|
5365
6020
|
}
|
|
6021
|
+
|
|
6022
|
+
async postEvent(event) {
|
|
6023
|
+
await this.post("/lens/events", event, event.correlationId);
|
|
6024
|
+
}
|
|
6025
|
+
|
|
6026
|
+
completenessWindow() {
|
|
6027
|
+
return this.emitter?.snapshotWindow() ?? null;
|
|
6028
|
+
}
|
|
6029
|
+
|
|
6030
|
+
async shutdown() {
|
|
6031
|
+
if (this.emitter) await this.emitter.shutdown();
|
|
6032
|
+
}
|
|
5366
6033
|
async postContract(contract) {
|
|
5367
6034
|
return this.post("/holonograph/contract", contract);
|
|
5368
6035
|
}
|
|
6036
|
+
async getContract() {
|
|
6037
|
+
return this.get("/holonograph/contract");
|
|
6038
|
+
}
|
|
5369
6039
|
async markAvailability(req) {
|
|
5370
6040
|
return this.post("/lens/availability/mark", req);
|
|
5371
6041
|
}
|
|
@@ -5413,6 +6083,45 @@ var HttpTransport = class {
|
|
|
5413
6083
|
}
|
|
5414
6084
|
return parsed;
|
|
5415
6085
|
}
|
|
6086
|
+
|
|
6087
|
+
async get(path) {
|
|
6088
|
+
const url = `${this.endpoint}${path}`;
|
|
6089
|
+
const headers = { "content-type": "application/json" };
|
|
6090
|
+
if (this.token !== void 0) headers.authorization = `Bearer ${this.token}`;
|
|
6091
|
+
if (this.runMode !== void 0) headers["x-holonograph-run-mode"] = this.runMode;
|
|
6092
|
+
let response;
|
|
6093
|
+
try {
|
|
6094
|
+
response = await this.fetchImpl(url, { method: "GET", headers });
|
|
6095
|
+
} catch (cause) {
|
|
6096
|
+
throw new HolonographHttpError(
|
|
6097
|
+
0,
|
|
6098
|
+
"transport_error",
|
|
6099
|
+
`the lens fetch failed: ${cause.message}`,
|
|
6100
|
+
{ cause: String(cause) }
|
|
6101
|
+
);
|
|
6102
|
+
}
|
|
6103
|
+
const text = await response.text();
|
|
6104
|
+
let parsed;
|
|
6105
|
+
try {
|
|
6106
|
+
parsed = text.length ? JSON.parse(text) : {};
|
|
6107
|
+
} catch {
|
|
6108
|
+
throw new HolonographHttpError(
|
|
6109
|
+
response.status,
|
|
6110
|
+
"parse_error",
|
|
6111
|
+
`the lens returned non-JSON (status ${String(response.status)}): ${text.slice(0, 200)}`
|
|
6112
|
+
);
|
|
6113
|
+
}
|
|
6114
|
+
if (!response.ok) {
|
|
6115
|
+
const envelope = parsed;
|
|
6116
|
+
throw new HolonographHttpError(
|
|
6117
|
+
response.status,
|
|
6118
|
+
envelope.code ?? "server_error",
|
|
6119
|
+
envelope.error ?? `the lens returned ${String(response.status)}`,
|
|
6120
|
+
envelope.details
|
|
6121
|
+
);
|
|
6122
|
+
}
|
|
6123
|
+
return parsed;
|
|
6124
|
+
}
|
|
5416
6125
|
};
|
|
5417
6126
|
function lightSnapshotFromResult(result) {
|
|
5418
6127
|
const snapshot = {
|
|
@@ -5446,9 +6155,18 @@ function flattenSystemBlocks(blocks) {
|
|
|
5446
6155
|
var HolonographClient = class {
|
|
5447
6156
|
transport;
|
|
5448
6157
|
inProcessTransport;
|
|
6158
|
+
httpTransport;
|
|
5449
6159
|
lensVersion;
|
|
5450
6160
|
substrate;
|
|
5451
6161
|
|
|
6162
|
+
onUnknownColumn;
|
|
6163
|
+
|
|
6164
|
+
declaredColumns;
|
|
6165
|
+
|
|
6166
|
+
warnedColumns = new Set();
|
|
6167
|
+
|
|
6168
|
+
refreshedColumnsOnce = false;
|
|
6169
|
+
|
|
5452
6170
|
messages;
|
|
5453
6171
|
|
|
5454
6172
|
contract;
|
|
@@ -5457,12 +6175,17 @@ var HolonographClient = class {
|
|
|
5457
6175
|
constructor(options) {
|
|
5458
6176
|
if (isHttpOptions(options)) {
|
|
5459
6177
|
const httpOpts = {
|
|
5460
|
-
endpoint: options.endpoint
|
|
6178
|
+
endpoint: options.endpoint,
|
|
6179
|
+
|
|
6180
|
+
lensVersion: options.lensVersion
|
|
5461
6181
|
};
|
|
5462
6182
|
if (options.token !== void 0) httpOpts.token = options.token;
|
|
5463
6183
|
if (options.fetch !== void 0) httpOpts.fetch = options.fetch;
|
|
5464
6184
|
if (options.runMode !== void 0) httpOpts.runMode = options.runMode;
|
|
5465
|
-
|
|
6185
|
+
if (options.emit !== void 0) httpOpts.emit = options.emit;
|
|
6186
|
+
const t = new HttpTransport(httpOpts);
|
|
6187
|
+
this.transport = t;
|
|
6188
|
+
this.httpTransport = t;
|
|
5466
6189
|
this.inProcessTransport = void 0;
|
|
5467
6190
|
} else {
|
|
5468
6191
|
const inProcOpts = {
|
|
@@ -5473,9 +6196,11 @@ var HolonographClient = class {
|
|
|
5473
6196
|
const t = new InProcessTransport(inProcOpts);
|
|
5474
6197
|
this.transport = t;
|
|
5475
6198
|
this.inProcessTransport = t;
|
|
6199
|
+
this.httpTransport = void 0;
|
|
5476
6200
|
}
|
|
5477
6201
|
this.lensVersion = options.lensVersion;
|
|
5478
6202
|
this.substrate = options.substrate;
|
|
6203
|
+
this.onUnknownColumn = isHttpOptions(options) ? options.onUnknownColumn ?? "warn" : "ignore";
|
|
5479
6204
|
this.messages = { create: (req) => this.messagesCreate(req) };
|
|
5480
6205
|
this.contract = { register: (c) => this.registerContract(c) };
|
|
5481
6206
|
this.availability = { mark: (req, opts) => this.markAvailability(req, opts) };
|
|
@@ -5531,6 +6256,60 @@ var HolonographClient = class {
|
|
|
5531
6256
|
return this.inProcessTransport ? this.inProcessTransport.topology : null;
|
|
5532
6257
|
}
|
|
5533
6258
|
|
|
6259
|
+
completenessWindow() {
|
|
6260
|
+
return this.httpTransport?.completenessWindow() ?? null;
|
|
6261
|
+
}
|
|
6262
|
+
|
|
6263
|
+
async shutdown() {
|
|
6264
|
+
if (this.httpTransport) await this.httpTransport.shutdown();
|
|
6265
|
+
}
|
|
6266
|
+
|
|
6267
|
+
fetchDeclaredColumns(force = false) {
|
|
6268
|
+
const getContract = this.transport.getContract?.bind(this.transport);
|
|
6269
|
+
if (getContract === void 0) return Promise.resolve(null);
|
|
6270
|
+
if (this.declaredColumns === void 0 || force) {
|
|
6271
|
+
this.declaredColumns = getContract().then(
|
|
6272
|
+
(c) => {
|
|
6273
|
+
const union = new Set();
|
|
6274
|
+
for (const surface of c.surfaces) {
|
|
6275
|
+
for (const id of surface.substrateColumnIds ?? []) union.add(id);
|
|
6276
|
+
}
|
|
6277
|
+
return union;
|
|
6278
|
+
},
|
|
6279
|
+
() => null
|
|
6280
|
+
);
|
|
6281
|
+
}
|
|
6282
|
+
return this.declaredColumns;
|
|
6283
|
+
}
|
|
6284
|
+
|
|
6285
|
+
async validateSubstrateColumns(columns) {
|
|
6286
|
+
if (columns === void 0 || this.onUnknownColumn === "ignore" || this.transport.getContract === void 0) {
|
|
6287
|
+
return;
|
|
6288
|
+
}
|
|
6289
|
+
const keys = Object.keys(columns);
|
|
6290
|
+
if (keys.length === 0) return;
|
|
6291
|
+
const declared = await this.fetchDeclaredColumns();
|
|
6292
|
+
if (declared === null || declared.size === 0) return;
|
|
6293
|
+
let unknown = keys.filter((k) => !declared.has(k));
|
|
6294
|
+
if (unknown.length > 0 && !this.refreshedColumnsOnce) {
|
|
6295
|
+
this.refreshedColumnsOnce = true;
|
|
6296
|
+
const refreshed = await this.fetchDeclaredColumns(true);
|
|
6297
|
+
if (refreshed === null || refreshed.size === 0) return;
|
|
6298
|
+
unknown = keys.filter((k) => !refreshed.has(k));
|
|
6299
|
+
}
|
|
6300
|
+
if (unknown.length === 0) return;
|
|
6301
|
+
if (this.onUnknownColumn === "throw") {
|
|
6302
|
+
throw new Error(
|
|
6303
|
+
`Holonograph: substrate column(s) declared in no surface contract: ${unknown.join(", ")}. Declare them on the surface contract or fix the typo. (onUnknownColumn: 'throw')`
|
|
6304
|
+
);
|
|
6305
|
+
}
|
|
6306
|
+
const toWarn = unknown.filter((k) => !this.warnedColumns.has(k));
|
|
6307
|
+
if (toWarn.length === 0) return;
|
|
6308
|
+
for (const k of toWarn) this.warnedColumns.add(k);
|
|
6309
|
+
console.warn(
|
|
6310
|
+
`Holonograph: substrate column(s) declared in no surface contract: ${toWarn.join(", ")}. They are still emitted, but the lens won't recognize them \u2014 declare them on the contract or fix the typo. (onUnknownColumn: 'throw' to block, 'ignore' to silence.)`
|
|
6311
|
+
);
|
|
6312
|
+
}
|
|
5534
6313
|
async registerContract(contract) {
|
|
5535
6314
|
if (typeof this.transport.postContract !== "function") {
|
|
5536
6315
|
throw new Error(
|
|
@@ -5627,6 +6406,7 @@ var HolonographClient = class {
|
|
|
5627
6406
|
substrate: { ...this.substrate, lightSourceIdentifier: callResult.record.lightSourceId },
|
|
5628
6407
|
surface
|
|
5629
6408
|
});
|
|
6409
|
+
await this.validateSubstrateColumns(event.substrate?.columns);
|
|
5630
6410
|
await this.transport.appendEvent(event);
|
|
5631
6411
|
if (callResult.observerRecords && callResult.observerRecords.length > 0) {
|
|
5632
6412
|
const siblingCohortTags = baseCohortTags;
|