@agoric/internal 0.4.0-u19.2 → 0.4.0-u21.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/src/metrics.js ADDED
@@ -0,0 +1,476 @@
1
+ /**
2
+ * @file Metadata about exported metrics. Note that any embedded unit
3
+ * information should be provided as case-sensitive UCUM (e.g., `s` for
4
+ * seconds, `ms` for milliseconds, `KiBy` for binary kilobytes [each being
5
+ * 1024 bytes]).
6
+ *
7
+ * - https://github.com/open-telemetry/semantic-conventions/blob/main/docs/general/metrics.md#instrument-units
8
+ * - https://ucum.org/ucum
9
+ * - https://ucum.nlm.nih.gov/ucum-lhc/demo.html
10
+ * - https://en.wikipedia.org/wiki/Unified_Code_for_Units_of_Measure
11
+ */
12
+
13
+ import { q } from '@endo/errors';
14
+ import { isNat } from '@endo/nat';
15
+
16
+ /**
17
+ * @import {Meter as OTelMeter, MetricAttributes} from '@opentelemetry/api';
18
+ * @import {TotalMap} from '@agoric/internal';
19
+ */
20
+
21
+ const HISTOGRAM_MS_LATENCY_BOUNDARIES = [
22
+ 5,
23
+ 10,
24
+ 25,
25
+ 50,
26
+ 100,
27
+ 250,
28
+ 500,
29
+ 1000,
30
+ 2500,
31
+ 5000,
32
+ 10000,
33
+ Infinity,
34
+ ];
35
+ const HISTOGRAM_SECONDS_LATENCY_BOUNDARIES =
36
+ HISTOGRAM_MS_LATENCY_BOUNDARIES.map(ms => ms / 1000);
37
+
38
+ // TODO: Validate these boundaries. We're not going to have 5ms blocks, but
39
+ // we probably care about the difference between 10 vs. 30 seconds.
40
+ export const HISTOGRAM_METRICS = /** @type {const} */ ({
41
+ swingset_crank_processing_time: {
42
+ description: 'Processing time per crank (ms)',
43
+ unit: 'ms',
44
+ boundaries: Array.of(1, 11, 21, 31, 41, 51, 61, 71, 81, 91, Infinity),
45
+ },
46
+ swingset_block_processing_seconds: {
47
+ description: 'Processing time per block',
48
+ unit: 's',
49
+ boundaries: HISTOGRAM_SECONDS_LATENCY_BOUNDARIES,
50
+ },
51
+ swingset_vat_startup: {
52
+ description: 'Vat startup time (ms)',
53
+ unit: 'ms',
54
+ boundaries: HISTOGRAM_MS_LATENCY_BOUNDARIES,
55
+ },
56
+ swingset_vat_delivery: {
57
+ description: 'Vat delivery time (ms)',
58
+ unit: 'ms',
59
+ boundaries: HISTOGRAM_MS_LATENCY_BOUNDARIES,
60
+ },
61
+ swingset_meter_usage: {
62
+ description: 'Vat meter usage',
63
+ unit: 'ms',
64
+ boundaries: HISTOGRAM_MS_LATENCY_BOUNDARIES,
65
+ },
66
+ });
67
+
68
+ const blockHistogramMetricDesc = {
69
+ // Disabled because importing from @opentelemetry/api breaks kernel bundling.
70
+ // Thankfully, it's the default anyway:
71
+ // https://github.com/open-telemetry/opentelemetry-js/blob/f4dd2a1062f980cd344cfb172a515d00115df570/api/src/metrics/Metric.ts#L53-L57
72
+ // valueType: ValueType.DOUBLE,
73
+ unit: 's',
74
+ advice: {
75
+ explicitBucketBoundaries: [
76
+ 0.1, 0.2, 0.3, 0.4, 0.5, 1, 2, 3, 4, 5, 6, 7, 10, 15, 30,
77
+ ],
78
+ },
79
+ };
80
+ export const BLOCK_HISTOGRAM_METRICS = /** @type {const} */ ({
81
+ swingsetRunSeconds: {
82
+ description: 'Per-block time spent executing SwingSet',
83
+ ...blockHistogramMetricDesc,
84
+ },
85
+ swingsetChainSaveSeconds: {
86
+ description: 'Per-block time spent propagating SwingSet state into cosmos',
87
+ ...blockHistogramMetricDesc,
88
+ },
89
+ swingsetCommitSeconds: {
90
+ description:
91
+ 'Per-block time spent committing SwingSet state to host storage',
92
+ ...blockHistogramMetricDesc,
93
+ },
94
+ cosmosCommitSeconds: {
95
+ description: 'Per-block time spent committing cosmos state',
96
+ ...blockHistogramMetricDesc,
97
+ },
98
+ fullCommitSeconds: {
99
+ description:
100
+ 'Per-block time spent committing state, inclusive of COMMIT_BLOCK processing plus time spent [outside of cosmic-swingset] before and after it',
101
+ ...blockHistogramMetricDesc,
102
+ },
103
+ interBlockSeconds: {
104
+ description: 'Time spent idle between blocks',
105
+ ...blockHistogramMetricDesc,
106
+ },
107
+ afterCommitHangoverSeconds: {
108
+ description:
109
+ 'Per-block time spent waiting for previous-block afterCommit work',
110
+ ...blockHistogramMetricDesc,
111
+ },
112
+ blockLagSeconds: {
113
+ description: 'The delay of each block from its expected begin time',
114
+ ...blockHistogramMetricDesc,
115
+ // Add buckets for excessively long delays.
116
+ advice: {
117
+ ...blockHistogramMetricDesc.advice,
118
+ explicitBucketBoundaries: /** @type {number[]} */ ([
119
+ ...blockHistogramMetricDesc.advice.explicitBucketBoundaries,
120
+ ...[60, 120, 180, 240, 300, 600, 3600],
121
+ ]),
122
+ },
123
+ },
124
+ });
125
+
126
+ /** @enum {(typeof QueueMetricAspect)[keyof typeof QueueMetricAspect]} */
127
+ export const QueueMetricAspect = /** @type {const} */ ({
128
+ Length: 'length',
129
+ IncrementCount: 'increments',
130
+ DecrementCount: 'decrements',
131
+ });
132
+
133
+ /**
134
+ * Queue metrics come in {length,add,remove} triples sharing a common prefix.
135
+ *
136
+ * @param {string} namePrefix
137
+ * @param {string} descPrefix
138
+ * @returns {Record<
139
+ * QueueMetricAspect,
140
+ * { name: string; options: MetricAttributes }
141
+ * >}
142
+ */
143
+ export const makeQueueMetricsMeta = (namePrefix, descPrefix) => {
144
+ /** @type {[QueueMetricAspect, string, string][]} */
145
+ const metricsMeta = [
146
+ [QueueMetricAspect.Length, 'length', 'length'],
147
+ [QueueMetricAspect.IncrementCount, 'add', 'increments'],
148
+ [QueueMetricAspect.DecrementCount, 'remove', 'decrements'],
149
+ ];
150
+ const entries = metricsMeta.map(([aspect, nameSuffix, descSuffix]) => {
151
+ const name = `${namePrefix}_${nameSuffix}`;
152
+ const description = `${descPrefix} ${descSuffix}`;
153
+ // Future OpenTelemetry SDKs should also support creating Instruments with
154
+ // attribute keys (such as the "queue" attribute for
155
+ // "cosmic_swingset_inbound_queue_{length,add,remove}" measurements):
156
+ // https://opentelemetry.io/docs/specs/otel/metrics/api/#instrument-advisory-parameter-attributes
157
+ // At that time, a new field will be added to this record.
158
+ return [aspect, { name, options: { description } }];
159
+ });
160
+ return Object.fromEntries(entries);
161
+ };
162
+
163
+ /**
164
+ * @template {string} QueueName
165
+ * @typedef QueueMetrics
166
+ * @property {(lengths: Record<QueueName, number>) => void} initLengths must be
167
+ * called before any other function
168
+ * @property {(lengths: Record<QueueName, number>) => void} updateLengths
169
+ * @property {(queue: QueueName, delta?: number) => void} incLength for a
170
+ * non-negative delta
171
+ * @property {(queue: QueueName, delta?: number) => void} decLength for a
172
+ * non-negative delta
173
+ */
174
+
175
+ /**
176
+ * Create {length,add,remove} Instruments for a queue and return functions for
177
+ * consistently initializing and updating them.
178
+ *
179
+ * @template {string} QueueName
180
+ * @param {object} config
181
+ * @param {OTelMeter} config.otelMeter
182
+ * @param {string} config.namePrefix
183
+ * @param {string} config.descPrefix
184
+ * @param {Pick<Console, 'warn'>} [config.console]
185
+ * @returns {QueueMetrics<QueueName>}
186
+ */
187
+ export const makeQueueMetrics = ({
188
+ otelMeter,
189
+ namePrefix,
190
+ descPrefix,
191
+ console,
192
+ }) => {
193
+ const {
194
+ [QueueMetricAspect.Length]: lengthMeta,
195
+ [QueueMetricAspect.IncrementCount]: upMeta,
196
+ [QueueMetricAspect.DecrementCount]: downMeta,
197
+ } = makeQueueMetricsMeta(namePrefix, descPrefix);
198
+ // TODO: When it's deemed safe to change reported metrics, make upCounter and
199
+ // downCounter synchronous (and drop `increments` and `decrements` maps).
200
+ // We can't do so until then because it will add a "_total" suffix to the
201
+ // Prometheus export metric name, diverging from `makeInboundQueueMetrics` in
202
+ // packages/cosmic-swingset/src/kernel-stats.js.
203
+ // https://github.com/open-telemetry/opentelemetry-js/blob/main/doc/upgrade-to-2.x.md#-other-changes
204
+ const lengthCounter = otelMeter.createUpDownCounter(
205
+ lengthMeta.name,
206
+ lengthMeta.options,
207
+ );
208
+ // const upCounter = otelMeter.createCounter(upMeta.name, upMeta.options);
209
+ // const downCounter = otelMeter.createCounter(downMeta.name, downMeta.options);
210
+ const upCounter = otelMeter.createObservableCounter(
211
+ upMeta.name,
212
+ upMeta.options,
213
+ );
214
+ const downCounter = otelMeter.createObservableCounter(
215
+ downMeta.name,
216
+ downMeta.options,
217
+ );
218
+ const increments = /** @type {TotalMap<string, number>} */ (new Map());
219
+ const decrements = /** @type {TotalMap<string, number>} */ (new Map());
220
+ upCounter.addCallback(observer => {
221
+ for (const [queueName, value] of increments.entries()) {
222
+ observer.observe(value, { queue: queueName });
223
+ }
224
+ });
225
+ downCounter.addCallback(observer => {
226
+ for (const [queueName, value] of decrements.entries()) {
227
+ observer.observe(value, { queue: queueName });
228
+ }
229
+ });
230
+
231
+ let ready = false;
232
+ const lengths = /** @type {TotalMap<string, number>} */ (new Map());
233
+ const nudge = (queueName, delta, init = false) => {
234
+ if (!lengths.has(queueName)) {
235
+ if (!init) console?.warn('Unknown queue', queueName);
236
+ lengths.set(queueName, 0);
237
+ increments.set(queueName, 0);
238
+ decrements.set(queueName, 0);
239
+ }
240
+ const oldLength = lengths.get(queueName);
241
+ lengths.set(queueName, oldLength + delta);
242
+ const dimensions = { queue: queueName };
243
+ lengthCounter.add(delta, dimensions);
244
+ if (init) {
245
+ return;
246
+ } else if (delta > 0) {
247
+ // TODO per above: upCounter.add(delta, dimensions);
248
+ increments.set(queueName, (increments.get(queueName) || 0) + delta);
249
+ } else if (delta < 0) {
250
+ // TODO per above: downCounter.add(-delta, dimensions);
251
+ decrements.set(queueName, (decrements.get(queueName) || 0) - delta);
252
+ }
253
+ ready = true;
254
+ };
255
+
256
+ return harden({
257
+ initLengths: initialLengths => {
258
+ !ready ||
259
+ console?.warn(
260
+ `Unexpected repeat queue length initialization from ${q(lengths)} to ${q(initialLengths)}`,
261
+ );
262
+ for (const [queueName, newLength] of Object.entries(initialLengths)) {
263
+ if (!isNat(newLength)) {
264
+ console?.warn(`Invalid length for queue ${q(queueName)}:`, newLength);
265
+ continue;
266
+ }
267
+ nudge(queueName, newLength, true);
268
+ }
269
+ },
270
+ updateLengths: newLengths => {
271
+ ready || console?.warn('Missing initial queue lengths');
272
+ for (const [queueName, newLength] of Object.entries(newLengths)) {
273
+ if (!isNat(newLength)) {
274
+ console?.warn(`Invalid length for queue ${q(queueName)}:`, newLength);
275
+ continue;
276
+ }
277
+ nudge(queueName, newLength - (lengths.get(queueName) || 0));
278
+ }
279
+ },
280
+ incLength: (queueName, delta = 1) => {
281
+ if (!isNat(delta)) {
282
+ console?.warn(`Invalid increment for queue ${q(queueName)}:`, delta);
283
+ return;
284
+ }
285
+ nudge(queueName, delta);
286
+ },
287
+ decLength: (queueName, delta = 1) => {
288
+ if (!isNat(delta)) {
289
+ console?.warn(`Invalid decrement for queue ${q(queueName)}:`, delta);
290
+ return;
291
+ }
292
+ nudge(queueName, -delta);
293
+ },
294
+ });
295
+ };
296
+
297
+ // All the kernel metrics we are prepared for.
298
+ /** @enum {(typeof MetricType)[keyof typeof MetricType]} MetricType */
299
+ const MetricType = /** @type {const} */ ({
300
+ Counter: 'counter',
301
+ Gauge: 'gauge',
302
+ });
303
+ /**
304
+ * @typedef KernelMetricMeta
305
+ * @property {string} key
306
+ * @property {string} name
307
+ * @property {{ dimension: string; value: string }} [sub]
308
+ * @property {string} description
309
+ * @property {boolean} [consensus]
310
+ * @property {MetricType} metricType
311
+ */
312
+ /** @type {Omit<KernelMetricMeta, 'metricType'>[]} */
313
+ export const KERNEL_STATS_SUM_METRICS = [
314
+ {
315
+ key: 'syscalls',
316
+ name: 'swingset_all_syscall_total',
317
+ description: 'Total number of SwingSet kernel calls',
318
+ },
319
+ {
320
+ key: 'syscallSend',
321
+ name: 'swingset_syscall_total',
322
+ sub: { dimension: 'syscall', value: 'send' },
323
+ description: 'Total number of SwingSet message send kernel calls',
324
+ },
325
+ {
326
+ key: 'syscallCallNow',
327
+ name: 'swingset_syscall_total',
328
+ sub: { dimension: 'syscall', value: 'callNow' },
329
+ description: 'Total number of SwingSet synchronous device kernel calls',
330
+ },
331
+ {
332
+ key: 'syscallSubscribe',
333
+ name: 'swingset_syscall_total',
334
+ sub: { dimension: 'syscall', value: 'subscribe' },
335
+ description: 'Total number of SwingSet promise subscription kernel calls',
336
+ },
337
+ {
338
+ key: 'syscallResolve',
339
+ name: 'swingset_syscall_total',
340
+ sub: { dimension: 'syscall', value: 'resolve' },
341
+ description: 'Total number of SwingSet promise resolution kernel calls',
342
+ },
343
+ {
344
+ key: 'syscallExit',
345
+ name: 'swingset_syscall_total',
346
+ sub: { dimension: 'syscall', value: 'exit' },
347
+ description: 'Total number of SwingSet vat exit kernel calls',
348
+ },
349
+ {
350
+ key: 'syscallVatstoreGet',
351
+ name: 'swingset_syscall_total',
352
+ sub: { dimension: 'syscall', value: 'vatstoreGet' },
353
+ description: 'Total number of SwingSet vatstore get kernel calls',
354
+ },
355
+ {
356
+ key: 'syscallVatstoreSet',
357
+ name: 'swingset_syscall_total',
358
+ sub: { dimension: 'syscall', value: 'vatstoreSet' },
359
+ description: 'Total number of SwingSet vatstore set kernel calls',
360
+ },
361
+ {
362
+ key: 'syscallVatstoreGetNextKey',
363
+ name: 'swingset_syscall_total',
364
+ sub: { dimension: 'syscall', value: 'vatstoreGetNext' },
365
+ description: 'Total number of SwingSet vatstore getNextKey kernel calls',
366
+ },
367
+ {
368
+ key: 'syscallVatstoreDelete',
369
+ name: 'swingset_syscall_total',
370
+ sub: { dimension: 'syscall', value: 'vatstoreDelete' },
371
+ description: 'Total number of SwingSet vatstore delete kernel calls',
372
+ },
373
+ {
374
+ key: 'syscallDropImports',
375
+ name: 'swingset_syscall_total',
376
+ sub: { dimension: 'syscall', value: 'dropImports' },
377
+ description: 'Total number of SwingSet drop imports kernel calls',
378
+ },
379
+ {
380
+ key: 'dispatches',
381
+ name: 'swingset_dispatch_total',
382
+ description: 'Total number of SwingSet vat calls',
383
+ },
384
+ {
385
+ key: 'dispatchDeliver',
386
+ name: 'swingset_dispatch_deliver_total',
387
+ description: 'Total number of SwingSet vat message deliveries',
388
+ },
389
+ {
390
+ key: 'dispatchNotify',
391
+ name: 'swingset_dispatch_notify_total',
392
+ description: 'Total number of SwingSet vat promise notifications',
393
+ },
394
+ ];
395
+
396
+ /** @type {Omit<KernelMetricMeta, 'metricType'>[]} */
397
+ export const KERNEL_STATS_UPDOWN_METRICS = [
398
+ {
399
+ key: 'kernelObjects',
400
+ name: 'swingset_kernel_objects',
401
+ description: 'Active kernel objects',
402
+ },
403
+ {
404
+ key: 'kernelDevices',
405
+ name: 'swingset_kernel_devices',
406
+ description: 'Active kernel devices',
407
+ },
408
+ {
409
+ key: 'kernelPromises',
410
+ name: 'swingset_kernel_promises',
411
+ description: 'Active kernel promises',
412
+ },
413
+ {
414
+ key: 'kpUnresolved',
415
+ name: 'swingset_unresolved_kernel_promises',
416
+ description: 'Unresolved kernel promises',
417
+ },
418
+ {
419
+ key: 'kpFulfilled',
420
+ name: 'swingset_fulfilled_kernel_promises',
421
+ description: 'Fulfilled kernel promises',
422
+ },
423
+ {
424
+ key: 'kpRejected',
425
+ name: 'swingset_rejected_kernel_promises',
426
+ description: 'Rejected kernel promises',
427
+ },
428
+ {
429
+ key: 'runQueueLength',
430
+ name: 'swingset_run_queue_length',
431
+ consensus: true,
432
+ description: 'Length of the kernel run queue',
433
+ },
434
+ {
435
+ key: 'acceptanceQueueLength',
436
+ name: 'swingset_acceptance_queue_length',
437
+ consensus: true,
438
+ description: 'Length of the kernel acceptance queue',
439
+ },
440
+ {
441
+ key: 'promiseQueuesLength',
442
+ name: 'swingset_promise_queues_length',
443
+ consensus: true,
444
+ description: 'Combined length of all kernel promise queues',
445
+ },
446
+ {
447
+ key: 'clistEntries',
448
+ name: 'swingset_clist_entries',
449
+ description: 'Number of entries in the kernel c-list',
450
+ },
451
+ {
452
+ key: 'vats',
453
+ name: 'swingset_vats',
454
+ description: 'Number of active vats',
455
+ },
456
+ ];
457
+
458
+ const { Counter, Gauge } = MetricType;
459
+ /** @type {KernelMetricMeta[]} */
460
+ export const KERNEL_STATS_METRICS = harden([
461
+ ...KERNEL_STATS_SUM_METRICS.map(m => ({ ...m, metricType: Counter })),
462
+ ...KERNEL_STATS_UPDOWN_METRICS.map(m => ({ ...m, metricType: Gauge })),
463
+ ]);
464
+
465
+ // Ensure kernel stats key uniqueness.
466
+ const kernelStatsKeys = new Map();
467
+ for (const { key } of KERNEL_STATS_METRICS) {
468
+ kernelStatsKeys.set(key, (kernelStatsKeys.get(key) || 0) + 1);
469
+ }
470
+ const duplicateKernelStatsKeys = [...kernelStatsKeys.entries()].flatMap(
471
+ ([key, value]) => (value > 1 ? [key] : []),
472
+ );
473
+ if (duplicateKernelStatsKeys.length > 0) {
474
+ const msg = `Duplicate kernel stats keys ${JSON.stringify(duplicateKernelStatsKeys)}`;
475
+ throw Error(msg);
476
+ }
@@ -0,0 +1,2 @@
1
+ export function getSpecifier(fileUrl: string): Promise<string>;
2
+ //# sourceMappingURL=module-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module-utils.d.ts","sourceRoot":"","sources":["module-utils.js"],"names":[],"mappings":"AAaO,sCAHI,MAAM,GACJ,OAAO,CAAC,MAAM,CAAC,CAc3B"}
@@ -0,0 +1,27 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { search } from '@endo/compartment-mapper';
4
+ import { Fail } from '@endo/errors';
5
+
6
+ /**
7
+ * Given a file URL, return a deep import specifier that starts with the
8
+ * specifier for its containing module (e.g., the deep import specifier for this
9
+ * file is expected to be "@agoric/internal/src/module-utils.js").
10
+ *
11
+ * @param {string} fileUrl
12
+ * @returns {Promise<string>}
13
+ */
14
+ export const getSpecifier = async fileUrl => {
15
+ // eslint-disable-next-line no-new
16
+ new URL(fileUrl); // validates fileUrl
17
+ const read = async location => {
18
+ const path = fileURLToPath(new URL(location, fileUrl));
19
+ return readFile(path);
20
+ };
21
+ const searchResult = await search(read, fileUrl);
22
+ const { packageDescriptorText, moduleSpecifier } = searchResult;
23
+ const { name } = JSON.parse(packageDescriptorText);
24
+ name || Fail`containing package must have a name`;
25
+ return `${name}/${moduleSpecifier.replace(/^\.\//, '')}`;
26
+ };
27
+ harden(getSpecifier);
@@ -34,6 +34,7 @@ export function PromiseAllOrErrors<T>(items: readonly (T | PromiseLike<T>)[]): P
34
34
  export function aggregateTryFinally<T>(trier: () => Promise<T>, finalizer: (error?: unknown) => Promise<unknown>): ReturnType<() => Promise<T>>;
35
35
  export function withDeferredCleanup<T>(fn: (addCleanup: (fn: (err?: unknown) => Promise<void>) => void) => Promise<T>): ReturnType<(addCleanup: (fn: (err?: unknown) => Promise<void>) => void) => Promise<T>>;
36
36
  export function assertAllDefined<T extends Record<string, unknown>>(obj: T): asserts obj is AllDefined<T>;
37
+ export function attenuate<T, P extends Permit<T>>(specimen: T, permit: P, transform?: <U, SubP extends Permit<U>>(attenuation: U, permit: SubP) => U): Attenuated<T, P>;
37
38
  export const forever: AsyncIterable<undefined>;
38
39
  export function whileTrue<T>(produce: () => T): AsyncIterable<Awaited<T>>;
39
40
  export function untilTrue<T>(produce: () => T): AsyncIterable<Awaited<T>>;
@@ -56,6 +57,12 @@ export type Callable = (...args: any[]) => any;
56
57
  export type DeeplyAwaitedObject<T extends {}> = { [K in keyof T]: T[K] extends Callable ? T[K] : DeeplyAwaited<T[K]>; };
57
58
  export type DeeplyAwaited<T> = T extends PromiseLike<any> ? Awaited<T> : T extends {} ? Simplify<DeeplyAwaitedObject<T>> : Awaited<T>;
58
59
  export type AllDefined<T extends Record<string, unknown>> = { [P in keyof T]: Exclude<T[P], undefined>; };
60
+ import { objectMap } from '@endo/common/object-map.js';
61
+ import { objectMetaMap } from '@endo/common/object-meta-map.js';
62
+ import { fromUniqueEntries } from '@endo/common/from-unique-entries.js';
59
63
  import type { LimitedConsole } from './js-utils.js';
64
+ import type { Permit } from './types.js';
65
+ import type { Attenuated } from './types.js';
60
66
  import type { ERef } from '@endo/far';
67
+ export { objectMap, objectMetaMap, fromUniqueEntries };
61
68
  //# sourceMappingURL=ses-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ses-utils.d.ts","sourceRoot":"","sources":["ses-utils.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ses-utils.d.ts","sourceRoot":"","sources":["ses-utils.js"],"names":[],"mappings":"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"}
package/src/ses-utils.js CHANGED
@@ -5,18 +5,27 @@
5
5
  * either directly or indirectly (e.g. by @endo imports).
6
6
  */
7
7
 
8
+ import { objectMap } from '@endo/common/object-map.js';
9
+ import { objectMetaMap } from '@endo/common/object-meta-map.js';
10
+ import { fromUniqueEntries } from '@endo/common/from-unique-entries.js';
8
11
  import { q, Fail, makeError, annotateError, X } from '@endo/errors';
9
12
  import { deeplyFulfilled, isObject } from '@endo/marshal';
10
13
  import { makePromiseKit } from '@endo/promise-kit';
11
14
  import { makeQueue } from '@endo/stream';
15
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
16
+ // @ts-ignore TS7016 The 'jessie.js' library may need to update its package.json or typings
12
17
  import { asyncGenerate } from 'jessie.js';
13
18
  import { logLevels } from './js-utils.js';
14
19
 
15
20
  /** @import {LimitedConsole} from './js-utils.js'; */
16
21
 
17
- const { fromEntries, keys, values } = Object;
22
+ /** @import {ERef} from '@endo/far'; */
23
+ /** @import {Primitive} from '@endo/pass-style'; */
24
+ /** @import {Permit, Attenuated} from './types.js'; */
25
+
26
+ export { objectMap, objectMetaMap, fromUniqueEntries };
18
27
 
19
- /** @import {ERef} from '@endo/far' */
28
+ const { fromEntries, keys, values } = Object;
20
29
 
21
30
  /** @param {(level: string) => (...args: unknown[]) => void} makeLogger */
22
31
  export const makeLimitedConsole = makeLogger => {
@@ -175,6 +184,61 @@ export const assertAllDefined = obj => {
175
184
  }
176
185
  };
177
186
 
187
+ /**
188
+ * Attenuate `specimen` to only properties allowed by `permit`.
189
+ *
190
+ * @template T
191
+ * @template {Permit<T>} P
192
+ * @param {T} specimen
193
+ * @param {P} permit
194
+ * @param {<U, SubP extends Permit<U>>(attenuation: U, permit: SubP) => U} [transform]
195
+ * used to replace the results of recursive picks (but not blanket permits)
196
+ * @returns {Attenuated<T, P>}
197
+ */
198
+ export const attenuate = (specimen, permit, transform = x => x) => {
199
+ // Fast-path for no attenuation.
200
+ if (permit === true || typeof permit === 'string') {
201
+ return /** @type {Attenuated<T, P>} */ (specimen);
202
+ }
203
+
204
+ /** @type {string[]} */
205
+ const path = [];
206
+ /**
207
+ * @template SubT
208
+ * @template {Exclude<Permit<SubT>, Primitive>} SubP
209
+ * @type {(specimen: SubT, permit: SubP) => Attenuated<SubT, SubP>}
210
+ */
211
+ const extract = (subSpecimen, subPermit) => {
212
+ if (subPermit === null || typeof subPermit !== 'object') {
213
+ throw path.length === 0
214
+ ? Fail`invalid permit: ${q(permit)}`
215
+ : Fail`invalid permit at path ${q(path)}: ${q(subPermit)}`;
216
+ } else if (subSpecimen === null || typeof subSpecimen !== 'object') {
217
+ throw path.length === 0
218
+ ? Fail`specimen must be an object for permit ${q(permit)}`
219
+ : Fail`specimen at path ${q(path)} must be an object for permit ${q(subPermit)}`;
220
+ }
221
+ const picks = Object.entries(subPermit).map(([subKey, deepPermit]) => {
222
+ if (!Object.hasOwn(subSpecimen, subKey)) {
223
+ throw Fail`specimen is missing path ${q(path.concat(subKey))}`;
224
+ }
225
+ const deepSpecimen = Reflect.get(subSpecimen, subKey);
226
+ if (deepPermit === true || typeof deepPermit === 'string') {
227
+ return [subKey, deepSpecimen];
228
+ }
229
+ path.push(subKey);
230
+ const extracted = extract(/** @type {any} */ (deepSpecimen), deepPermit);
231
+ const entry = [subKey, extracted];
232
+ path.pop();
233
+ return entry;
234
+ });
235
+ return transform(Object.fromEntries(picks), subPermit);
236
+ };
237
+
238
+ // @ts-expect-error cast
239
+ return extract(specimen, permit);
240
+ };
241
+
178
242
  /** @type {IteratorResult<undefined, never>} */
179
243
  const notDone = harden({ done: false, value: undefined });
180
244
 
@@ -1 +1 @@
1
- {"version":3,"file":"storage-test-utils.d.ts","sourceRoot":"","sources":["storage-test-utils.js"],"names":[],"mappings":"AAyBO,yCAHI,MAAM,UACN,MAAM,6GAGC;AAElB;;;GAGG;AACH;;;;;EAEG;;eAMU,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO;mBAEzB,CAAC,GAAG,EAAE,GAAG,KAAK,MAAM;;AA8CjC,8CAtBuB,GAAG,KAAK,GAAG,CAsB+B;AAU1D,6CAHI,MAAM,gBACN,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC,CAAC,CAAC;;;;;oBA+Fb,CAAC;;;;UAgDpB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;;sBAd3B,MAAM,KACJ,MAAM,EAAE;;0BA9FP,cAAc;;;8IAAd,cAAc;;;;EAiH7B;AAoBM,4CADO,oBAAoB,CA6BjC;AAUM,yCAPI,OAAO,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,WACvC,oBAAoB,GAAG,cAAc,QACrC,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,GAC9D,CAAK;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,EAAE,CAAC,GAAG;IAChD,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;CACpC,iBAkDL;6BAxGa,UAAU,CAAC,OAAO,kBAAkB,CAAC;;;;;;;;aAIrC,CACT,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,UAAU,EACvB,KAAK,CAAC,EAAE,MAAM,KACX,OAAO;UAKD,MAAM,MAAM,EAAE;;mCAEd,WAAW,GAAG,2BAA2B;qCAhQJ,uBAAuB;iCAOU,uBAAuB;oCAAvB,uBAAuB;gCAAvB,uBAAuB"}
1
+ {"version":3,"file":"storage-test-utils.d.ts","sourceRoot":"","sources":["storage-test-utils.js"],"names":[],"mappings":"AAyBO,yCAHI,MAAM,UACN,MAAM,6GAGC;AAElB;;;GAGG;AACH;;;;;EAEG;;eAMU,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO;mBAEzB,CAAC,GAAG,EAAE,GAAG,KAAK,MAAM;;AA8CjC,8CAtBuB,GAAG,KAAK,GAAG,CAsB+B;AAU1D,6CAHI,MAAM,gBACN,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC,CAAC,CAAC;;;;;oBAuFhB,CAAC;;;;UAwDjB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;;sBAd3B,MAAM,KACJ,MAAM,EAAE;;0BA9FP,cAAc;;;8IAAd,cAAc;;;;EAiH7B;AAoBM,4CADO,oBAAoB,CA6BjC;AAUM,yCAPI,OAAO,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,WACvC,oBAAoB,GAAG,cAAc,QACrC,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,GAC9D,CAAK;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,EAAE,CAAC,GAAG;IAChD,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;CACpC,iBAkDL;6BAxGa,UAAU,CAAC,OAAO,kBAAkB,CAAC;;;;;;;;aAIrC,CACT,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,UAAU,EACvB,KAAK,CAAC,EAAE,MAAM,KACX,OAAO;UAKD,MAAM,MAAM,EAAE;;mCAEd,WAAW,GAAG,2BAA2B;qCAhQJ,uBAAuB;iCAOU,uBAAuB;oCAAvB,uBAAuB;gCAAvB,uBAAuB"}
@@ -0,0 +1,2 @@
1
+ export function makeTempDirFactory(tmp: Pick<typeof import("tmp"), "dirSync">): (prefix?: string) => [dirName: string, cleanup: () => void];
2
+ //# sourceMappingURL=tmpDir.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tmpDir.d.ts","sourceRoot":"","sources":["tmpDir.js"],"names":[],"mappings":"AAMO,wCAFI,IAAI,CAAC,oBAAa,EAAE,SAAS,CAAC,aAGlB,MAAM,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC,CASvE"}
package/src/tmpDir.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Create a `tmpDir` function that synchronously creates a temporary directory
3
+ * and a function for deleting it along with any added contents.
4
+ *
5
+ * @param {Pick<import('tmp'), 'dirSync'>} tmp
6
+ */
7
+ export const makeTempDirFactory = tmp => {
8
+ /** @type {(prefix?: string) => [dirName: string, cleanup: () => void]} */
9
+ const tmpDir = prefix => {
10
+ const { name, removeCallback } = tmp.dirSync({
11
+ prefix,
12
+ unsafeCleanup: true,
13
+ });
14
+ return [name, removeCallback];
15
+ };
16
+ return tmpDir;
17
+ };