@twin.org/node-core 0.0.2-next.24 → 0.0.2-next.26
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 +21 -7
- package/dist/cjs/index.cjs +389 -51
- package/dist/esm/index.mjs +385 -56
- package/dist/types/index.d.ts +4 -0
- package/dist/types/models/ICacheMetadata.d.ts +17 -0
- package/dist/types/models/IModuleProtocol.d.ts +18 -0
- package/dist/types/models/INodeEnvironmentVariables.d.ts +25 -0
- package/dist/types/models/IProtocolHandlerResult.d.ts +13 -0
- package/dist/types/models/moduleProtocol.d.ts +29 -0
- package/dist/types/node.d.ts +3 -2
- package/dist/types/utils.d.ts +67 -0
- package/docs/changelog.md +14 -0
- package/docs/detailed-guide.md +129 -0
- package/docs/reference/functions/createModuleImportUrl.md +21 -0
- package/docs/reference/functions/getExtensionsCacheDir.md +31 -0
- package/docs/reference/functions/handleHttpsProtocol.md +49 -0
- package/docs/reference/functions/handleNpmProtocol.md +31 -0
- package/docs/reference/functions/hashUrl.md +19 -0
- package/docs/reference/functions/isCacheExpired.md +31 -0
- package/docs/reference/functions/overrideModuleImport.md +8 -2
- package/docs/reference/functions/parseModuleProtocol.md +19 -0
- package/docs/reference/functions/resolvePackageEntryPoint.md +32 -0
- package/docs/reference/index.md +13 -0
- package/docs/reference/interfaces/ICacheMetadata.md +27 -0
- package/docs/reference/interfaces/IModuleProtocol.md +27 -0
- package/docs/reference/interfaces/INodeEnvironmentVariables.md +70 -0
- package/docs/reference/interfaces/IProtocolHandlerResult.md +19 -0
- package/docs/reference/type-aliases/ModuleProtocol.md +5 -0
- package/docs/reference/variables/ModuleProtocol.md +37 -0
- package/locales/en.json +11 -2
- package/package.json +7 -3
package/dist/esm/index.mjs
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { PasswordHelper } from '@twin.org/api-auth-entity-storage-service';
|
|
2
|
-
import { I18n, Is,
|
|
3
|
-
import { PasswordGenerator, Bip39 } from '@twin.org/crypto';
|
|
2
|
+
import { I18n, Is, Converter, GeneralError, BaseError, Coerce, RandomHelper, Urn, EnvHelper } from '@twin.org/core';
|
|
3
|
+
import { Sha256, PasswordGenerator, Bip39 } from '@twin.org/crypto';
|
|
4
4
|
import { AuthenticationComponentType, InformationComponentType, RestRouteProcessorType, SocketRouteProcessorType, AuthenticationAdminComponentType } from '@twin.org/engine-server-types';
|
|
5
5
|
import { WalletConnectorType, IdentityConnectorType, EntityStorageConnectorType, BlobStorageConnectorType, BlobStorageComponentType, VaultConnectorType, DltConfigType, LoggingConnectorType, LoggingComponentType, BackgroundTaskConnectorType, TaskSchedulerComponentType, EventBusConnectorType, EventBusComponentType, TelemetryConnectorType, TelemetryComponentType, MessagingEmailConnectorType, MessagingSmsConnectorType, MessagingPushNotificationConnectorType, MessagingAdminComponentType, MessagingComponentType, FaucetConnectorType, EngineTypeHelper, NftConnectorType, NftComponentType, VerifiableStorageConnectorType, VerifiableStorageComponentType, ImmutableProofComponentType, IdentityComponentType, IdentityResolverConnectorType, IdentityResolverComponentType, IdentityProfileConnectorType, IdentityProfileComponentType, AttestationConnectorType, AttestationComponentType, DataProcessingComponentType, DataConverterConnectorType, DataExtractorConnectorType, AuditableItemGraphComponentType, AuditableItemStreamComponentType, DocumentManagementComponentType, AuthenticationGeneratorComponentType, RightsManagementPapComponentType, RightsManagementPmpComponentType, RightsManagementPipComponentType, RightsManagementPxpComponentType, RightsManagementPdpComponentType, RightsManagementPepComponentType, RightsManagementPnpComponentType, RightsManagementPnapComponentType, RightsManagementDapComponentType, RightsManagementDarpComponentType, SynchronisedStorageComponentType, FederatedCatalogueComponentType, DataSpaceConnectorComponentType } from '@twin.org/engine-types';
|
|
6
6
|
import { EntityStorageConnectorFactory } from '@twin.org/entity-storage-models';
|
|
7
7
|
import { IdentityProfileConnectorFactory, IdentityConnectorFactory, IdentityResolverConnectorFactory, DocumentHelper } from '@twin.org/identity-models';
|
|
8
8
|
import { VaultConnectorFactory, VaultKeyType } from '@twin.org/vault-models';
|
|
9
9
|
import { WalletConnectorFactory } from '@twin.org/wallet-models';
|
|
10
|
-
import {
|
|
10
|
+
import { execSync } from 'node:child_process';
|
|
11
|
+
import { readFile, stat, readdir, mkdir, writeFile, rename } from 'node:fs/promises';
|
|
12
|
+
import { get } from 'node:https';
|
|
11
13
|
import path from 'node:path';
|
|
12
14
|
import { CLIDisplay } from '@twin.org/cli-core';
|
|
13
|
-
import { PolicyNegotiationPointClient, DataAccessPointClient } from '@twin.org/rights-management-rest-client';
|
|
14
|
-
import { addDefaultRestPaths, addDefaultSocketPaths, EngineServer } from '@twin.org/engine-server';
|
|
15
15
|
import { ModuleHelper } from '@twin.org/modules';
|
|
16
|
-
import {
|
|
16
|
+
import { PolicyNegotiationPointRestClient, DataAccessPointRestClient } from '@twin.org/rights-management-rest-client';
|
|
17
|
+
import { addDefaultRestPaths, addDefaultSocketPaths, EngineServer } from '@twin.org/engine-server';
|
|
17
18
|
import * as dotenv from 'dotenv';
|
|
18
19
|
import { Engine } from '@twin.org/engine';
|
|
19
20
|
import { FileStateStorage } from '@twin.org/engine-core';
|
|
@@ -49,6 +50,35 @@ const NodeFeatures = {
|
|
|
49
50
|
NodeWallet: "node-wallet"
|
|
50
51
|
};
|
|
51
52
|
|
|
53
|
+
// Copyright 2024 IOTA Stiftung.
|
|
54
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
55
|
+
/**
|
|
56
|
+
* The protocol types for modules.
|
|
57
|
+
*/
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
59
|
+
const ModuleProtocol = {
|
|
60
|
+
/**
|
|
61
|
+
* Local module (starts with . or / or file://).
|
|
62
|
+
*/
|
|
63
|
+
Local: "local",
|
|
64
|
+
/**
|
|
65
|
+
* NPM package (starts with npm:).
|
|
66
|
+
*/
|
|
67
|
+
Npm: "npm",
|
|
68
|
+
/**
|
|
69
|
+
* HTTPS URL (starts with https://).
|
|
70
|
+
*/
|
|
71
|
+
Https: "https",
|
|
72
|
+
/**
|
|
73
|
+
* HTTP URL (starts with http://).
|
|
74
|
+
*/
|
|
75
|
+
Http: "http",
|
|
76
|
+
/**
|
|
77
|
+
* Default/standard module resolution.
|
|
78
|
+
*/
|
|
79
|
+
Default: "default"
|
|
80
|
+
};
|
|
81
|
+
|
|
52
82
|
// Copyright 2024 IOTA Stiftung.
|
|
53
83
|
// SPDX-License-Identifier: Apache-2.0.
|
|
54
84
|
/**
|
|
@@ -182,6 +212,280 @@ function getFeatures(env) {
|
|
|
182
212
|
}
|
|
183
213
|
return features;
|
|
184
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Parse the protocol from a module name.
|
|
217
|
+
* @param moduleName The module name to parse.
|
|
218
|
+
* @returns The parsed protocol information.
|
|
219
|
+
*/
|
|
220
|
+
function parseModuleProtocol(moduleName) {
|
|
221
|
+
const trimmed = moduleName.trim();
|
|
222
|
+
if (trimmed.startsWith("npm:")) {
|
|
223
|
+
return {
|
|
224
|
+
protocol: ModuleProtocol.Npm,
|
|
225
|
+
identifier: trimmed.slice(4),
|
|
226
|
+
original: trimmed
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
if (trimmed.startsWith("https://")) {
|
|
230
|
+
return {
|
|
231
|
+
protocol: ModuleProtocol.Https,
|
|
232
|
+
identifier: trimmed,
|
|
233
|
+
original: trimmed
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
if (trimmed.startsWith("http://")) {
|
|
237
|
+
return {
|
|
238
|
+
protocol: ModuleProtocol.Http,
|
|
239
|
+
identifier: trimmed,
|
|
240
|
+
original: trimmed
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
if (trimmed.startsWith("file://")) {
|
|
244
|
+
return {
|
|
245
|
+
protocol: ModuleProtocol.Local,
|
|
246
|
+
identifier: trimmed,
|
|
247
|
+
original: trimmed
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
if (ModuleHelper.isLocalModule(trimmed)) {
|
|
251
|
+
return {
|
|
252
|
+
protocol: ModuleProtocol.Local,
|
|
253
|
+
identifier: trimmed,
|
|
254
|
+
original: trimmed
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
protocol: ModuleProtocol.Default,
|
|
259
|
+
identifier: trimmed,
|
|
260
|
+
original: trimmed
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Hash a URL to create a safe filename.
|
|
265
|
+
* @param url The URL to hash.
|
|
266
|
+
* @returns A hashed filename safe for the filesystem.
|
|
267
|
+
*/
|
|
268
|
+
function hashUrl(url) {
|
|
269
|
+
const urlBytes = Converter.utf8ToBytes(url);
|
|
270
|
+
const hashBytes = Sha256.sum256(urlBytes);
|
|
271
|
+
const hash = Converter.bytesToHex(hashBytes);
|
|
272
|
+
const ext = path.extname(new URL(url).pathname);
|
|
273
|
+
return `${hash}${ext}`;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Get the extensions cache directory.
|
|
277
|
+
* @param executionDirectory The execution directory.
|
|
278
|
+
* @param protocol The protocol type for subdirectory organization.
|
|
279
|
+
* @param cacheDirectory The cache directory base path.
|
|
280
|
+
* @returns The cache directory path.
|
|
281
|
+
*/
|
|
282
|
+
function getExtensionsCacheDir(executionDirectory, protocol, cacheDirectory) {
|
|
283
|
+
// Resolve to absolute path to ensure consistent behavior
|
|
284
|
+
const absoluteDir = path.resolve(executionDirectory);
|
|
285
|
+
const baseDir = cacheDirectory ?? ".tmp";
|
|
286
|
+
return path.join(absoluteDir, baseDir, "extensions", protocol);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Handle the npm: protocol by installing the package if needed.
|
|
290
|
+
* @param packageName The npm package name (without npm: prefix).
|
|
291
|
+
* @param executionDirectory The execution directory.
|
|
292
|
+
* @param cacheDirectory The cache directory base path.
|
|
293
|
+
* @returns The resolved path to the installed module.
|
|
294
|
+
*/
|
|
295
|
+
async function handleNpmProtocol(packageName, executionDirectory, cacheDirectory) {
|
|
296
|
+
const cacheDir = getExtensionsCacheDir(executionDirectory, ModuleProtocol.Npm, cacheDirectory);
|
|
297
|
+
// Extract just the package name (without version) for the directory
|
|
298
|
+
// e.g. "picocolors@1.0.0" becomes "picocolors"
|
|
299
|
+
// e.g. "@scope/package@1.0.0" becomes "@scope/package"
|
|
300
|
+
const lastAtIndex = packageName.lastIndexOf("@");
|
|
301
|
+
const packageNameOnly = lastAtIndex > 0 ? packageName.slice(0, lastAtIndex) : packageName;
|
|
302
|
+
const packageDir = path.join(cacheDir, "node_modules", packageNameOnly);
|
|
303
|
+
const packageJsonPath = path.join(packageDir, "package.json");
|
|
304
|
+
const exists = await fileExists(packageJsonPath);
|
|
305
|
+
if (exists) {
|
|
306
|
+
const mainFile = await resolvePackageEntryPoint(packageDir, packageNameOnly);
|
|
307
|
+
const modulePath = path.join(packageDir, mainFile);
|
|
308
|
+
return {
|
|
309
|
+
resolvedPath: modulePath,
|
|
310
|
+
cached: true
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
await mkdir(cacheDir, { recursive: true });
|
|
314
|
+
CLIDisplay.task(I18n.formatMessage("node.extensionNpmInstalling"), packageName);
|
|
315
|
+
try {
|
|
316
|
+
// Always pipe stdio to comply with env access restrictions in tests/lint
|
|
317
|
+
const stdio = "pipe";
|
|
318
|
+
execSync(`npm install ${packageName} --prefix "${cacheDir}" --no-save --no-package-lock`, {
|
|
319
|
+
cwd: cacheDir,
|
|
320
|
+
stdio
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
catch (err) {
|
|
324
|
+
throw new GeneralError("node", "extensionNpmInstallFailed", {
|
|
325
|
+
package: packageName
|
|
326
|
+
}, BaseError.fromError(err));
|
|
327
|
+
}
|
|
328
|
+
const mainFile = await resolvePackageEntryPoint(packageDir, packageNameOnly);
|
|
329
|
+
const modulePath = path.join(packageDir, mainFile);
|
|
330
|
+
return {
|
|
331
|
+
resolvedPath: modulePath,
|
|
332
|
+
cached: false
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Check if a cached file has expired based on TTL and force refresh settings.
|
|
337
|
+
* @param metadataPath Path to the cache metadata file.
|
|
338
|
+
* @param ttlHours Time to live in hours.
|
|
339
|
+
* @param forceRefresh Whether to force refresh regardless of TTL.
|
|
340
|
+
* @returns True if the cache is expired or should be refreshed.
|
|
341
|
+
*/
|
|
342
|
+
async function isCacheExpired(metadataPath, ttlHours, forceRefresh) {
|
|
343
|
+
if (forceRefresh) {
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
try {
|
|
347
|
+
const metadata = await loadJsonFile(metadataPath);
|
|
348
|
+
const ttlMillis = ttlHours * 60 * 60 * 1000;
|
|
349
|
+
const expireTime = metadata.downloadedAt + ttlMillis;
|
|
350
|
+
return Date.now() > expireTime;
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
// If metadata doesn't exist or is corrupted, consider expired
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Handle the https: protocol by downloading the module if needed.
|
|
359
|
+
* @param url The HTTPS URL to download from.
|
|
360
|
+
* @param executionDirectory The execution directory.
|
|
361
|
+
* @param maxSizeMb The maximum size in MB for the download.
|
|
362
|
+
* @param cacheDirectory The cache directory base path.
|
|
363
|
+
* @param ttlHours TTL in hours for cache expiration.
|
|
364
|
+
* @param forceRefresh Whether to force refresh the cache.
|
|
365
|
+
* @returns The resolved path to the downloaded module.
|
|
366
|
+
*/
|
|
367
|
+
async function handleHttpsProtocol(url, executionDirectory, maxSizeMb, cacheDirectory, ttlHours, forceRefresh) {
|
|
368
|
+
const effectiveTtlHours = ttlHours ?? 24;
|
|
369
|
+
const effectiveForceRefresh = forceRefresh ?? false;
|
|
370
|
+
const cacheDir = getExtensionsCacheDir(executionDirectory, ModuleProtocol.Https, cacheDirectory);
|
|
371
|
+
const filename = hashUrl(url);
|
|
372
|
+
const cachedPath = path.join(cacheDir, filename);
|
|
373
|
+
const metadataPath = `${cachedPath}.meta`;
|
|
374
|
+
const exists = await fileExists(cachedPath);
|
|
375
|
+
if (exists) {
|
|
376
|
+
const expired = await isCacheExpired(metadataPath, effectiveTtlHours, effectiveForceRefresh);
|
|
377
|
+
if (!expired) {
|
|
378
|
+
return {
|
|
379
|
+
resolvedPath: cachedPath,
|
|
380
|
+
cached: true
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
if (effectiveForceRefresh) {
|
|
384
|
+
CLIDisplay.warning(I18n.formatMessage("node.extensionForceRefresh", { url }));
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
CLIDisplay.task(I18n.formatMessage("node.extensionCacheExpired", { url }));
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
CLIDisplay.warning(I18n.formatMessage("node.extensionSecurityWarning", { url }));
|
|
391
|
+
CLIDisplay.task(I18n.formatMessage("node.extensionHttpsDownloading"), url);
|
|
392
|
+
await mkdir(cacheDir, { recursive: true });
|
|
393
|
+
const maxSizeBytes = maxSizeMb * 1024 * 1024;
|
|
394
|
+
let downloadedSize = 0;
|
|
395
|
+
const chunks = [];
|
|
396
|
+
try {
|
|
397
|
+
await new Promise((resolve, reject) => {
|
|
398
|
+
get(url, response => {
|
|
399
|
+
if (response.statusCode !== 200) {
|
|
400
|
+
reject(new GeneralError("node", "extensionDownloadFailed", {
|
|
401
|
+
url,
|
|
402
|
+
status: response.statusCode ?? 0
|
|
403
|
+
}));
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
response.on("data", (chunk) => {
|
|
407
|
+
downloadedSize += chunk.length;
|
|
408
|
+
if (downloadedSize > maxSizeBytes) {
|
|
409
|
+
response.destroy();
|
|
410
|
+
reject(new GeneralError("node", "extensionSizeLimitExceeded", {
|
|
411
|
+
size: downloadedSize,
|
|
412
|
+
limit: maxSizeBytes
|
|
413
|
+
}));
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
chunks.push(chunk);
|
|
417
|
+
});
|
|
418
|
+
response.on("end", () => {
|
|
419
|
+
resolve();
|
|
420
|
+
});
|
|
421
|
+
response.on("error", err => {
|
|
422
|
+
reject(BaseError.fromError(err));
|
|
423
|
+
});
|
|
424
|
+
}).on("error", err => {
|
|
425
|
+
reject(BaseError.fromError(err));
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
catch (err) {
|
|
430
|
+
throw new GeneralError("node", "extensionDownloadFailed", {
|
|
431
|
+
url
|
|
432
|
+
}, BaseError.fromError(err));
|
|
433
|
+
}
|
|
434
|
+
const tempPath = `${cachedPath}.tmp`;
|
|
435
|
+
await writeFile(tempPath, Buffer.concat(chunks));
|
|
436
|
+
// Atomic move from temp to final location
|
|
437
|
+
await rename(tempPath, cachedPath);
|
|
438
|
+
// Save metadata for TTL tracking
|
|
439
|
+
const metadata = {
|
|
440
|
+
downloadedAt: Date.now(),
|
|
441
|
+
url,
|
|
442
|
+
size: Buffer.concat(chunks).length
|
|
443
|
+
};
|
|
444
|
+
await writeFile(metadataPath, JSON.stringify(metadata, null, 2));
|
|
445
|
+
return {
|
|
446
|
+
resolvedPath: cachedPath,
|
|
447
|
+
cached: false
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Resolve the main entry point from a package directory using Node.js resolution with fallback.
|
|
452
|
+
* Uses require.resolve() when possible for standard Node.js behavior, with manual fallback.
|
|
453
|
+
* @param packagePath The absolute path to the package directory.
|
|
454
|
+
* @param packageName The package name for require.resolve().
|
|
455
|
+
* @param fallback The fallback file name if no entry point is found.
|
|
456
|
+
* @returns The resolved entry point file name (relative to package directory).
|
|
457
|
+
*/
|
|
458
|
+
async function resolvePackageEntryPoint(packagePath, packageName, fallback = "index.js") {
|
|
459
|
+
try {
|
|
460
|
+
// Try require.resolve() first - handles exports, main, module automatically
|
|
461
|
+
const resolvedPath = require.resolve(packageName, { paths: [path.dirname(packagePath)] });
|
|
462
|
+
// Convert absolute path back to relative filename within package
|
|
463
|
+
const relativePath = path.relative(packagePath, resolvedPath);
|
|
464
|
+
return relativePath ?? fallback;
|
|
465
|
+
}
|
|
466
|
+
catch {
|
|
467
|
+
// Fallback to manual package.json parsing if require.resolve fails
|
|
468
|
+
try {
|
|
469
|
+
const packageJsonPath = path.join(packagePath, "package.json");
|
|
470
|
+
const packageJsonContent = await loadJsonFile(packageJsonPath);
|
|
471
|
+
// Future: Could expand exports field support here
|
|
472
|
+
return packageJsonContent.module ?? packageJsonContent.main ?? fallback;
|
|
473
|
+
}
|
|
474
|
+
catch {
|
|
475
|
+
return fallback;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Convert a file path to an import-compatible URL for cross-platform module loading.
|
|
481
|
+
* On Windows, adds the 'file://' protocol prefix required for dynamic imports.
|
|
482
|
+
* On other platforms, returns the path unchanged.
|
|
483
|
+
* @param filePath The absolute file path to convert.
|
|
484
|
+
* @returns A URL string compatible with dynamic import().
|
|
485
|
+
*/
|
|
486
|
+
function createModuleImportUrl(filePath) {
|
|
487
|
+
return process.platform === "win32" ? `file://${filePath}` : filePath;
|
|
488
|
+
}
|
|
185
489
|
|
|
186
490
|
// Copyright 2024 IOTA Stiftung.
|
|
187
491
|
// SPDX-License-Identifier: Apache-2.0.
|
|
@@ -1494,7 +1798,7 @@ async function configureRightsManagement(coreConfig, envVars) {
|
|
|
1494
1798
|
offers: Is.arrayValue(envVars.rightsManagementOffers)
|
|
1495
1799
|
? envVars.rightsManagementOffers
|
|
1496
1800
|
: [],
|
|
1497
|
-
negotiationComponentCreator: async (url) => new
|
|
1801
|
+
negotiationComponentCreator: async (url) => new PolicyNegotiationPointRestClient({ endpoint: url })
|
|
1498
1802
|
}
|
|
1499
1803
|
}
|
|
1500
1804
|
});
|
|
@@ -1511,7 +1815,7 @@ async function configureRightsManagement(coreConfig, envVars) {
|
|
|
1511
1815
|
type: RightsManagementDarpComponentType.Service,
|
|
1512
1816
|
options: {
|
|
1513
1817
|
config: {
|
|
1514
|
-
dataAccessComponentCreator: async (url) => new
|
|
1818
|
+
dataAccessComponentCreator: async (url) => new DataAccessPointRestClient({ endpoint: url })
|
|
1515
1819
|
}
|
|
1516
1820
|
}
|
|
1517
1821
|
});
|
|
@@ -1973,7 +2277,7 @@ async function run(nodeOptions) {
|
|
|
1973
2277
|
nodeOptions ??= {};
|
|
1974
2278
|
const serverInfo = {
|
|
1975
2279
|
name: nodeOptions?.serverName ?? "TWIN Node Server",
|
|
1976
|
-
version: nodeOptions?.serverVersion ?? "0.0.2-next.
|
|
2280
|
+
version: nodeOptions?.serverVersion ?? "0.0.2-next.26" // x-release-please-version
|
|
1977
2281
|
};
|
|
1978
2282
|
CLIDisplay.header(serverInfo.name, serverInfo.version, "🌩️ ");
|
|
1979
2283
|
if (!Is.stringValue(nodeOptions?.executionDirectory)) {
|
|
@@ -2111,10 +2415,13 @@ async function buildConfiguration(processEnv, options, serverInfo) {
|
|
|
2111
2415
|
return { nodeEngineConfig, nodeEnvVars: envVars };
|
|
2112
2416
|
}
|
|
2113
2417
|
/**
|
|
2114
|
-
* Override module imports to
|
|
2418
|
+
* Override module imports to support protocol-based loading (npm:, https:) and local files.
|
|
2115
2419
|
* @param executionDirectory The execution directory for resolving local module paths.
|
|
2420
|
+
* @param envVars The environment variables containing extension configuration (optional, uses defaults if not provided).
|
|
2116
2421
|
*/
|
|
2117
|
-
function overrideModuleImport(executionDirectory) {
|
|
2422
|
+
function overrideModuleImport(executionDirectory, envVars) {
|
|
2423
|
+
const maxSizeMb = Coerce.number(envVars?.extensionsMaxSizeMb) ?? 10;
|
|
2424
|
+
const cacheDirectory = envVars?.extensionsCacheDirectory;
|
|
2118
2425
|
ModuleHelper.overrideImport(async (moduleName) => {
|
|
2119
2426
|
if (moduleCache[moduleName]) {
|
|
2120
2427
|
return {
|
|
@@ -2122,58 +2429,80 @@ function overrideModuleImport(executionDirectory) {
|
|
|
2122
2429
|
useDefault: false
|
|
2123
2430
|
};
|
|
2124
2431
|
}
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
let exists = await fileExists(localFilename);
|
|
2133
|
-
if (!exists) {
|
|
2134
|
-
// Doesn't exist in the current directory, try the execution directory
|
|
2135
|
-
localFilename = path.resolve(executionDirectory, moduleName);
|
|
2136
|
-
exists = await fileExists(localFilename);
|
|
2137
|
-
}
|
|
2138
|
-
if (exists) {
|
|
2139
|
-
// If the module exists then we can load it, otherwise
|
|
2140
|
-
// we fallback to regular handling to see if that can import it
|
|
2141
|
-
const module = await import(process.platform === "win32" ? `file://${localFilename}` : localFilename);
|
|
2142
|
-
moduleCache[moduleName] = module;
|
|
2143
|
-
return {
|
|
2144
|
-
module,
|
|
2145
|
-
useDefault: false
|
|
2146
|
-
};
|
|
2432
|
+
const parsed = parseModuleProtocol(moduleName);
|
|
2433
|
+
let resolvedPath;
|
|
2434
|
+
switch (parsed.protocol) {
|
|
2435
|
+
case ModuleProtocol.Npm: {
|
|
2436
|
+
const result = await handleNpmProtocol(parsed.identifier, executionDirectory, cacheDirectory);
|
|
2437
|
+
resolvedPath = result.resolvedPath;
|
|
2438
|
+
break;
|
|
2147
2439
|
}
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2440
|
+
case ModuleProtocol.Https: {
|
|
2441
|
+
const result = await handleHttpsProtocol(parsed.identifier, executionDirectory, maxSizeMb, cacheDirectory, envVars?.extensionsCacheTtlHours, envVars?.extensionsForceRefresh);
|
|
2442
|
+
resolvedPath = result.resolvedPath;
|
|
2443
|
+
break;
|
|
2444
|
+
}
|
|
2445
|
+
case ModuleProtocol.Http: {
|
|
2446
|
+
throw new GeneralError("node", "insecureProtocol", { protocol: ModuleProtocol.Http });
|
|
2447
|
+
}
|
|
2448
|
+
case ModuleProtocol.Local: {
|
|
2449
|
+
let localFilename = path.resolve(moduleName);
|
|
2450
|
+
let exists = await fileExists(localFilename);
|
|
2451
|
+
if (!exists) {
|
|
2452
|
+
localFilename = path.resolve(executionDirectory, moduleName);
|
|
2453
|
+
exists = await fileExists(localFilename);
|
|
2454
|
+
}
|
|
2455
|
+
if (exists) {
|
|
2456
|
+
resolvedPath = localFilename;
|
|
2457
|
+
}
|
|
2458
|
+
break;
|
|
2459
|
+
}
|
|
2460
|
+
case ModuleProtocol.Default: {
|
|
2461
|
+
try {
|
|
2462
|
+
const npmRoot = execSync("npm root").toString().trim().replace(/\\/g, "/");
|
|
2463
|
+
const packagePath = path.resolve(npmRoot, moduleName);
|
|
2464
|
+
const mainFile = await resolvePackageEntryPoint(packagePath, moduleName);
|
|
2465
|
+
const modulePath = path.resolve(packagePath, mainFile);
|
|
2466
|
+
const exists = await fileExists(modulePath);
|
|
2467
|
+
if (exists) {
|
|
2468
|
+
resolvedPath = modulePath;
|
|
2469
|
+
break;
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
catch {
|
|
2473
|
+
// Continue to fallback resolution
|
|
2474
|
+
}
|
|
2475
|
+
// Fallback: resolve from npm protocol cache directory (installed via handleNpmProtocol)
|
|
2476
|
+
try {
|
|
2477
|
+
const cacheNpmRoot = path.resolve(getExtensionsCacheDir(executionDirectory, ModuleProtocol.Npm, cacheDirectory), "node_modules");
|
|
2478
|
+
const packagePath = path.resolve(cacheNpmRoot, moduleName);
|
|
2479
|
+
const mainFile = await resolvePackageEntryPoint(packagePath, moduleName);
|
|
2480
|
+
const modulePath = path.resolve(packagePath, mainFile);
|
|
2481
|
+
const exists = await fileExists(modulePath);
|
|
2482
|
+
if (exists) {
|
|
2483
|
+
resolvedPath = modulePath;
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
catch {
|
|
2487
|
+
// No cached resolution either; fall through
|
|
2488
|
+
}
|
|
2489
|
+
break;
|
|
2165
2490
|
}
|
|
2166
2491
|
}
|
|
2167
|
-
|
|
2168
|
-
|
|
2492
|
+
// Common module loading and caching logic
|
|
2493
|
+
if (resolvedPath) {
|
|
2494
|
+
const module = await import(createModuleImportUrl(resolvedPath));
|
|
2495
|
+
moduleCache[moduleName] = module;
|
|
2496
|
+
return {
|
|
2497
|
+
module,
|
|
2498
|
+
useDefault: false
|
|
2499
|
+
};
|
|
2169
2500
|
}
|
|
2170
|
-
// We don't appear to be able to manually resolve this module
|
|
2171
|
-
// So we let the default handling take care of it
|
|
2172
|
-
// This will allow built-in modules and regular node_modules to load as normal
|
|
2173
2501
|
return {
|
|
2502
|
+
module: undefined,
|
|
2174
2503
|
useDefault: true
|
|
2175
2504
|
};
|
|
2176
2505
|
});
|
|
2177
2506
|
}
|
|
2178
2507
|
|
|
2179
|
-
export { ATTESTATION_VERIFICATION_METHOD_ID, AUTH_SIGNING_KEY_ID, BLOB_STORAGE_ENCRYPTION_KEY_ID, IMMUTABLE_PROOF_VERIFICATION_METHOD_ID, NodeFeatures, SYNCHRONISED_STORAGE_BLOB_STORAGE_ENCRYPTION_KEY_ID, VC_AUTHENTICATION_VERIFICATION_METHOD_ID, bootstrap, bootstrapAuth, bootstrapBlobEncryption, bootstrapImmutableProofMethod, bootstrapNodeIdentity, bootstrapNodeUser, bootstrapSynchronisedStorage, buildConfiguration, buildEngineConfiguration, buildEngineServerConfiguration, directoryExists, extensionsConfiguration, extensionsInitialiseEngine, extensionsInitialiseEngineServer, fileExists, getExecutionDirectory, getFeatures, getFiles, getSubFolders, initialiseLocales, loadJsonFile, loadTextFile, overrideModuleImport, run, shutdownExtensions, start };
|
|
2508
|
+
export { ATTESTATION_VERIFICATION_METHOD_ID, AUTH_SIGNING_KEY_ID, BLOB_STORAGE_ENCRYPTION_KEY_ID, IMMUTABLE_PROOF_VERIFICATION_METHOD_ID, ModuleProtocol, NodeFeatures, SYNCHRONISED_STORAGE_BLOB_STORAGE_ENCRYPTION_KEY_ID, VC_AUTHENTICATION_VERIFICATION_METHOD_ID, bootstrap, bootstrapAuth, bootstrapBlobEncryption, bootstrapImmutableProofMethod, bootstrapNodeIdentity, bootstrapNodeUser, bootstrapSynchronisedStorage, buildConfiguration, buildEngineConfiguration, buildEngineServerConfiguration, createModuleImportUrl, directoryExists, extensionsConfiguration, extensionsInitialiseEngine, extensionsInitialiseEngineServer, fileExists, getExecutionDirectory, getExtensionsCacheDir, getFeatures, getFiles, getSubFolders, handleHttpsProtocol, handleNpmProtocol, hashUrl, initialiseLocales, isCacheExpired, loadJsonFile, loadTextFile, overrideModuleImport, parseModuleProtocol, resolvePackageEntryPoint, run, shutdownExtensions, start };
|
package/dist/types/index.d.ts
CHANGED
|
@@ -3,11 +3,15 @@ export * from "./builders/engineEnvBuilder";
|
|
|
3
3
|
export * from "./builders/engineServerEnvBuilder";
|
|
4
4
|
export * from "./builders/extensionsBuilder";
|
|
5
5
|
export * from "./defaults";
|
|
6
|
+
export * from "./models/ICacheMetadata";
|
|
6
7
|
export * from "./models/IEngineEnvironmentVariables";
|
|
7
8
|
export * from "./models/IEngineServerEnvironmentVariables";
|
|
9
|
+
export * from "./models/IModuleProtocol";
|
|
8
10
|
export * from "./models/INodeEngineConfig";
|
|
9
11
|
export * from "./models/INodeEnvironmentVariables";
|
|
10
12
|
export * from "./models/INodeOptions";
|
|
13
|
+
export * from "./models/IProtocolHandlerResult";
|
|
14
|
+
export * from "./models/moduleProtocol";
|
|
11
15
|
export * from "./models/nodeExtensionMethods";
|
|
12
16
|
export * from "./models/nodeFeatures";
|
|
13
17
|
export * from "./node";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata for cached HTTPS extensions.
|
|
3
|
+
*/
|
|
4
|
+
export interface ICacheMetadata {
|
|
5
|
+
/**
|
|
6
|
+
* Timestamp when the file was downloaded.
|
|
7
|
+
*/
|
|
8
|
+
downloadedAt: number;
|
|
9
|
+
/**
|
|
10
|
+
* Original URL of the cached file.
|
|
11
|
+
*/
|
|
12
|
+
url: string;
|
|
13
|
+
/**
|
|
14
|
+
* Size of the cached file in bytes.
|
|
15
|
+
*/
|
|
16
|
+
size: number;
|
|
17
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ModuleProtocol } from "./moduleProtocol";
|
|
2
|
+
/**
|
|
3
|
+
* The parsed module protocol information.
|
|
4
|
+
*/
|
|
5
|
+
export interface IModuleProtocol {
|
|
6
|
+
/**
|
|
7
|
+
* The protocol type.
|
|
8
|
+
*/
|
|
9
|
+
protocol: ModuleProtocol;
|
|
10
|
+
/**
|
|
11
|
+
* The identifier after the protocol (or the original if no protocol).
|
|
12
|
+
*/
|
|
13
|
+
identifier: string;
|
|
14
|
+
/**
|
|
15
|
+
* The original module string.
|
|
16
|
+
*/
|
|
17
|
+
original: string;
|
|
18
|
+
}
|
|
@@ -25,4 +25,29 @@ export interface INodeEnvironmentVariables extends IEngineServerEnvironmentVaria
|
|
|
25
25
|
* If the node-user feature is enabled, this will be the password of the user, if empty it will be randomly generated.
|
|
26
26
|
*/
|
|
27
27
|
password?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Maximum size in MB for HTTPS extensions downloads.
|
|
30
|
+
* @default 10
|
|
31
|
+
*/
|
|
32
|
+
extensionsMaxSizeMb?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Whether to clear the extensions cache on startup.
|
|
35
|
+
* @default false
|
|
36
|
+
*/
|
|
37
|
+
extensionsClearCache?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Custom directory for extensions cache storage.
|
|
40
|
+
* @default ".tmp"
|
|
41
|
+
*/
|
|
42
|
+
extensionsCacheDirectory?: string;
|
|
43
|
+
/**
|
|
44
|
+
* TTL in hours for HTTPS extensions cache.
|
|
45
|
+
* @default 24
|
|
46
|
+
*/
|
|
47
|
+
extensionsCacheTtlHours?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Force refresh of all cached extensions.
|
|
50
|
+
* @default false
|
|
51
|
+
*/
|
|
52
|
+
extensionsForceRefresh?: boolean;
|
|
28
53
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The protocol types for modules.
|
|
3
|
+
*/
|
|
4
|
+
export declare const ModuleProtocol: {
|
|
5
|
+
/**
|
|
6
|
+
* Local module (starts with . or / or file://).
|
|
7
|
+
*/
|
|
8
|
+
readonly Local: "local";
|
|
9
|
+
/**
|
|
10
|
+
* NPM package (starts with npm:).
|
|
11
|
+
*/
|
|
12
|
+
readonly Npm: "npm";
|
|
13
|
+
/**
|
|
14
|
+
* HTTPS URL (starts with https://).
|
|
15
|
+
*/
|
|
16
|
+
readonly Https: "https";
|
|
17
|
+
/**
|
|
18
|
+
* HTTP URL (starts with http://).
|
|
19
|
+
*/
|
|
20
|
+
readonly Http: "http";
|
|
21
|
+
/**
|
|
22
|
+
* Default/standard module resolution.
|
|
23
|
+
*/
|
|
24
|
+
readonly Default: "default";
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The protocol type for a module.
|
|
28
|
+
*/
|
|
29
|
+
export type ModuleProtocol = (typeof ModuleProtocol)[keyof typeof ModuleProtocol];
|
package/dist/types/node.d.ts
CHANGED
|
@@ -25,7 +25,8 @@ export declare function buildConfiguration(processEnv: {
|
|
|
25
25
|
nodeEngineConfig: INodeEngineConfig;
|
|
26
26
|
}>;
|
|
27
27
|
/**
|
|
28
|
-
* Override module imports to
|
|
28
|
+
* Override module imports to support protocol-based loading (npm:, https:) and local files.
|
|
29
29
|
* @param executionDirectory The execution directory for resolving local module paths.
|
|
30
|
+
* @param envVars The environment variables containing extension configuration (optional, uses defaults if not provided).
|
|
30
31
|
*/
|
|
31
|
-
export declare function overrideModuleImport(executionDirectory: string): void;
|
|
32
|
+
export declare function overrideModuleImport(executionDirectory: string, envVars?: INodeEnvironmentVariables): void;
|