@aws-blocks/core 0.4.0 → 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 +12 -0
- package/dist/cdk/blocks-backend.d.ts +4 -1
- package/dist/cdk/blocks-backend.d.ts.map +1 -1
- package/dist/cdk/blocks-backend.js +56 -10
- package/dist/cdk/blocks-backend.test.js +71 -1
- package/dist/cdk/blocks-defaults.d.ts +11 -0
- package/dist/cdk/blocks-defaults.d.ts.map +1 -1
- package/dist/cdk/blocks-stack.test.js +23 -3
- package/dist/cdk/compute/compute.d.ts +80 -2
- package/dist/cdk/compute/compute.d.ts.map +1 -1
- package/dist/cdk/compute/compute.js +57 -3
- package/dist/cdk/config-registry.test.js +12 -0
- package/dist/cdk/dashboard-registry.d.ts +41 -0
- package/dist/cdk/dashboard-registry.d.ts.map +1 -0
- package/dist/cdk/dashboard-registry.js +61 -0
- package/dist/cdk/index.d.ts +67 -11
- package/dist/cdk/index.d.ts.map +1 -1
- package/dist/cdk/index.js +100 -18
- package/dist/cdk/internal.d.ts +3 -1
- package/dist/cdk/internal.d.ts.map +1 -1
- package/dist/cdk/internal.js +4 -1
- package/dist/cdk/tracer-registry.d.ts +31 -0
- package/dist/cdk/tracer-registry.d.ts.map +1 -0
- package/dist/cdk/tracer-registry.js +49 -0
- package/dist/cdk/vpc-requirements-registry.d.ts +33 -0
- package/dist/cdk/vpc-requirements-registry.d.ts.map +1 -0
- package/dist/cdk/vpc-requirements-registry.js +46 -0
- package/dist/cdk/vpc-types.d.ts +151 -0
- package/dist/cdk/vpc-types.d.ts.map +1 -0
- package/dist/cdk/vpc-types.js +3 -0
- package/dist/cdk/vpc.d.ts +59 -0
- package/dist/cdk/vpc.d.ts.map +1 -0
- package/dist/cdk/vpc.js +298 -0
- package/dist/cdk/vpc.test.d.ts +2 -0
- package/dist/cdk/vpc.test.d.ts.map +1 -0
- package/dist/cdk/vpc.test.js +285 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +5 -0
- package/dist/hosting.d.ts.map +1 -1
- package/dist/hosting.js +2 -0
- package/dist/hosting.test.js +39 -1
- package/dist/index.cdk.d.ts +2 -1
- package/dist/index.cdk.d.ts.map +1 -1
- package/dist/index.cdk.js +1 -1
- package/dist/lambda-handler.js +9 -2
- package/dist/lambda-handler.test.js +61 -1
- package/dist/raw-route.d.ts +15 -1
- package/dist/raw-route.d.ts.map +1 -1
- package/dist/raw-route.js +96 -12
- package/dist/raw-route.test.js +332 -1
- package/dist/scripts/dev-server.d.ts.map +1 -1
- package/dist/scripts/dev-server.js +11 -0
- package/dist/scripts/extract-ts-types.d.ts.map +1 -1
- package/dist/scripts/extract-ts-types.js +107 -23
- package/dist/scripts/extract-ts-types.test.js +225 -26
- package/dist/scripts/generate-spec.d.ts.map +1 -1
- package/dist/scripts/generate-spec.js +14 -5
- package/dist/scripts/generate-spec.test.js +93 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +8 -1
- package/src/cdk/blocks-backend.test.ts +144 -60
- package/src/cdk/blocks-backend.ts +298 -239
- package/src/cdk/blocks-defaults.ts +12 -0
- package/src/cdk/blocks-stack.test.ts +32 -13
- package/src/cdk/compute/compute.ts +105 -3
- package/src/cdk/config-registry.test.ts +14 -0
- package/src/cdk/dashboard-registry.ts +68 -0
- package/src/cdk/index.ts +426 -298
- package/src/cdk/internal.ts +6 -2
- package/src/cdk/tracer-registry.ts +54 -0
- package/src/cdk/vpc-requirements-registry.ts +63 -0
- package/src/cdk/vpc-types.ts +158 -0
- package/src/cdk/vpc.test.ts +348 -0
- package/src/cdk/vpc.ts +336 -0
- package/src/errors.ts +5 -0
- package/src/hosting.test.ts +59 -1
- package/src/hosting.ts +3 -0
- package/src/index.cdk.ts +7 -0
- package/src/lambda-handler.test.ts +79 -1
- package/src/lambda-handler.ts +11 -2
- package/src/raw-route.test.ts +427 -1
- package/src/raw-route.ts +125 -12
- package/src/scripts/dev-server.ts +12 -1
- package/src/scripts/extract-ts-types.test.ts +228 -26
- package/src/scripts/extract-ts-types.ts +104 -20
- package/src/scripts/generate-spec.test.ts +101 -0
- package/src/scripts/generate-spec.ts +15 -5
- package/src/version.ts +1 -1
|
@@ -7,6 +7,7 @@ import { before, describe, test } from 'node:test';
|
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import * as cdk from 'aws-cdk-lib';
|
|
9
9
|
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
|
|
10
|
+
import type { IWidget } from 'aws-cdk-lib/aws-cloudwatch';
|
|
10
11
|
import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
|
|
11
12
|
import type { Construct } from 'constructs';
|
|
12
13
|
import type { ScopeParent } from '../common/index.js';
|
|
@@ -15,7 +16,7 @@ import { BlocksBackend } from './blocks-backend.js';
|
|
|
15
16
|
import { Compute } from './compute/compute.js';
|
|
16
17
|
import { getComputes } from './compute/compute-registry.js';
|
|
17
18
|
import type { DefaultComputeFactory } from './compute/default-compute-factory.js';
|
|
18
|
-
import {
|
|
19
|
+
import { BlocksPresets, BlocksStack, Scope } from './index.js';
|
|
19
20
|
|
|
20
21
|
// A real app gets its default compute from @aws-blocks/bb-lambda-compute (via
|
|
21
22
|
// @aws-blocks/blocks), which core's own tests can't depend on. Use an
|
|
@@ -55,6 +56,17 @@ class StubLambdaCompute extends Compute {
|
|
|
55
56
|
setEnv(key: string, value: string): void {
|
|
56
57
|
this.fn.addEnvironment(key, value);
|
|
57
58
|
}
|
|
59
|
+
|
|
60
|
+
protected applyTracing(): void {}
|
|
61
|
+
protected healthWidgets(_region: string): IWidget[][] {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
protected loggingWidgets(_region: string): IWidget[][] {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
protected tracingWidgets(_region: string): IWidget[][] {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
58
70
|
}
|
|
59
71
|
|
|
60
72
|
const stubComputeFactory: DefaultComputeFactory = (root) => new StubLambdaCompute(root as never, 'DefaultCompute');
|
|
@@ -72,9 +84,19 @@ const factoryBackendPath = join(__dirname, '__fixtures__', 'factory-backend.js')
|
|
|
72
84
|
// Wrap create(), injecting the stub default-compute factory the way
|
|
73
85
|
// @aws-blocks/blocks injects LambdaCompute — so tests don't repeat it.
|
|
74
86
|
const makeStack = (scope: Construct, id: string, backendCDKPath: string) =>
|
|
75
|
-
BlocksStack.create(scope, id, {
|
|
87
|
+
BlocksStack.create(scope, id, {
|
|
88
|
+
backendHandlerPath: handlerPath,
|
|
89
|
+
backendCDKPath,
|
|
90
|
+
defaults: BlocksPresets.production,
|
|
91
|
+
defaultComputeFactory: stubComputeFactory,
|
|
92
|
+
});
|
|
76
93
|
const makeBackend = (scope: Construct, id: string, backendCDKPath: string) =>
|
|
77
|
-
BlocksBackend.create(scope, id, {
|
|
94
|
+
BlocksBackend.create(scope, id, {
|
|
95
|
+
backendHandlerPath: handlerPath,
|
|
96
|
+
backendCDKPath,
|
|
97
|
+
defaults: BlocksPresets.production,
|
|
98
|
+
defaultComputeFactory: stubComputeFactory,
|
|
99
|
+
});
|
|
78
100
|
|
|
79
101
|
describe('ESM cache-busting (multi-stage)', () => {
|
|
80
102
|
test('BlocksStack.create() with same backendCDKPath but different IDs produces constructs in each', async () => {
|
|
@@ -203,16 +225,13 @@ describe('assertCdkConditionActive', () => {
|
|
|
203
225
|
try {
|
|
204
226
|
const app = new cdk.App();
|
|
205
227
|
|
|
206
|
-
await assert.rejects(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
return true;
|
|
214
|
-
},
|
|
215
|
-
);
|
|
228
|
+
await assert.rejects(makeStack(app, 'MissingConditionStack', sideEffectBackendPath), (err: Error) => {
|
|
229
|
+
assert.ok(
|
|
230
|
+
err.message.includes('Missing --conditions=cdk'),
|
|
231
|
+
`Expected condition error, got: ${err.message}`,
|
|
232
|
+
);
|
|
233
|
+
return true;
|
|
234
|
+
});
|
|
216
235
|
} finally {
|
|
217
236
|
process.env.NODE_OPTIONS = origNodeOptions;
|
|
218
237
|
process.execArgv = origExecArgv;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
+
import type { IWidget } from 'aws-cdk-lib/aws-cloudwatch';
|
|
4
5
|
import type { ScopeOptions } from '../../common/index.js';
|
|
5
6
|
import { Scope } from '../index.js';
|
|
6
7
|
import { registerCompute } from './compute-registry.js';
|
|
@@ -16,11 +17,17 @@ import { registerCompute } from './compute-registry.js';
|
|
|
16
17
|
* compute in an app runs the same backend and agrees on the resource-name
|
|
17
18
|
* namespace.
|
|
18
19
|
*
|
|
20
|
+
* **Observability is compute-owned at deploy time.** Logging is always on (a
|
|
21
|
+
* compute owns its log group and captures stdout; retention is a compute-level
|
|
22
|
+
* setting), so there is no "enable logging" — logs always exist. Tracing, by
|
|
23
|
+
* contrast, provisions real infrastructure (X-Ray) and has cost, so it is
|
|
24
|
+
* enabled explicitly via {@link enableTracing} — which the framework calls on
|
|
25
|
+
* every compute when the app contains a `Tracer` (presence-gated).
|
|
26
|
+
*
|
|
19
27
|
* The abstract base lives in core (a framework primitive); concrete computes
|
|
20
28
|
* live in their own packages (e.g. `LambdaCompute` in `@aws-blocks/bb-lambda-compute`).
|
|
21
29
|
*
|
|
22
|
-
* @internal Not exported from the package's public entry points.
|
|
23
|
-
* cannot instantiate a compute until the customer-facing surface exists.
|
|
30
|
+
* @internal Not exported from the package's public entry points.
|
|
24
31
|
*/
|
|
25
32
|
export abstract class Compute extends Scope {
|
|
26
33
|
/**
|
|
@@ -30,9 +37,23 @@ export abstract class Compute extends Scope {
|
|
|
30
37
|
*/
|
|
31
38
|
readonly namespaces: string[] = [];
|
|
32
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Whether tracing has been enabled on this compute — flipped by
|
|
42
|
+
* {@link enableTracing}. Private so it can't be set independently of the
|
|
43
|
+
* infra; read internally by {@link dashboardSection} to decide whether to
|
|
44
|
+
* render the traces section.
|
|
45
|
+
*/
|
|
46
|
+
private tracerEnabled = false;
|
|
47
|
+
|
|
48
|
+
/** Read-only view of {@link tracerEnabled} for subclasses (e.g. to guard
|
|
49
|
+
* their `tracingWidgets` builder). Subclasses can read but not set it. */
|
|
50
|
+
protected get isTracerEnabled(): boolean {
|
|
51
|
+
return this.tracerEnabled;
|
|
52
|
+
}
|
|
53
|
+
|
|
33
54
|
constructor(id: string, options?: ScopeOptions) {
|
|
34
55
|
super(id, options);
|
|
35
|
-
// Self-register on the owning stack so finalize steps (
|
|
56
|
+
// Self-register on the owning stack so finalize steps (tracing, routing,
|
|
36
57
|
// dashboards) can enumerate every compute without a separate discovery
|
|
37
58
|
// pass. Scoped per stack, so a multi-stack synth keeps lists isolated.
|
|
38
59
|
registerCompute(this);
|
|
@@ -44,4 +65,85 @@ export abstract class Compute extends Scope {
|
|
|
44
65
|
* directly so config targets the right compute.
|
|
45
66
|
*/
|
|
46
67
|
abstract setEnv(key: string, value: string): void;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Enable distributed tracing on this compute: mark it traced (so the Dashboard
|
|
71
|
+
* renders its traces section) and turn on the compute's active tracing via
|
|
72
|
+
* {@link applyTracing}. Idempotent — the framework calls this on **every**
|
|
73
|
+
* compute when the app contains a `Tracer` (tracing is presence-gated, not
|
|
74
|
+
* per-compute), so calling it more than once is a no-op.
|
|
75
|
+
*/
|
|
76
|
+
enableTracing(): void {
|
|
77
|
+
if (this.tracerEnabled) return;
|
|
78
|
+
this.tracerEnabled = true;
|
|
79
|
+
this.applyTracing();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Turn on this compute's active tracing (e.g. X-Ray) and grant its role the
|
|
84
|
+
* permission to publish trace segments. Called by {@link enableTracing};
|
|
85
|
+
* `protected` so tracing can't be turned on without marking the compute
|
|
86
|
+
* traced.
|
|
87
|
+
*/
|
|
88
|
+
protected abstract applyTracing(): void;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Build this compute's CloudWatch Dashboard section: health widgets and log
|
|
92
|
+
* widgets **always** (logs are always captured for a compute), plus trace
|
|
93
|
+
* widgets **only when** tracing is enabled on this compute (via
|
|
94
|
+
* {@link enableTracing}).
|
|
95
|
+
*
|
|
96
|
+
* This is the single public entry the Dashboard Building Block uses; the
|
|
97
|
+
* per-kind builders below are `protected`. Whether the logs / traces sections
|
|
98
|
+
* are actually shown is a display choice the Dashboard makes on top (its
|
|
99
|
+
* `logs` / `traces` options) — this returns what the compute *has*.
|
|
100
|
+
*
|
|
101
|
+
* @param region - AWS region the widgets query metrics in.
|
|
102
|
+
*/
|
|
103
|
+
dashboardSection(region: string): ComputeDashboardSection {
|
|
104
|
+
return {
|
|
105
|
+
// The scope id (e.g. 'DefaultCompute', 'api') — short and readable for a
|
|
106
|
+
// section header, and distinct per compute within a stack.
|
|
107
|
+
label: this.id,
|
|
108
|
+
health: this.healthWidgets(region),
|
|
109
|
+
logging: this.loggingWidgets(region),
|
|
110
|
+
tracing: this.tracerEnabled ? this.tracingWidgets(region) : undefined,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Build this compute's health widget rows. Implemented by a concrete compute;
|
|
116
|
+
* obtained only via {@link dashboardSection}.
|
|
117
|
+
*/
|
|
118
|
+
protected abstract healthWidgets(region: string): IWidget[][];
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Build this compute's **log** widget rows for its own log group. Logs always
|
|
122
|
+
* exist, so this is always available; the Dashboard's `logs` option decides
|
|
123
|
+
* whether to render it.
|
|
124
|
+
*/
|
|
125
|
+
protected abstract loggingWidgets(region: string): IWidget[][];
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Build this compute's **trace** widget rows. Gated behind
|
|
129
|
+
* {@link dashboardSection} so it is only used when tracing is enabled.
|
|
130
|
+
*/
|
|
131
|
+
protected abstract tracingWidgets(region: string): IWidget[][];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* A compute's self-reported CloudWatch Dashboard section. `health` and `logging`
|
|
136
|
+
* are always present; `tracing` is populated only when tracing is enabled on the
|
|
137
|
+
* compute. (The Dashboard may still hide `logging` / `tracing` via its display
|
|
138
|
+
* options.)
|
|
139
|
+
*/
|
|
140
|
+
export interface ComputeDashboardSection {
|
|
141
|
+
/** Display label used as the compute's group header. */
|
|
142
|
+
label: string;
|
|
143
|
+
/** Health widget rows — always present. */
|
|
144
|
+
health: IWidget[][];
|
|
145
|
+
/** Log widget rows — always present (logs are always captured). */
|
|
146
|
+
logging?: IWidget[][];
|
|
147
|
+
/** Trace widget rows — present only when tracing is enabled. */
|
|
148
|
+
tracing?: IWidget[][];
|
|
47
149
|
}
|
|
@@ -13,6 +13,7 @@ import assert from 'node:assert';
|
|
|
13
13
|
import { afterEach, test } from 'node:test';
|
|
14
14
|
import * as cdk from 'aws-cdk-lib';
|
|
15
15
|
import { Match, Template } from 'aws-cdk-lib/assertions';
|
|
16
|
+
import type { IWidget } from 'aws-cdk-lib/aws-cloudwatch';
|
|
16
17
|
import { Construct } from 'constructs';
|
|
17
18
|
import { Compute } from './compute/compute.js';
|
|
18
19
|
import { finalizeConfigRegistry, getConfigLocation, registerConfig } from './config-registry.js';
|
|
@@ -42,6 +43,19 @@ class TestCompute extends Compute {
|
|
|
42
43
|
setEnv(key: string, value: string): void {
|
|
43
44
|
this.fn.addEnvironment(key, value);
|
|
44
45
|
}
|
|
46
|
+
|
|
47
|
+
// Observability hooks are irrelevant to config-registry tests — stub them so
|
|
48
|
+
// this test double satisfies Compute's abstract contract.
|
|
49
|
+
protected applyTracing(): void {}
|
|
50
|
+
protected healthWidgets(): IWidget[][] {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
protected loggingWidgets(): IWidget[][] {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
protected tracingWidgets(): IWidget[][] {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
45
59
|
}
|
|
46
60
|
|
|
47
61
|
function stackWithCompute(id: string): {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import * as cdk from 'aws-cdk-lib';
|
|
4
|
+
import type { Construct } from 'constructs';
|
|
5
|
+
|
|
6
|
+
const REGISTRY_KEY = Symbol.for('BLOCKS_DASHBOARD_REGISTRY');
|
|
7
|
+
|
|
8
|
+
/** A Dashboard's deferred widget-body build, run once after the app is constructed. */
|
|
9
|
+
type DashboardFinalizer = () => void;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get or create the deferred-dashboard list for a given stack. Stored on the
|
|
13
|
+
* stack object (keyed by a Symbol), so each stack in a multi-stack synth gets
|
|
14
|
+
* its own — mirrors the config + compute registries.
|
|
15
|
+
*/
|
|
16
|
+
function getRegistry(stack: cdk.Stack): DashboardFinalizer[] {
|
|
17
|
+
let list = (stack as unknown as Record<symbol, DashboardFinalizer[] | undefined>)[REGISTRY_KEY];
|
|
18
|
+
if (!list) {
|
|
19
|
+
list = [];
|
|
20
|
+
(stack as unknown as Record<symbol, DashboardFinalizer[]>)[REGISTRY_KEY] = list;
|
|
21
|
+
}
|
|
22
|
+
return list;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Register a Dashboard's deferred body-build, to run after every Building Block
|
|
27
|
+
* in the app has been constructed (the end of `BlocksStack`/`BlocksBackend`
|
|
28
|
+
* `create()`, once the backend module has fully imported).
|
|
29
|
+
*
|
|
30
|
+
* The Dashboard builds its widget body here rather than in its constructor
|
|
31
|
+
* because the body depends on which computes are traced, and a `Tracer` may be
|
|
32
|
+
* constructed *after* the Dashboard. Deferring makes the Dashboard observe the
|
|
33
|
+
* complete app, so it never depends on construction order. (The Dashboard's
|
|
34
|
+
* CloudWatch resource itself is created eagerly in the constructor, so its URL /
|
|
35
|
+
* redirect route never dangle — only the body is deferred.)
|
|
36
|
+
*
|
|
37
|
+
* This is intentionally scoped to the Dashboard (the only deferred-build case
|
|
38
|
+
* today) rather than a generic finalizer mechanism; generalize it only if a
|
|
39
|
+
* second use case appears.
|
|
40
|
+
*
|
|
41
|
+
* @param scope - Any construct in the stack (used to locate the stack).
|
|
42
|
+
* @param finalize - The deferred body-build; run once (in registration order)
|
|
43
|
+
* by {@link finalizeDashboards}.
|
|
44
|
+
*/
|
|
45
|
+
export function registerDashboardFinalizer(scope: Construct, finalize: DashboardFinalizer): void {
|
|
46
|
+
getRegistry(cdk.Stack.of(scope)).push(finalize);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Run — and clear — every registered Dashboard finalizer on `scope`'s stack, in
|
|
51
|
+
* registration order. Called once from `create()` after the backend module has
|
|
52
|
+
* imported. Clearing the list makes a repeated call a no-op, so a dashboard's
|
|
53
|
+
* body is never built twice.
|
|
54
|
+
*
|
|
55
|
+
* A Dashboard constructed outside `create()` (e.g. directly in a unit test) must
|
|
56
|
+
* call this explicitly before synth — the same way `config-registry.test.ts`
|
|
57
|
+
* drives `finalizeConfigRegistry`. (A Dashboard's CloudWatch resource is created
|
|
58
|
+
* eagerly in its constructor, so even if its finalizer never runs its URL /
|
|
59
|
+
* redirect never dangle — only the widget body is missing.)
|
|
60
|
+
*
|
|
61
|
+
* @param scope - Any construct in the stack (used to locate the stack).
|
|
62
|
+
*/
|
|
63
|
+
export function finalizeDashboards(scope: Construct): void {
|
|
64
|
+
const list = getRegistry(cdk.Stack.of(scope));
|
|
65
|
+
// Drain the list so a repeated call can't rebuild an already-built dashboard.
|
|
66
|
+
const pending = list.splice(0, list.length);
|
|
67
|
+
for (const finalize of pending) finalize();
|
|
68
|
+
}
|