@harperfast/harper 5.2.0 → 5.2.1
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/bin/copyDb.ts +21 -4
- package/bin/harper.ts +20 -51
- package/bin/help.ts +216 -0
- package/components/Application.ts +236 -46
- package/components/ApplicationScope.ts +26 -0
- package/components/EntryHandler.ts +410 -105
- package/components/RuntimeModuleTracker.ts +189 -0
- package/components/Scope.ts +68 -24
- package/components/componentLoader.ts +70 -16
- package/components/deployLifecycle.ts +119 -33
- package/components/mcp/tools/operations.ts +6 -0
- package/components/operations.js +4 -6
- package/config/configUtils.ts +12 -9
- package/config-root.schema.json +10 -0
- package/dataLayer/harperBridge/ResourceBridge.ts +26 -5
- package/dataLayer/hdbInfoController.ts +8 -0
- package/dataLayer/schemaDescribe.ts +2 -1
- package/dist/bin/copyDb.js +13 -2
- package/dist/bin/copyDb.js.map +1 -1
- package/dist/bin/harper.d.ts +6 -0
- package/dist/bin/harper.js +18 -50
- package/dist/bin/harper.js.map +1 -1
- package/dist/bin/help.d.ts +8 -0
- package/dist/bin/help.js +192 -0
- package/dist/bin/help.js.map +1 -0
- package/dist/components/Application.d.ts +16 -1
- package/dist/components/Application.js +210 -38
- package/dist/components/Application.js.map +1 -1
- package/dist/components/ApplicationScope.d.ts +7 -0
- package/dist/components/ApplicationScope.js +22 -0
- package/dist/components/ApplicationScope.js.map +1 -1
- package/dist/components/EntryHandler.d.ts +4 -4
- package/dist/components/EntryHandler.js +386 -95
- package/dist/components/EntryHandler.js.map +1 -1
- package/dist/components/RuntimeModuleTracker.d.ts +11 -0
- package/dist/components/RuntimeModuleTracker.js +189 -0
- package/dist/components/RuntimeModuleTracker.js.map +1 -0
- package/dist/components/Scope.d.ts +1 -0
- package/dist/components/Scope.js +69 -22
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/componentLoader.js +69 -10
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/deployLifecycle.d.ts +6 -2
- package/dist/components/deployLifecycle.js +109 -31
- package/dist/components/deployLifecycle.js.map +1 -1
- package/dist/components/mcp/tools/operations.js +6 -0
- package/dist/components/mcp/tools/operations.js.map +1 -1
- package/dist/components/operations.js +4 -6
- package/dist/components/operations.js.map +1 -1
- package/dist/config/configUtils.js +13 -9
- package/dist/config/configUtils.js.map +1 -1
- package/dist/dataLayer/harperBridge/ResourceBridge.js +12 -5
- package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
- package/dist/dataLayer/hdbInfoController.js +4 -0
- package/dist/dataLayer/hdbInfoController.js.map +1 -1
- package/dist/dataLayer/schemaDescribe.js +2 -1
- package/dist/dataLayer/schemaDescribe.js.map +1 -1
- package/dist/resources/DatabaseTransaction.d.ts +55 -0
- package/dist/resources/DatabaseTransaction.js +282 -103
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +8 -2
- package/dist/resources/ResourceInterface.js.map +1 -1
- package/dist/resources/Resources.js +22 -4
- package/dist/resources/Resources.js.map +1 -1
- package/dist/resources/Table.d.ts +6 -5
- package/dist/resources/Table.js +92 -15
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/write.js +6 -6
- package/dist/resources/analytics/write.js.map +1 -1
- package/dist/resources/blob.d.ts +0 -1
- package/dist/resources/blob.js +15 -7
- package/dist/resources/blob.js.map +1 -1
- package/dist/resources/databases.d.ts +22 -5
- package/dist/resources/databases.js +118 -9
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/jsResource.d.ts +4 -26
- package/dist/resources/jsResource.js +5 -59
- package/dist/resources/jsResource.js.map +1 -1
- package/dist/resources/models/Models.d.ts +11 -1
- package/dist/resources/models/Models.js +10 -1
- package/dist/resources/models/Models.js.map +1 -1
- package/dist/resources/models/backendRegistry.d.ts +9 -0
- package/dist/resources/models/backendRegistry.js +10 -0
- package/dist/resources/models/backendRegistry.js.map +1 -1
- package/dist/resources/models/openaiStream.d.ts +16 -1
- package/dist/resources/models/openaiStream.js +113 -21
- package/dist/resources/models/openaiStream.js.map +1 -1
- package/dist/resources/models/v1/chatCompletions.d.ts +54 -0
- package/dist/resources/models/v1/chatCompletions.js +115 -0
- package/dist/resources/models/v1/chatCompletions.js.map +1 -0
- package/dist/resources/models/v1/embeddings.d.ts +11 -0
- package/dist/resources/models/v1/embeddings.js +71 -0
- package/dist/resources/models/v1/embeddings.js.map +1 -0
- package/dist/resources/models/v1/errors.d.ts +54 -0
- package/dist/resources/models/v1/errors.js +130 -0
- package/dist/resources/models/v1/errors.js.map +1 -0
- package/dist/resources/models/v1/index.d.ts +36 -0
- package/dist/resources/models/v1/index.js +75 -0
- package/dist/resources/models/v1/index.js.map +1 -0
- package/dist/resources/models/v1/models.d.ts +26 -0
- package/dist/resources/models/v1/models.js +44 -0
- package/dist/resources/models/v1/models.js.map +1 -0
- package/dist/resources/models/v1/translation.d.ts +133 -0
- package/dist/resources/models/v1/translation.js +298 -0
- package/dist/resources/models/v1/translation.js.map +1 -0
- package/dist/resources/roles.d.ts +1 -1
- package/dist/resources/roles.js +54 -7
- package/dist/resources/roles.js.map +1 -1
- package/dist/security/jsLoader.js +84 -33
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/security/role.js +4 -0
- package/dist/security/role.js.map +1 -1
- package/dist/security/superUserGuard.d.ts +7 -0
- package/dist/security/superUserGuard.js +23 -0
- package/dist/security/superUserGuard.js.map +1 -0
- package/dist/security/tokenAuthentication.d.ts +0 -1
- package/dist/security/tokenAuthentication.js +6 -2
- package/dist/security/tokenAuthentication.js.map +1 -1
- package/dist/security/user.d.ts +6 -1
- package/dist/security/user.js +23 -1
- package/dist/security/user.js.map +1 -1
- package/dist/server/http.d.ts +23 -2
- package/dist/server/http.js +99 -12
- package/dist/server/http.js.map +1 -1
- package/dist/server/operationsServer.d.ts +0 -2
- package/dist/server/operationsServer.js.map +1 -1
- package/dist/server/serverHelpers/multipartParser.js +9 -0
- package/dist/server/serverHelpers/multipartParser.js.map +1 -1
- package/dist/server/serverHelpers/operationAuthorizationState.d.ts +2 -0
- package/dist/server/serverHelpers/operationAuthorizationState.js +13 -0
- package/dist/server/serverHelpers/operationAuthorizationState.js.map +1 -0
- package/dist/server/serverHelpers/registeredOperations.d.ts +3 -2
- package/dist/server/serverHelpers/registeredOperations.js +14 -13
- package/dist/server/serverHelpers/registeredOperations.js.map +1 -1
- package/dist/server/serverHelpers/serverHandlers.js +19 -3
- package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.d.ts +1 -1
- package/dist/server/serverHelpers/serverUtilities.js +10 -7
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/static.js +57 -18
- package/dist/server/static.js.map +1 -1
- package/dist/server/storageReclamation.d.ts +17 -0
- package/dist/server/storageReclamation.js +90 -6
- package/dist/server/storageReclamation.js.map +1 -1
- package/dist/server/threads/socketRouter.js +20 -0
- package/dist/server/threads/socketRouter.js.map +1 -1
- package/dist/server/threads/threadServer.js +12 -0
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/sqlEngine/diff/differential.js +7 -3
- package/dist/sqlEngine/diff/differential.js.map +1 -1
- package/dist/sqlTranslator/index.js +6 -1
- package/dist/sqlTranslator/index.js.map +1 -1
- package/dist/upgrade/upgradePrompt.d.ts +2 -2
- package/dist/upgrade/upgradePrompt.js +22 -3
- package/dist/upgrade/upgradePrompt.js.map +1 -1
- package/dist/utility/errors/commonErrors.d.ts +1 -0
- package/dist/utility/errors/commonErrors.js +1 -0
- package/dist/utility/errors/commonErrors.js.map +1 -1
- package/dist/utility/hdbTerms.d.ts +2 -0
- package/dist/utility/hdbTerms.js +2 -0
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/install/installer.js +32 -0
- package/dist/utility/install/installer.js.map +1 -1
- package/dist/utility/logging/harper_logger.d.ts +7 -0
- package/dist/utility/logging/harper_logger.js +57 -24
- package/dist/utility/logging/harper_logger.js.map +1 -1
- package/npm-shrinkwrap.json +197 -193
- package/package.json +9 -7
- package/resources/DatabaseTransaction.ts +310 -101
- package/resources/ResourceInterface.ts +8 -2
- package/resources/Resources.ts +22 -4
- package/resources/Table.ts +321 -243
- package/resources/analytics/write.ts +22 -20
- package/resources/blob.ts +15 -8
- package/resources/databases.ts +123 -12
- package/resources/jsResource.ts +5 -62
- package/resources/models/Models.ts +14 -1
- package/resources/models/backendRegistry.ts +10 -0
- package/resources/models/openaiStream.ts +131 -19
- package/resources/models/v1/chatCompletions.ts +128 -0
- package/resources/models/v1/embeddings.ts +70 -0
- package/resources/models/v1/errors.ts +141 -0
- package/resources/models/v1/index.ts +72 -0
- package/resources/models/v1/models.ts +53 -0
- package/resources/models/v1/translation.ts +362 -0
- package/resources/roles.ts +67 -7
- package/security/jsLoader.ts +84 -30
- package/security/role.ts +7 -0
- package/security/superUserGuard.ts +20 -0
- package/security/tokenAuthentication.ts +6 -3
- package/security/user.ts +26 -1
- package/server/DESIGN.md +45 -34
- package/server/http.ts +100 -13
- package/server/operationsServer.ts +0 -2
- package/server/serverHelpers/multipartParser.ts +9 -0
- package/server/serverHelpers/operationAuthorizationState.ts +11 -0
- package/server/serverHelpers/registeredOperations.ts +19 -15
- package/server/serverHelpers/serverHandlers.js +20 -3
- package/server/serverHelpers/serverUtilities.ts +10 -7
- package/server/static.ts +75 -20
- package/server/storageReclamation.ts +104 -8
- package/server/threads/socketRouter.ts +20 -0
- package/server/threads/threadServer.js +11 -0
- package/sqlTranslator/index.ts +6 -1
- package/static/defaultConfig.yaml +2 -0
- package/studio/web/assets/{Chat-DoVWScmq.js → Chat-aApwhRmz.js} +2 -2
- package/studio/web/assets/{Chat-DoVWScmq.js.map → Chat-aApwhRmz.js.map} +1 -1
- package/studio/web/assets/{FloatingChat-UZ2NsUOZ.js → FloatingChat-sC0H91n9.js} +4 -4
- package/studio/web/assets/{FloatingChat-UZ2NsUOZ.js.map → FloatingChat-sC0H91n9.js.map} +1 -1
- package/studio/web/assets/{apiToken-BUI_04o7.js → apiToken-DZ8JhHOI.js} +2 -2
- package/studio/web/assets/{apiToken-BUI_04o7.js.map → apiToken-DZ8JhHOI.js.map} +1 -1
- package/studio/web/assets/{applications-D03NA7wW.js → applications-9SEVLIO5.js} +2 -2
- package/studio/web/assets/{applications-D03NA7wW.js.map → applications-9SEVLIO5.js.map} +1 -1
- package/studio/web/assets/{index-Bh_CNAHr.js → index-Dy3uDGXb.js} +6 -6
- package/studio/web/assets/{index-Bh_CNAHr.js.map → index-Dy3uDGXb.js.map} +1 -1
- package/studio/web/assets/{index.lazy-Dx3MpyDC.js → index.lazy-CN1zq4I4.js} +4 -4
- package/studio/web/assets/{index.lazy-Dx3MpyDC.js.map → index.lazy-CN1zq4I4.js.map} +1 -1
- package/studio/web/assets/{notifications-0edoFTsb.js → notifications-BFAF07xr.js} +2 -2
- package/studio/web/assets/{notifications-0edoFTsb.js.map → notifications-BFAF07xr.js.map} +1 -1
- package/studio/web/assets/{notifications-CwKhipK7.js → notifications-CKlYVvVN.js} +2 -2
- package/studio/web/assets/{notifications-CwKhipK7.js.map → notifications-CKlYVvVN.js.map} +1 -1
- package/studio/web/assets/{profile-DUfEPQtx.js → profile-A1zhEdFG.js} +2 -2
- package/studio/web/assets/{profile-DUfEPQtx.js.map → profile-A1zhEdFG.js.map} +1 -1
- package/studio/web/assets/{setComponentFile-DMPo4UjC.js → setComponentFile-SEtBt_GV.js} +2 -2
- package/studio/web/assets/{setComponentFile-DMPo4UjC.js.map → setComponentFile-SEtBt_GV.js.map} +1 -1
- package/studio/web/assets/{setup-B56Oz1_u.js → setup-BisINqdH.js} +2 -2
- package/studio/web/assets/{setup-B56Oz1_u.js.map → setup-BisINqdH.js.map} +1 -1
- package/studio/web/assets/{status-BAod7p3o.js → status-BuQoCc7l.js} +2 -2
- package/studio/web/assets/{status-BAod7p3o.js.map → status-BuQoCc7l.js.map} +1 -1
- package/studio/web/assets/{swagger-ui-react-lQrBxfwM.js → swagger-ui-react-CAi_s1PC.js} +2 -2
- package/studio/web/assets/{swagger-ui-react-lQrBxfwM.js.map → swagger-ui-react-CAi_s1PC.js.map} +1 -1
- package/studio/web/assets/{tsMode-CrHCRjTK.js → tsMode-DT74tlkM.js} +2 -2
- package/studio/web/assets/{tsMode-CrHCRjTK.js.map → tsMode-DT74tlkM.js.map} +1 -1
- package/studio/web/assets/{useEntityRestURL-DoaBMEvU.js → useEntityRestURL-CGRGc1n7.js} +2 -2
- package/studio/web/assets/{useEntityRestURL-DoaBMEvU.js.map → useEntityRestURL-CGRGc1n7.js.map} +1 -1
- package/studio/web/index.html +1 -1
- package/upgrade/upgradePrompt.ts +22 -3
- package/utility/errors/commonErrors.ts +2 -0
- package/utility/hdbTerms.ts +2 -0
- package/utility/install/installer.ts +37 -0
- package/utility/logging/harper_logger.ts +57 -26
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.EntryHandler = void 0;
|
|
7
7
|
const harper_logger_ts_1 = require("../utility/logging/harper_logger.js");
|
|
8
|
+
const node_crypto_1 = require("node:crypto");
|
|
8
9
|
const node_events_1 = require("node:events");
|
|
9
10
|
const Component_ts_1 = require("./Component.js");
|
|
10
11
|
const chokidar_1 = __importDefault(require("chokidar"));
|
|
@@ -18,29 +19,63 @@ class EntryHandler extends node_events_1.EventEmitter {
|
|
|
18
19
|
#watcher;
|
|
19
20
|
#logger;
|
|
20
21
|
#pendingFileReads;
|
|
22
|
+
#pendingFileReadsByGeneration;
|
|
21
23
|
#isInitialScanComplete;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
#readyPending = false;
|
|
25
|
+
#readyGeneration = 0;
|
|
26
|
+
#watchGeneration = 0;
|
|
27
|
+
#eventSequence = 0;
|
|
28
|
+
#latestPathSequence = new Map();
|
|
29
|
+
#entries = new Map();
|
|
30
|
+
#redeployScan;
|
|
26
31
|
#paused = false;
|
|
27
|
-
// Tracks the in-flight close() promise from pause() so resume() can await
|
|
28
|
-
// the old watcher's inotify handles releasing before installing a fresh
|
|
29
|
-
// chokidar instance — otherwise a rapid pause→resume can overlap teardown
|
|
30
|
-
// and setup, which under inotify pressure can produce spurious EMFILE.
|
|
31
32
|
#pausedClose;
|
|
33
|
+
// Serializes replacements so concurrent update/recovery calls cannot orphan an intervening watcher.
|
|
34
|
+
#watchInstall;
|
|
32
35
|
#usingPolling = false;
|
|
33
36
|
#closed = false;
|
|
34
37
|
#openCount = 0;
|
|
38
|
+
#liveWatchers = new Set();
|
|
35
39
|
ready;
|
|
36
40
|
constructor(name, directory, config, logger) {
|
|
37
41
|
super();
|
|
38
42
|
this.#component = new Component_ts_1.Component(name, directory, castConfig(config));
|
|
39
43
|
this.#logger = logger || (0, harper_logger_ts_1.loggerWithTag)(name);
|
|
40
44
|
this.#pendingFileReads = new Set();
|
|
45
|
+
this.#pendingFileReadsByGeneration = new Map();
|
|
41
46
|
this.#isInitialScanComplete = false;
|
|
42
|
-
this.ready = (
|
|
43
|
-
this.#watch();
|
|
47
|
+
this.ready = this.#createReadyLatch();
|
|
48
|
+
this.#watch().catch(() => { });
|
|
49
|
+
}
|
|
50
|
+
#createReadyLatch() {
|
|
51
|
+
if (this.#readyPending)
|
|
52
|
+
return this.ready;
|
|
53
|
+
this.#readyPending = true;
|
|
54
|
+
const ready = new Promise((resolve, reject) => {
|
|
55
|
+
const cleanup = () => {
|
|
56
|
+
this.#readyPending = false;
|
|
57
|
+
this.off('ready', handleReady);
|
|
58
|
+
this.off('error', handleError);
|
|
59
|
+
this.off('close', handleClose);
|
|
60
|
+
};
|
|
61
|
+
const handleReady = () => {
|
|
62
|
+
cleanup();
|
|
63
|
+
resolve([]);
|
|
64
|
+
};
|
|
65
|
+
const handleError = (error) => {
|
|
66
|
+
cleanup();
|
|
67
|
+
reject(error);
|
|
68
|
+
};
|
|
69
|
+
const handleClose = () => {
|
|
70
|
+
cleanup();
|
|
71
|
+
reject(new Error(`Entry handler for ${this.name} closed before becoming ready`));
|
|
72
|
+
};
|
|
73
|
+
this.once('ready', handleReady);
|
|
74
|
+
this.once('error', handleError);
|
|
75
|
+
this.once('close', handleClose);
|
|
76
|
+
});
|
|
77
|
+
void ready.catch(() => { });
|
|
78
|
+
return ready;
|
|
44
79
|
}
|
|
45
80
|
get name() {
|
|
46
81
|
return this.#component.name;
|
|
@@ -48,7 +83,9 @@ class EntryHandler extends node_events_1.EventEmitter {
|
|
|
48
83
|
get directory() {
|
|
49
84
|
return this.#component.directory;
|
|
50
85
|
}
|
|
51
|
-
#handleAll(...[event, path, stats]) {
|
|
86
|
+
#handleAll(generation, ...[event, path, stats]) {
|
|
87
|
+
if (generation !== this.#watchGeneration)
|
|
88
|
+
return;
|
|
52
89
|
if (path === '')
|
|
53
90
|
path = '/';
|
|
54
91
|
if (!(0, micromatch_1.isMatch)(path, this.#component.globOptions.source, { ignore: this.#component.globOptions.ignore }))
|
|
@@ -57,25 +94,7 @@ class EntryHandler extends node_events_1.EventEmitter {
|
|
|
57
94
|
switch (event) {
|
|
58
95
|
case 'add':
|
|
59
96
|
case 'change': {
|
|
60
|
-
|
|
61
|
-
const fileReadPromise = (0, promises_1.readFile)(absolutePath)
|
|
62
|
-
.then((contents) => {
|
|
63
|
-
const entry = {
|
|
64
|
-
eventType: event,
|
|
65
|
-
entryType: 'file',
|
|
66
|
-
contents,
|
|
67
|
-
stats,
|
|
68
|
-
absolutePath,
|
|
69
|
-
urlPath,
|
|
70
|
-
};
|
|
71
|
-
this.emit('all', entry);
|
|
72
|
-
this.emit(event, entry);
|
|
73
|
-
})
|
|
74
|
-
.finally(() => {
|
|
75
|
-
this.#pendingFileReads.delete(fileReadPromise);
|
|
76
|
-
this.#checkIfAllComplete();
|
|
77
|
-
});
|
|
78
|
-
this.#pendingFileReads.add(fileReadPromise);
|
|
97
|
+
this.#queueFileRead(generation, event, path, absolutePath, stats, (0, promises_1.readFile)(absolutePath));
|
|
79
98
|
break;
|
|
80
99
|
}
|
|
81
100
|
case 'unlink': {
|
|
@@ -87,27 +106,200 @@ class EntryHandler extends node_events_1.EventEmitter {
|
|
|
87
106
|
absolutePath,
|
|
88
107
|
urlPath,
|
|
89
108
|
};
|
|
90
|
-
this
|
|
91
|
-
this.emit(event, entry);
|
|
109
|
+
this.#handleRemovedEntry(generation, entry);
|
|
92
110
|
break;
|
|
93
111
|
}
|
|
94
112
|
case 'addDir':
|
|
95
113
|
case 'unlinkDir': {
|
|
96
114
|
const urlPath = (0, deriveURLPath_ts_1.deriveURLPath)(this.#component, path, 'directory');
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
115
|
+
if (event === 'addDir') {
|
|
116
|
+
const entry = {
|
|
117
|
+
eventType: event,
|
|
118
|
+
entryType: 'directory',
|
|
119
|
+
stats,
|
|
120
|
+
absolutePath,
|
|
121
|
+
urlPath,
|
|
122
|
+
};
|
|
123
|
+
this.#handleAddedEntry(generation, entry, this.#snapshot(entry));
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
const entry = {
|
|
127
|
+
eventType: event,
|
|
128
|
+
entryType: 'directory',
|
|
129
|
+
stats,
|
|
130
|
+
absolutePath,
|
|
131
|
+
urlPath,
|
|
132
|
+
};
|
|
133
|
+
this.#handleRemovedEntry(generation, entry);
|
|
134
|
+
}
|
|
106
135
|
break;
|
|
107
136
|
}
|
|
108
137
|
}
|
|
109
138
|
}
|
|
110
|
-
#
|
|
139
|
+
#queueFileRead(generation, event, path, absolutePath, stats, contentsPromise) {
|
|
140
|
+
const sequence = ++this.#eventSequence;
|
|
141
|
+
this.#latestPathSequence.set(absolutePath, sequence);
|
|
142
|
+
const fileReadPromise = contentsPromise
|
|
143
|
+
.then((contents) => {
|
|
144
|
+
if (generation !== this.#watchGeneration || this.#latestPathSequence.get(absolutePath) !== sequence)
|
|
145
|
+
return;
|
|
146
|
+
if (this.#redeployScan?.generation === generation)
|
|
147
|
+
this.#redeployScan.readFailures.delete(absolutePath);
|
|
148
|
+
const entry = {
|
|
149
|
+
eventType: event,
|
|
150
|
+
entryType: 'file',
|
|
151
|
+
contents,
|
|
152
|
+
stats,
|
|
153
|
+
absolutePath,
|
|
154
|
+
urlPath: (0, deriveURLPath_ts_1.deriveURLPath)(this.#component, path, 'file'),
|
|
155
|
+
};
|
|
156
|
+
try {
|
|
157
|
+
this.#handleAddedEntry(generation, entry, this.#snapshot(entry));
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
this.#reportConsumerError(error);
|
|
161
|
+
}
|
|
162
|
+
}, (error) => {
|
|
163
|
+
if (generation !== this.#watchGeneration ||
|
|
164
|
+
this.#latestPathSequence.get(absolutePath) !== sequence ||
|
|
165
|
+
error?.code === 'ENOENT')
|
|
166
|
+
return;
|
|
167
|
+
if (this.#redeployScan?.generation === generation) {
|
|
168
|
+
this.#redeployScan.readFailures.set(absolutePath, error);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
this.#handleWatcherError(error);
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
.finally(() => {
|
|
175
|
+
if (this.#latestPathSequence.get(absolutePath) === sequence)
|
|
176
|
+
this.#latestPathSequence.delete(absolutePath);
|
|
177
|
+
this.#pendingFileReads.delete(fileReadPromise);
|
|
178
|
+
const generationReads = this.#pendingFileReadsByGeneration.get(generation);
|
|
179
|
+
generationReads?.delete(fileReadPromise);
|
|
180
|
+
if (generationReads?.size === 0)
|
|
181
|
+
this.#pendingFileReadsByGeneration.delete(generation);
|
|
182
|
+
this.#checkIfAllComplete(generation);
|
|
183
|
+
});
|
|
184
|
+
this.#pendingFileReads.add(fileReadPromise);
|
|
185
|
+
let generationReads = this.#pendingFileReadsByGeneration.get(generation);
|
|
186
|
+
if (!generationReads)
|
|
187
|
+
this.#pendingFileReadsByGeneration.set(generation, (generationReads = new Set()));
|
|
188
|
+
generationReads.add(fileReadPromise);
|
|
189
|
+
return fileReadPromise;
|
|
190
|
+
}
|
|
191
|
+
#snapshot(entry) {
|
|
192
|
+
if (entry.entryType === 'file')
|
|
193
|
+
return {
|
|
194
|
+
entryType: entry.entryType,
|
|
195
|
+
urlPath: entry.urlPath,
|
|
196
|
+
digest: (0, node_crypto_1.createHash)('sha256').update(entry.contents).digest(),
|
|
197
|
+
};
|
|
198
|
+
return { entryType: entry.entryType, urlPath: entry.urlPath };
|
|
199
|
+
}
|
|
200
|
+
#handleAddedEntry(generation, entry, snapshot) {
|
|
201
|
+
if (generation !== this.#watchGeneration)
|
|
202
|
+
return;
|
|
203
|
+
const scan = this.#redeployScan?.generation === generation ? this.#redeployScan : undefined;
|
|
204
|
+
if (!scan) {
|
|
205
|
+
const wasKnown = this.#entries.has(entry.absolutePath);
|
|
206
|
+
this.#entries.set(entry.absolutePath, snapshot);
|
|
207
|
+
// A newer change read can supersede an initial add read for the same path. The consumer still
|
|
208
|
+
// needs its first event to be an add so it can load the entry before handling later changes.
|
|
209
|
+
if (entry.entryType === 'file' && entry.eventType === 'change' && !wasKnown)
|
|
210
|
+
this.#emitEntrySafely({ ...entry, eventType: 'add' });
|
|
211
|
+
else
|
|
212
|
+
this.#emitEntrySafely(entry);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const wasRemovedDuringScan = scan.removalsEmitted.delete(entry.absolutePath);
|
|
216
|
+
const currentEntry = scan.currentEntries.get(entry.absolutePath);
|
|
217
|
+
const previousEntry = wasRemovedDuringScan
|
|
218
|
+
? undefined
|
|
219
|
+
: (currentEntry ?? scan.previousEntries.get(entry.absolutePath));
|
|
220
|
+
scan.currentEntries.set(entry.absolutePath, snapshot);
|
|
221
|
+
if (!previousEntry) {
|
|
222
|
+
this.#emitAddition(entry);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (previousEntry.entryType !== snapshot.entryType || previousEntry.urlPath !== snapshot.urlPath) {
|
|
226
|
+
try {
|
|
227
|
+
this.#emitRemoval(previousEntry, entry.absolutePath);
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
this.#reportConsumerError(error);
|
|
231
|
+
}
|
|
232
|
+
this.#emitAddition(entry);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (snapshot.entryType === 'file' &&
|
|
236
|
+
previousEntry.entryType === 'file' &&
|
|
237
|
+
!previousEntry.digest.equals(snapshot.digest)) {
|
|
238
|
+
this.#emitEntrySafely({ ...entry, eventType: 'change' });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
#emitAddition(entry) {
|
|
242
|
+
if (entry.entryType === 'file')
|
|
243
|
+
this.#emitEntrySafely({ ...entry, eventType: 'add' });
|
|
244
|
+
else
|
|
245
|
+
this.#emitEntrySafely({ ...entry, eventType: 'addDir' });
|
|
246
|
+
}
|
|
247
|
+
#handleRemovedEntry(generation, entry) {
|
|
248
|
+
if (generation !== this.#watchGeneration)
|
|
249
|
+
return;
|
|
250
|
+
this.#latestPathSequence.delete(entry.absolutePath);
|
|
251
|
+
const scan = this.#redeployScan?.generation === generation ? this.#redeployScan : undefined;
|
|
252
|
+
if (!scan) {
|
|
253
|
+
this.#entries.delete(entry.absolutePath);
|
|
254
|
+
this.#emitEntrySafely(entry);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const knownEntry = scan.currentEntries.get(entry.absolutePath) ?? scan.previousEntries.get(entry.absolutePath);
|
|
258
|
+
scan.currentEntries.delete(entry.absolutePath);
|
|
259
|
+
if (!knownEntry || scan.removalsEmitted.has(entry.absolutePath))
|
|
260
|
+
return;
|
|
261
|
+
try {
|
|
262
|
+
this.#emitRemoval(knownEntry, entry.absolutePath);
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
this.#reportConsumerError(error);
|
|
266
|
+
}
|
|
267
|
+
scan.removalsEmitted.add(entry.absolutePath);
|
|
268
|
+
}
|
|
269
|
+
#emitRemoval(snapshot, absolutePath) {
|
|
270
|
+
if (snapshot.entryType === 'file')
|
|
271
|
+
this.#emitEntry({ eventType: 'unlink', entryType: 'file', absolutePath, urlPath: snapshot.urlPath });
|
|
272
|
+
else
|
|
273
|
+
this.#emitEntry({ eventType: 'unlinkDir', entryType: 'directory', absolutePath, urlPath: snapshot.urlPath });
|
|
274
|
+
}
|
|
275
|
+
#emitEntry(entry) {
|
|
276
|
+
this.emit('all', entry);
|
|
277
|
+
switch (entry.eventType) {
|
|
278
|
+
case 'add':
|
|
279
|
+
this.emit('add', entry);
|
|
280
|
+
break;
|
|
281
|
+
case 'change':
|
|
282
|
+
this.emit('change', entry);
|
|
283
|
+
break;
|
|
284
|
+
case 'unlink':
|
|
285
|
+
this.emit('unlink', entry);
|
|
286
|
+
break;
|
|
287
|
+
case 'addDir':
|
|
288
|
+
this.emit('addDir', entry);
|
|
289
|
+
break;
|
|
290
|
+
case 'unlinkDir':
|
|
291
|
+
this.emit('unlinkDir', entry);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
#emitEntrySafely(entry) {
|
|
295
|
+
try {
|
|
296
|
+
this.#emitEntry(entry);
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
this.#reportConsumerError(error);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
#handleWatcherError(error) {
|
|
111
303
|
if ((0, watcherFallback_ts_1.isWatcherExhaustionError)(error)) {
|
|
112
304
|
// Swallow every exhaustion error — chokidar can emit several before the
|
|
113
305
|
// failed native watcher closes, and we don't want a flurry of ENOSPC to
|
|
@@ -127,46 +319,133 @@ class EntryHandler extends node_events_1.EventEmitter {
|
|
|
127
319
|
}
|
|
128
320
|
return;
|
|
129
321
|
}
|
|
130
|
-
this
|
|
322
|
+
this.#reportConsumerError(error);
|
|
323
|
+
}
|
|
324
|
+
#reportConsumerError(error) {
|
|
325
|
+
if (this.listenerCount('error') > 0)
|
|
326
|
+
this.emit('error', error);
|
|
327
|
+
else
|
|
328
|
+
this.#logger.error?.('Error in entry listener:', error);
|
|
131
329
|
}
|
|
132
|
-
#handleReady() {
|
|
330
|
+
#handleReady(generation) {
|
|
331
|
+
if (generation !== this.#watchGeneration)
|
|
332
|
+
return;
|
|
133
333
|
this.#isInitialScanComplete = true;
|
|
134
|
-
|
|
135
|
-
|
|
334
|
+
const pendingReads = this.#pendingFileReadsByGeneration.get(generation)?.size ?? 0;
|
|
335
|
+
if (pendingReads > 0) {
|
|
336
|
+
this.#logger.debug?.(`Initial scan complete, still waiting for ${pendingReads} pending file reads`);
|
|
136
337
|
}
|
|
137
|
-
this.#checkIfAllComplete();
|
|
338
|
+
this.#checkIfAllComplete(generation);
|
|
138
339
|
}
|
|
139
|
-
#checkIfAllComplete() {
|
|
140
|
-
|
|
141
|
-
|
|
340
|
+
#checkIfAllComplete(generation) {
|
|
341
|
+
if (generation === this.#watchGeneration &&
|
|
342
|
+
this.#readyGeneration !== generation &&
|
|
343
|
+
this.#isInitialScanComplete &&
|
|
344
|
+
!this.#pendingFileReadsByGeneration.has(generation)) {
|
|
345
|
+
this.#readyGeneration = generation;
|
|
346
|
+
const { listenerErrors, readErrors } = this.#finishRedeployScan(generation);
|
|
142
347
|
this.emit('ready');
|
|
348
|
+
// The readiness latch rejects if `error` is emitted first. Surface consumer failures only
|
|
349
|
+
// after resolving the generation's ready latch so one bad removal listener cannot strand startup.
|
|
350
|
+
for (const error of listenerErrors)
|
|
351
|
+
this.#reportConsumerError(error);
|
|
352
|
+
for (const error of readErrors)
|
|
353
|
+
this.#handleWatcherError(error);
|
|
143
354
|
}
|
|
144
355
|
}
|
|
356
|
+
#finishRedeployScan(generation) {
|
|
357
|
+
const scan = this.#redeployScan;
|
|
358
|
+
if (!scan || scan.generation !== generation)
|
|
359
|
+
return { listenerErrors: [], readErrors: [] };
|
|
360
|
+
const listenerErrors = [];
|
|
361
|
+
for (const absolutePath of scan.readFailures.keys()) {
|
|
362
|
+
const previousEntry = scan.previousEntries.get(absolutePath);
|
|
363
|
+
if (previousEntry && !scan.currentEntries.has(absolutePath))
|
|
364
|
+
scan.currentEntries.set(absolutePath, previousEntry);
|
|
365
|
+
}
|
|
366
|
+
const missingEntries = [...scan.previousEntries].filter(([absolutePath]) => !scan.currentEntries.has(absolutePath) && !scan.removalsEmitted.has(absolutePath));
|
|
367
|
+
// Match watcher deletion semantics: descendants disappear before the directory that contained them.
|
|
368
|
+
missingEntries.sort(([a], [b]) => b.length - a.length);
|
|
369
|
+
// Commit the generation before invoking consumer code. A synchronously throwing removal listener must
|
|
370
|
+
// not leave the old snapshot installed or keep the readiness latch permanently unresolved.
|
|
371
|
+
this.#entries = scan.currentEntries;
|
|
372
|
+
this.#redeployScan = undefined;
|
|
373
|
+
for (const [absolutePath, snapshot] of missingEntries) {
|
|
374
|
+
try {
|
|
375
|
+
this.#emitRemoval(snapshot, absolutePath);
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
listenerErrors.push(error);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return { listenerErrors, readErrors: [...scan.readFailures.values()] };
|
|
382
|
+
}
|
|
383
|
+
#observedEntries() {
|
|
384
|
+
const scan = this.#redeployScan;
|
|
385
|
+
if (!scan)
|
|
386
|
+
return this.#entries;
|
|
387
|
+
// A generation can be replaced before `ready`, after some events have already reached consumers.
|
|
388
|
+
// Carry exactly that observed state forward: retain previously known paths that this partial scan
|
|
389
|
+
// has not reached, apply additions/changes it emitted, and remove paths whose unlink was emitted.
|
|
390
|
+
const observedEntries = new Map(scan.previousEntries);
|
|
391
|
+
for (const absolutePath of scan.removalsEmitted)
|
|
392
|
+
observedEntries.delete(absolutePath);
|
|
393
|
+
for (const [absolutePath, snapshot] of scan.currentEntries)
|
|
394
|
+
observedEntries.set(absolutePath, snapshot);
|
|
395
|
+
return observedEntries;
|
|
396
|
+
}
|
|
145
397
|
async #watch() {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
398
|
+
const install = this.#watchInstall ? this.#watchInstall.then(() => this.#installWatcher()) : this.#installWatcher();
|
|
399
|
+
const settled = install.then(() => {
|
|
400
|
+
if (this.#watchInstall === settled)
|
|
401
|
+
this.#watchInstall = undefined;
|
|
402
|
+
}, () => {
|
|
403
|
+
if (this.#watchInstall === settled)
|
|
404
|
+
this.#watchInstall = undefined;
|
|
405
|
+
});
|
|
406
|
+
this.#watchInstall = settled;
|
|
407
|
+
await install;
|
|
408
|
+
return this.ready;
|
|
409
|
+
}
|
|
410
|
+
async #installWatcher() {
|
|
149
411
|
if (this.#pausedClose) {
|
|
150
412
|
await this.#pausedClose;
|
|
151
413
|
this.#pausedClose = undefined;
|
|
152
414
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
415
|
+
const outgoing = this.#watcher;
|
|
416
|
+
if (outgoing) {
|
|
417
|
+
// Stays installed across the await so a close() landing in the gap still includes this
|
|
418
|
+
// teardown in its own settle set; only clear it if close() didn't already replace it.
|
|
419
|
+
try {
|
|
420
|
+
await outgoing.close();
|
|
421
|
+
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
this.#logger.warn?.(`Failed to close the previous watcher for ${this.name}; replacing it anyway:`, error);
|
|
424
|
+
}
|
|
425
|
+
finally {
|
|
426
|
+
this.#liveWatchers.delete(outgoing);
|
|
427
|
+
}
|
|
428
|
+
if (this.#watcher === outgoing)
|
|
429
|
+
this.#watcher = undefined;
|
|
430
|
+
}
|
|
157
431
|
if (this.#closed)
|
|
158
|
-
return
|
|
159
|
-
// pause() may have landed in the gap before our async close resolved.
|
|
160
|
-
// If so, do not install a replacement watcher — resume() will.
|
|
432
|
+
return;
|
|
161
433
|
if (this.#paused)
|
|
162
|
-
return
|
|
163
|
-
|
|
164
|
-
// initial scan emits add events anew, so reset the readiness latch so
|
|
165
|
-
// `ready` resolves after the new scan completes.
|
|
434
|
+
return;
|
|
435
|
+
const generation = ++this.#watchGeneration;
|
|
166
436
|
this.#isInitialScanComplete = false;
|
|
437
|
+
const previousEntries = this.#observedEntries();
|
|
438
|
+
this.#entries = previousEntries;
|
|
439
|
+
this.#redeployScan = {
|
|
440
|
+
generation,
|
|
441
|
+
previousEntries,
|
|
442
|
+
currentEntries: new Map(),
|
|
443
|
+
removalsEmitted: new Set(),
|
|
444
|
+
readFailures: new Map(),
|
|
445
|
+
};
|
|
167
446
|
const allowedBases = this.#component.patternBases.map((base) => (0, node_path_1.join)(this.#component.directory, base));
|
|
168
447
|
this.#openCount++;
|
|
169
|
-
this.#watcher = chokidar_1.default
|
|
448
|
+
const watcher = (this.#watcher = chokidar_1.default
|
|
170
449
|
.watch(this.#component.commonPatternBase, {
|
|
171
450
|
cwd: this.#component.directory,
|
|
172
451
|
persistent: false,
|
|
@@ -200,34 +479,43 @@ class EntryHandler extends node_events_1.EventEmitter {
|
|
|
200
479
|
return (normalizedPath !== normalizedDirectory && normalizedBases.every((base) => !normalizedPath.startsWith(base)));
|
|
201
480
|
},
|
|
202
481
|
})
|
|
203
|
-
.on('all', this.#handleAll
|
|
204
|
-
.on('error',
|
|
205
|
-
|
|
206
|
-
|
|
482
|
+
.on('all', (...args) => this.#handleAll(generation, ...args))
|
|
483
|
+
.on('error', (error) => {
|
|
484
|
+
if (generation === this.#watchGeneration)
|
|
485
|
+
this.#handleWatcherError(error);
|
|
486
|
+
})
|
|
487
|
+
.on('ready', () => this.#handleReady(generation)));
|
|
488
|
+
this.#liveWatchers.add(watcher);
|
|
207
489
|
}
|
|
208
|
-
// Test-only: simulate the underlying chokidar watcher emitting an error.
|
|
209
|
-
// Exposed so the polling-fallback path can be exercised without triggering a
|
|
210
|
-
// real ENOSPC/EMFILE on the host.
|
|
211
490
|
_simulateWatcherErrorForTests(error) {
|
|
212
|
-
this.#
|
|
491
|
+
this.#handleWatcherError(error);
|
|
492
|
+
}
|
|
493
|
+
_simulateFileReadForTests(event, path, contents) {
|
|
494
|
+
const absolutePath = (0, node_path_1.join)(this.directory, path);
|
|
495
|
+
return this.#queueFileRead(this.#watchGeneration, event, path, absolutePath, undefined, contents);
|
|
213
496
|
}
|
|
214
|
-
// Test-only: whether the watcher has fallen back to polling.
|
|
215
497
|
get _usingPollingForTests() {
|
|
216
498
|
return this.#usingPolling;
|
|
217
499
|
}
|
|
218
|
-
// Test-only: number of times the underlying watcher has been (re)opened.
|
|
219
|
-
// Used to assert that a close()-during-fallback race didn't install a
|
|
220
|
-
// replacement watcher.
|
|
221
500
|
get _openCountForTests() {
|
|
222
501
|
return this.#openCount;
|
|
223
502
|
}
|
|
503
|
+
get _liveWatcherCountForTests() {
|
|
504
|
+
return this.#liveWatchers.size;
|
|
505
|
+
}
|
|
224
506
|
close() {
|
|
507
|
+
this.#watchGeneration++;
|
|
225
508
|
this.#closed = true;
|
|
226
509
|
const pendingReads = [...this.#pendingFileReads];
|
|
227
|
-
const
|
|
510
|
+
const outgoing = this.#watcher;
|
|
511
|
+
const watcherClose = outgoing
|
|
512
|
+
? Promise.resolve(outgoing.close())
|
|
513
|
+
.catch(() => { })
|
|
514
|
+
.then(() => {
|
|
515
|
+
this.#liveWatchers.delete(outgoing);
|
|
516
|
+
})
|
|
517
|
+
: Promise.resolve();
|
|
228
518
|
this.#watcher = undefined;
|
|
229
|
-
// If paused, there may be an in-flight close from pause() that hasn't settled yet.
|
|
230
|
-
// Include it so close() doesn't resolve while inotify handles are still releasing.
|
|
231
519
|
const pausedClose = this.#pausedClose ?? Promise.resolve();
|
|
232
520
|
this.#pausedClose = undefined;
|
|
233
521
|
this.emit('close');
|
|
@@ -244,27 +532,25 @@ class EntryHandler extends node_events_1.EventEmitter {
|
|
|
244
532
|
* Idempotent. Awaiting `ready` while paused will not resolve until resume().
|
|
245
533
|
*/
|
|
246
534
|
pause() {
|
|
535
|
+
if (this.#paused || this.#closed)
|
|
536
|
+
return;
|
|
537
|
+
this.#watchGeneration++;
|
|
247
538
|
this.#paused = true;
|
|
248
|
-
|
|
249
|
-
// ready while paused will not resolve until resume()" contract holds even
|
|
250
|
-
// when the watcher had already become ready before pause(). The next
|
|
251
|
-
// 'ready' emit will come from the chokidar instance installed by resume().
|
|
252
|
-
this.ready = (0, node_events_1.once)(this, 'ready');
|
|
539
|
+
this.ready = this.#createReadyLatch();
|
|
253
540
|
if (this.#watcher) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
541
|
+
const outgoing = this.#watcher;
|
|
542
|
+
this.#pausedClose = Promise.resolve(outgoing.close())
|
|
543
|
+
.catch(() => { })
|
|
544
|
+
.then(() => {
|
|
545
|
+
this.#liveWatchers.delete(outgoing);
|
|
258
546
|
});
|
|
259
547
|
this.#watcher = undefined;
|
|
260
548
|
}
|
|
261
549
|
}
|
|
262
550
|
/**
|
|
263
551
|
* Reinstate the watcher previously stopped by pause(). The fresh chokidar
|
|
264
|
-
* instance
|
|
265
|
-
*
|
|
266
|
-
* caller (Scope, on deploy:end) wants plugins to see the post-deploy tree
|
|
267
|
-
* as if loading cold.
|
|
552
|
+
* instance scans the post-pause tree, compares it with the retained snapshot,
|
|
553
|
+
* and emits only the logical add, change, unlink, addDir, and unlinkDir events.
|
|
268
554
|
*
|
|
269
555
|
* No-op if not currently paused.
|
|
270
556
|
*/
|
|
@@ -272,12 +558,17 @@ class EntryHandler extends node_events_1.EventEmitter {
|
|
|
272
558
|
if (!this.#paused)
|
|
273
559
|
return this.ready;
|
|
274
560
|
this.#paused = false;
|
|
275
|
-
// `this.ready` was already reset to a pending promise in pause(); just
|
|
276
|
-
// trigger the watcher recreation and let its 'ready' emit resolve it.
|
|
277
561
|
return this.#watch();
|
|
278
562
|
}
|
|
279
563
|
update(config) {
|
|
564
|
+
if (this.#closed)
|
|
565
|
+
return this.ready;
|
|
566
|
+
this.#watchGeneration++;
|
|
280
567
|
this.#component = new Component_ts_1.Component(this.name, this.directory, castConfig(config));
|
|
568
|
+
// Re-arm the readiness latch for parity with pause(). Without this, awaiting update() on an
|
|
569
|
+
// already-ready handler resolves against the outgoing generation's 'ready' — before the
|
|
570
|
+
// replacement generation has scanned, digested, and emitted its logical events.
|
|
571
|
+
this.ready = this.#createReadyLatch();
|
|
281
572
|
return this.#watch();
|
|
282
573
|
}
|
|
283
574
|
}
|