jitar 0.7.2 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{A as AccessLevels,H as Version,j as RemoteRepository,k as RemoteGateway,L as LocalWorker}from"./globals-
|
|
1
|
+
import{A as AccessLevels,H as Version,j as RemoteRepository,k as RemoteGateway,L as LocalWorker}from"./globals-B8DxiuDw.js";export{O as ArrayParameter,B as BadRequest,C as ClientNotFound,F as FileNotFound,v as Forbidden,Y as ImplementationNotFound,b as InvalidClientId,Z as InvalidParameterValue,a as InvalidSegmentFile,_ as InvalidVersionNumber,$ as MissingParameterValue,a0 as ModuleNotAccessible,a1 as ModuleNotLoaded,K as NamedParameter,N as NoWorkerAvailable,w as NotFound,x as NotImplemented,Q as ObjectParameter,u as PaymentRequired,a2 as ProcedureNotAccessible,P as ProcedureNotFound,a3 as RepositoryNotAvailable,y as Request,J as Response,a4 as RuntimeNotAvailable,a5 as SegmentNotFound,X as ServerError,T as Teapot,U as Unauthorized,a6 as UnknownParameter}from"./globals-B8DxiuDw.js";class Implementation{#version;#access;#parameters;#executable;constructor(version,access,parameters,executable){this.#version=version,this.#access=access,this.#parameters=parameters,this.#executable=executable}get version(){return this.#version}get public(){return this.#access===AccessLevels.PUBLIC}get protected(){return this.#access===AccessLevels.PROTECTED}get parameters(){return this.#parameters}get executable(){return this.#executable}}class Procedure{#fqn;#implementations=new Map;#latestImplementation;constructor(fqn){this.#fqn=fqn}get fqn(){return this.#fqn}get public(){return[...this.#implementations.values()].some((implementation=>implementation.public))}get protected(){if(this.public)return!1;return[...this.#implementations.values()].some((implementation=>implementation.protected))}addImplementation(implementation){return this.#implementations.set(implementation.version,implementation),this.#isNewLatestImplementation(implementation)&&(this.#latestImplementation=implementation),this}#isNewLatestImplementation(implementation){return void 0===this.#latestImplementation||implementation.version.greater(this.#latestImplementation.version)}getImplementation(version){const selectedVersion=this.#selectAvailableVersion(version);return this.#implementations.get(selectedVersion)}#selectAvailableVersion(version){let selectedVersion=Version.DEFAULT;for(const implementationVersion of this.#implementations.keys()){if(implementationVersion.equals(version))return implementationVersion;implementationVersion.greater(version)||selectedVersion.less(implementationVersion)&&(selectedVersion=implementationVersion)}return selectedVersion}}class Segment{#id;#procedures=new Map;constructor(id){this.#id=id}get id(){return this.#id}addProcedure(procedure){return this.#procedures.set(procedure.fqn,procedure),this}hasProcedure(fqn){return void 0!==this.getProcedure(fqn)}getProcedure(fqn){return this.#procedures.get(fqn)}getExposedProcedures(){return[...this.#procedures.values()].filter((procedure=>procedure.public||procedure.protected))}}let client;const resolvers=[];async function startClient(remoteUrl,segmentNames=[],middlewares=[]){const repository=new RemoteRepository(remoteUrl),gateway=new RemoteGateway(remoteUrl),worker=new LocalWorker(repository,gateway);return worker.segmentNames=new Set(segmentNames),worker.middlewareFiles=new Set(middlewares),await worker.start(),client=worker,resolvers.forEach((resolve=>resolve(worker))),resolvers.length=0,worker}async function getClient(){return void 0===client?new Promise((resolve=>{resolvers.push(resolve)})):client}export{Implementation,Procedure,Segment,Version,getClient,startClient};
|
package/dist/server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{N as NoWorkerAvailable,G as Gateway,I as InvalidTrustKey,P as ProcedureNotFound,R as Repository,M as ModuleLoader,a as InvalidSegmentFile,F as FileNotFound,b as InvalidClientId,C as ClientNotFound,c as File,d as convertToRemoteFilename,i as isSegmentFilename,e as convertToLocalFilename,f as createRepositoryFilename,g as ProcedureRuntime,W as Worker,D as DummyRepository,h as Remote,j as RemoteRepository,k as RemoteGateway,l as RuntimeNotBuilt,L as LocalWorker,m as Reflector,A as AccessLevels,n as ReflectionField,o as ReflectionDestructuredArray,p as ReflectionDestructuredObject,q as ReflectionDestructuredValue,r as createWorkerFilename,V as VersionParser,s as ReflectionFunction,t as Files,B as BadRequest,U as Unauthorized,u as PaymentRequired,v as Forbidden,w as NotFound,T as Teapot,x as NotImplemented,y as Request,z as RemoteClassLoader,S as SerializerBuilder,E as ExecutionScopes}from"./globals-i6yGYfXV.js";import express from"express";import{readFileSync}from"fs";import{z}from"zod";import fs from"fs-extra";import glob from"glob-promise";import mime from"mime-types";import path from"path";import yargs from"yargs";import{Logger}from"tslog";import{fileURLToPath}from"url";import expressProxy from"express-http-proxy";class CorsMiddleware{#allowOrigin;#allowMethods="GET, POST";#allowHeaders;constructor(origin="*",headers="*"){this.#allowOrigin=origin,this.#allowHeaders=headers}get allowOrigin(){return this.#allowOrigin}get allowMethods(){return this.#allowMethods}get allowHeaders(){return this.#allowHeaders}async handle(request,next){const response=await next();return this.#setHeaders(response),response}#setHeaders(response){response.setHeader("Access-Control-Allow-Origin",this.#allowOrigin),response.setHeader("Access-Control-Allow-Methods",this.#allowMethods),response.setHeader("Access-Control-Allow-Headers",this.#allowHeaders)}}class WorkerBalancer{#workers=[];#currentIndex=0;addWorker(worker){this.#workers.includes(worker)||this.#workers.push(worker)}removeWorker(worker){const index=this.#workers.indexOf(worker);-1!==index&&this.#workers.splice(index,1)}getNextWorker(){if(0!==this.#workers.length)return this.#currentIndex>=this.#workers.length&&(this.#currentIndex=0),this.#workers[this.#currentIndex++]}run(request){const worker=this.getNextWorker();if(void 0===worker)throw new NoWorkerAvailable(request.fqn);return worker.run(request)}}class LocalGateway extends Gateway{#workers=new Set;#balancers=new Map;#trustKey;constructor(repository,url,trustKey){super(repository,url),this.#trustKey=trustKey}get workers(){return[...this.#workers.values()]}getProcedureNames(){const procedureNames=this.workers.map((worker=>worker.getProcedureNames()));return[...new Set(procedureNames.flat()).values()]}hasProcedure(fqn){return this.getProcedureNames().includes(fqn)}async addWorker(worker,trustKey){if(void 0!==trustKey&&this.#trustKey!==trustKey)throw new InvalidTrustKey;this.#workers.add(worker);for(const name of worker.getProcedureNames()){this.#getOrCreateBalancer(name).addWorker(worker)}}removeWorker(worker){this.#workers.delete(worker);for(const name of worker.getProcedureNames()){const balancer=this.#getBalancer(name);void 0!==balancer&&balancer.removeWorker(worker)}}#getBalancer(fqn){return this.#balancers.get(fqn)}#getOrCreateBalancer(fqn){let balancer=this.#getBalancer(fqn);return void 0===balancer&&(balancer=new WorkerBalancer,this.#balancers.set(fqn,balancer)),balancer}run(request){const balancer=this.#getBalancer(request.fqn);if(void 0===balancer)throw new ProcedureNotFound(request.fqn);return balancer.run(request)}}const CLIENT_ID_REGEX=/^CLIENT_\d+$/;let lastClientId=0;class ClientIdHelper{generate(){return"CLIENT_"+lastClientId++}validate(clientId){return CLIENT_ID_REGEX.test(clientId)}}const clientIdHelper$1=new ClientIdHelper;class LocalRepository extends Repository{#fileManager;#segments=new Map;#clients=new Map;#segmentNames=new Set;#assets=new Set;#overrides=new Map;constructor(fileManager,url){super(url),this.#fileManager=fileManager,ModuleLoader.setBaseUrl(fileManager.getRootLocation())}set segmentNames(names){this.#segmentNames=new Set(names)}set assets(patterns){this.#assets=patterns}set overrides(overrides){this.#overrides=overrides}async start(){await super.start(),await this.#loadSegments(),this.#translateOverrides()}stop(){return this.#unregisterClients(),this.#unloadSegments(),super.stop()}async#loadSegments(){for(const name of this.#segmentNames)await this.#loadSegment(name)}async#loadSegment(name){const relativeFilename=`./${createRepositoryFilename(name)}`,absoluteFilename=this.#fileManager.getAbsoluteLocation(relativeFilename),files=(await ModuleLoader.load(absoluteFilename)).files;if(void 0===files)throw new InvalidSegmentFile(absoluteFilename);this.registerSegment(name,files)}#unloadSegments(){this.#segments.clear()}async registerSegment(name,filenames){filenames.forEach((filename=>this.#segments.set(filename,name)))}#translateOverrides(){const translated=new Map;for(const[targetName,destinationName]of this.#overrides){const relativeTargetFilename=ModuleLoader.assureExtension(targetName),relativeDestinationFilename=ModuleLoader.assureExtension(destinationName),absoluteTargetFilename=this.#fileManager.getAbsoluteLocation(relativeTargetFilename),absoluteDestinationFilename=this.#fileManager.getAbsoluteLocation(relativeDestinationFilename);translated.set(absoluteTargetFilename,absoluteDestinationFilename)}this.#overrides=translated}async registerClient(segmentFilenames){const clientId=clientIdHelper$1.generate();return this.#clients.set(clientId,segmentFilenames),clientId}#unregisterClients(){this.#clients.clear()}readAsset(filename){if(!1===this.#assets.has(filename))throw new FileNotFound(filename);return this.#readFile(filename)}readModule(name,clientId){clientId=this.#validateClientId(clientId);const segmentFilename=this.#segments.get(name);return void 0===segmentFilename||this.#hasClientSegmentFile(clientId,segmentFilename)?this.#readWorkerModule(name):this.#readRemoteModule(name)}loadModule(name){const filename=this.#getModuleFilename(name);return ModuleLoader.load(filename)}#validateClientId(clientId){if(!1===clientIdHelper$1.validate(clientId))throw new InvalidClientId(clientId);if(!1===this.#clients.has(clientId))throw new ClientNotFound(clientId);return clientId}#hasClientSegmentFile(clientId,segmentFilename){const clientSegmentFiles=this.#clients.get(clientId);if(void 0===clientSegmentFiles)throw new ClientNotFound(clientId);return clientSegmentFiles.some((clientSegmentFilename=>segmentFilename.endsWith(clientSegmentFilename)))}async#readWorkerModule(name){const filename=this.#getModuleFilename(name),code=(await this.#readFile(filename)).content.toString();return new File(filename,"application/javascript",code)}#readRemoteModule(name){const remoteFilename=convertToRemoteFilename(name);return this.#readFile(remoteFilename)}#getModuleFilename(name){const relativeFilename=ModuleLoader.assureExtension(name),absoluteFilename=this.#fileManager.getAbsoluteLocation(relativeFilename);if(isSegmentFilename(absoluteFilename))return absoluteFilename;const assignedFilename=this.#getAssignedFilename(absoluteFilename);return convertToLocalFilename(assignedFilename)}#getAssignedFilename(filename){return this.#overrides.get(filename)??filename}#readFile(filename){return this.#fileManager.read(filename)}}class Proxy extends ProcedureRuntime{#runner;constructor(repository,runner,url){super(repository,url),this.#runner=runner}get runner(){return this.#runner}async start(){await super.start(),await this.#runner.start()}async stop(){await this.#runner.stop(),await super.stop()}getProcedureNames(){return this.#runner.getProcedureNames()}hasProcedure(fqn){return this.getProcedureNames().includes(fqn)}readAsset(filename){return this.repository.readAsset(filename)}registerClient(segmentFiles){return this.repository.registerClient(segmentFiles)}readModule(filename,clientId){return this.repository.readModule(filename,clientId)}run(request){return this.#runner.run(request)}}class RemoteWorker extends Worker{#remote;#procedureNames=new Set;constructor(url){super(new DummyRepository,url),this.#remote=new Remote(url)}get trustKey(){}set procedureNames(names){this.#procedureNames=names}getProcedureNames(){return[...this.#procedureNames.values()]}hasProcedure(name){return this.#procedureNames.has(name)}isHealthy(){return this.#remote.isHealthy()}getHealth(){return this.#remote.getHealth()}run(request){return this.#remote.run(request)}}class Standalone extends ProcedureRuntime{#worker;constructor(repository,worker,url){super(repository,url),this.#worker=worker}get worker(){return this.#worker}async start(){await this.#worker.start(),await super.start()}async stop(){await this.#worker.stop(),await super.stop()}getProcedureNames(){return this.#worker.getProcedureNames()}hasProcedure(fqn){return this.getProcedureNames().includes(fqn)}readAsset(filename){return this.repository.readAsset(filename)}registerClient(segmentFiles){return this.repository.registerClient(segmentFiles)}readModule(filename,clientId){return this.repository.readModule(filename,clientId)}run(request){return this.#worker.run(request)}}class RuntimeBuilder{#url;#fileManager;#segments=new Set;#healthChecks=new Set;#middlewares=new Set;#assets=new Set;#overrides=new Map;#repository;#gateway;#worker;url(url){return this.#url=url,this}fileManager(fileManager){return this.#fileManager=fileManager,this}segment(...names){return names.forEach((name=>this.#segments.add(name))),this}healthCheck(...filenames){return filenames.forEach((filename=>this.#healthChecks.add(filename))),this}middleware(...filenames){return filenames.forEach((filename=>this.#middlewares.add(filename))),this}asset(...patterns){return patterns.forEach((pattern=>this.#assets.add(pattern))),this}override(...mappings){for(const map of mappings)for(const[key,value]of Object.entries(map))this.#overrides.set(key,value);return this}repository(url){return this.#repository=void 0!==url?new RemoteRepository(url):void 0,this}gateway(url){return this.#gateway=void 0!==url?new RemoteGateway(url):void 0,this}worker(url){return this.#worker=void 0!==url?new RemoteWorker(url):void 0,this}buildRepository(){if(void 0===this.#fileManager)throw new RuntimeNotBuilt("File manager is not set for the repository");const repository=new LocalRepository(this.#fileManager,this.#url);return repository.healthCheckFiles=this.#healthChecks,repository.segmentNames=this.#segments,repository.assets=this.#assets,repository.overrides=this.#overrides,repository}buildGateway(trustKey){if(void 0===this.#repository)throw new RuntimeNotBuilt("Repository is not set for the gateway");const gateway=new LocalGateway(this.#repository,this.#url,trustKey);return gateway.healthCheckFiles=this.#healthChecks,gateway.middlewareFiles=this.#middlewares,gateway}buildWorker(trustKey){if(void 0===this.#repository)throw new RuntimeNotBuilt("Repository is not set for the worker");const worker=new LocalWorker(this.#repository,this.#gateway,this.#url,trustKey);return worker.segmentNames=this.#segments,worker.healthCheckFiles=this.#healthChecks,worker.middlewareFiles=this.#middlewares,this.#repository.segmentNames=this.#segments,void 0!==this.gateway&&(this.#gateway.worker=worker),worker}buildProxy(){if(void 0===this.#repository)throw new RuntimeNotBuilt("Repository is not set for the proxy");const runner=this.#gateway??this.#worker;if(void 0===runner)throw new RuntimeNotBuilt("Runner (gateway or worker) is not set for the proxy");const proxy=new Proxy(this.#repository,runner,this.#url);return proxy.healthCheckFiles=this.#healthChecks,proxy.middlewareFiles=this.#middlewares,proxy}buildStandalone(trustKey){if(void 0===this.#fileManager)throw new RuntimeNotBuilt("File manager is not set for the standalone");const repository=new LocalRepository(this.#fileManager,this.#url);repository.segmentNames=this.#segments,repository.assets=this.#assets,repository.overrides=this.#overrides;const worker=new LocalWorker(repository,this.#gateway,this.#url,trustKey);worker.segmentNames=this.#segments;const standalone=new Standalone(repository,worker,this.#url);return standalone.healthCheckFiles=this.#healthChecks,standalone.middlewareFiles=this.#middlewares,standalone}}class ProcedureRuntimeConfiguration{#middlewares;constructor(middlewares){this.#middlewares=middlewares}get middlewares(){return this.#middlewares}}const gatewaySchema=z.object({repository:z.string().url(),middlewares:z.array(z.string()).optional(),monitor:z.number().optional(),trustKey:z.string().optional()}).strict().transform((value=>new GatewayConfiguration(value.repository,value.middlewares,value.monitor,value.trustKey)));class GatewayConfiguration extends ProcedureRuntimeConfiguration{#monitor;#repository;#trustKey;constructor(repository,middlewares,monitor,trustKey){super(middlewares),this.#monitor=monitor,this.#repository=repository,this.#trustKey=trustKey}get monitor(){return this.#monitor}get repository(){return this.#repository}get trustKey(){return this.#trustKey}}const workerSchema=z.object({gateway:z.string().url().optional(),repository:z.string().url().optional(),segments:z.array(z.string()).nonempty(),middlewares:z.array(z.string()).optional(),trustKey:z.string().optional()}).strict().transform((value=>new WorkerConfiguration(value.gateway,value.repository,value.segments,value.middlewares,value.trustKey)));class WorkerConfiguration extends ProcedureRuntimeConfiguration{#gateway;#repository;#segments;#trustKey;constructor(gateway,repository,segments,middlewares,trustKey){super(middlewares),this.#gateway=gateway,this.#repository=repository,this.#segments=segments,this.#trustKey=trustKey}get gateway(){return this.#gateway}get repository(){return this.#repository}get segments(){return this.#segments}get trustKey(){return this.#trustKey}}const proxySchema=z.object({worker:z.string().url().optional(),gateway:z.string().url().optional(),repository:z.string().url(),middlewares:z.array(z.string()).optional()}).strict().superRefine(((value,ctx)=>{void 0===value.worker&&void 0===value.gateway&&ctx.addIssue({code:z.ZodIssueCode.custom,message:"Either worker or gateway must be defined",path:["worker","gateway"]}),void 0!==value.worker&&void 0!==value.gateway&&ctx.addIssue({code:z.ZodIssueCode.custom,message:"Only worker or gateway must be defined",path:["worker","gateway"]})})).transform((value=>new ProxyConfiguration(value.worker,value.gateway,value.repository,value.middlewares)));class ProxyConfiguration extends ProcedureRuntimeConfiguration{#worker;#gateway;#repository;constructor(worker,gateway,repository,middlewares){super(middlewares),this.#worker=worker,this.#gateway=gateway,this.#repository=repository}get worker(){return this.#worker}get gateway(){return this.#gateway}get repository(){return this.#repository}}const repositorySchema=z.object({source:z.string().optional(),cache:z.string().optional(),index:z.string().optional(),serveIndexOnNotFound:z.boolean().optional(),assets:z.array(z.string()).optional(),overrides:z.record(z.string(),z.string()).optional()}).strict().transform((value=>new RepositoryConfiguration(value.source,value.cache,value.index,value.serveIndexOnNotFound,value.assets,value.overrides)));class RepositoryConfiguration{#source;#cache;#index;#serveIndexOnNotFound;#assets;#overrides;constructor(source,cache,index,serveIndexOnNotFound,assets,overrides){this.#source=source,this.#cache=cache,this.#index=index,this.#serveIndexOnNotFound=serveIndexOnNotFound,this.#assets=assets,this.#overrides=overrides}get source(){return this.#source}get cache(){return this.#cache}get index(){return this.#index}get serveIndexOnNotFound(){return this.#serveIndexOnNotFound}get assets(){return this.#assets}get overrides(){return this.#overrides}}const standaloneSchema=z.object({source:z.string().optional(),cache:z.string().optional(),index:z.string().optional(),serveIndexOnNotFound:z.boolean().optional(),segments:z.array(z.string()).optional(),assets:z.array(z.string()).optional(),middlewares:z.array(z.string()).optional(),overrides:z.record(z.string(),z.string()).optional(),trustKey:z.string().optional()}).strict().transform((value=>new StandaloneConfiguration(value.source,value.cache,value.index,value.serveIndexOnNotFound,value.segments,value.assets,value.middlewares,value.overrides,value.trustKey)));class StandaloneConfiguration extends ProcedureRuntimeConfiguration{#source;#cache;#index;#serveIndexOnNotFound;#segments;#assets;#overrides;#trustKey;constructor(source,cache,index,serveIndexOnNotFound,segments,assets,middlewares,overrides,trustKey){super(middlewares),this.#source=source,this.#cache=cache,this.#index=index,this.#serveIndexOnNotFound=serveIndexOnNotFound,this.#segments=segments,this.#assets=assets,this.#overrides=overrides,this.#trustKey=trustKey}get source(){return this.#source}get cache(){return this.#cache}get index(){return this.#index}get serveIndexOnNotFound(){return this.#serveIndexOnNotFound}get segments(){return this.#segments}get assets(){return this.#assets}get overrides(){return this.#overrides}get trustKey(){return this.#trustKey}}const runtimeSchema=z.object({url:z.string().optional(),setUp:z.array(z.string()).optional(),tearDown:z.array(z.string()).optional(),healthChecks:z.array(z.string()).optional(),standalone:standaloneSchema.optional(),repository:repositorySchema.optional(),gateway:gatewaySchema.optional(),worker:workerSchema.optional(),proxy:proxySchema.optional()}).strict().transform((value=>new RuntimeConfiguration(value.url,value.setUp,value.tearDown,value.healthChecks,value.standalone,value.repository,value.gateway,value.worker,value.proxy)));class RuntimeConfiguration{#url;#setUp;#tearDown;#healthChecks;#standalone;#repository;#gateway;#worker;#proxy;constructor(url,setUp,tearDown,healthChecks,standalone,repository,gateway,worker,proxy){this.#url=url,this.#setUp=setUp,this.#tearDown=tearDown,this.#healthChecks=healthChecks,this.#standalone=standalone,this.#repository=repository,this.#gateway=gateway,this.#worker=worker,this.#proxy=proxy}get url(){return this.#url}get setUp(){return this.#setUp}get tearDown(){return this.#tearDown}get healthChecks(){return this.#healthChecks}get standalone(){return this.#standalone}get repository(){return this.#repository}get gateway(){return this.#gateway}get worker(){return this.#worker}get proxy(){return this.#proxy}}class EnvironmentVariableNotFound extends Error{constructor(name){super(`The environment variable '${name}' is not found`)}}class ConversionError extends Error{constructor(message){super(message)}}class DataConverter{static convert(schema,dataObject){try{return schema.parse(dataObject)}catch(error){const errors=error.errors,message=JSON.stringify(errors);throw new ConversionError(message)}}}const ENVIRONMENT_VARIABLE_REGEX=/\${([^}]*)}/g;class RuntimeConfigurationLoader{static load(filename){const plainContents=readFileSync(filename,"utf-8"),replacedContents=this.#replaceEnvironmentVariables(plainContents),parsedContents=JSON.parse(replacedContents);return DataConverter.convert(runtimeSchema,parsedContents)}static#replaceEnvironmentVariables(contents){return contents.replace(ENVIRONMENT_VARIABLE_REGEX,((match,group)=>{const value=process.env[group];if(void 0===value)throw new EnvironmentVariableNotFound(group);return value}))}}class ApplicationCache{#segments;#modules;constructor(segments,modules){this.#segments=segments,this.#modules=modules}get segments(){return this.#segments}get modules(){return this.#modules}}class ModuleCache{#module;#segment;constructor(module,segment){this.#module=module,this.#segment=segment}get module(){return this.#module}get segment(){return this.#segment}}class ModuleCacheBuilder{build(application,module){const segment=application.getSegmentModule(module.filename);return new ModuleCache(module,segment)}}class DuplicateImplementation extends Error{constructor(fqn,version){super(`Duplicate implementation found for '${fqn}' with version '${version}'.`)}}class SegmentCache{#name;#imports;#procedures;#files;constructor(name,files,imports,procedures){this.#name=name,this.#files=files,this.#imports=imports,this.#procedures=procedures}get name(){return this.#name}get files(){return this.#files}get imports(){return this.#imports}get procedures(){return this.#procedures}}class SegmentImport{#members;#from;constructor(members,from){this.#members=members,this.#from=from}get members(){return this.#members}get from(){return this.#from}}class SegmentProcedure{#fqn;#implementations=[];constructor(fqn,implementations=[]){this.#fqn=fqn,this.#implementations=implementations}get fqn(){return this.#fqn}get implementations(){return this.#implementations}addImplementation(implementation){this.#implementations.push(implementation)}}class SegmentCacheBuilder{build(segment){const files=this.#extractFiles(segment),imports=this.#createImports(segment),procedures=this.#mergeProcedures(segment);return this.#validateProcedures(procedures),new SegmentCache(segment.name,files,imports,procedures)}#extractFiles(segment){return segment.modules.map((module=>module.filename))}#createImports(segment){const imports=[];for(const module of segment.modules){let members=[];for(const procedure of module.procedures){const ids=procedure.implementations.map((implementation=>this.#createImportMember(implementation)));members=[...members,...ids]}imports.push(new SegmentImport(members,module.filename))}return imports}#createImportMember(implementation){return`${implementation.importKey} : ${implementation.id}`}#mergeProcedures(segment){const procedures=new Map;for(const module of segment.modules)for(const procedure of module.procedures){if(procedures.has(procedure.fqn)){const existingProcedure=procedures.get(procedure.fqn);for(const implementation of procedure.implementations)existingProcedure.addImplementation(implementation);continue}const procedureCopy=new SegmentProcedure(procedure.fqn,[...procedure.implementations]);procedures.set(procedure.fqn,procedureCopy)}return[...procedures.values()]}#validateProcedures(procedures){for(const procedure of procedures)this.#checkForDuplicateImplementations(procedure)}#checkForDuplicateImplementations(procedure){for(const implementation of procedure.implementations){if(void 0!==procedure.implementations.find((other=>other.id!==implementation.id&&other.version===implementation.version)))throw new DuplicateImplementation(procedure.fqn,implementation.version)}}}class ApplicationCacheBuilder{#moduleCacheBuilder;#segmentCacheBuilder;constructor(){this.#moduleCacheBuilder=new ModuleCacheBuilder,this.#segmentCacheBuilder=new SegmentCacheBuilder}build(application){const segments=this.#buildSegmentCaches(application),modules=this.#buildModuleCaches(application);return new ApplicationCache(segments,modules)}#buildSegmentCaches(application){return application.segments.map((segment=>this.#segmentCacheBuilder.build(segment)))}#buildModuleCaches(application){return application.modules.map((module=>this.#moduleCacheBuilder.build(application,module)))}}const Keyword={DEFAULT:"default"};Object.freeze(Keyword);const IMPORT_PATTERN=/import\s(?:["'\s]*([\w*{}\n, ]+)from\s*)?["'\s]*([@\w/._-]+)["'\s].*/g,APPLICATION_MODULE_INDICATORS=[".","/","http:","https:"],reflector$2=new Reflector;const importRewriter=new class{rewrite(code,filename){return code.replaceAll(IMPORT_PATTERN,(statement=>this.#replaceImport(statement,filename)))}#replaceImport(statement,filename){const dependency=reflector$2.parseImport(statement);return this.#isApplicationModule(dependency)?this.#rewriteApplicationImport(dependency,filename):this.#rewriteRuntimeImport(dependency,filename)}#isApplicationModule(dependency){return APPLICATION_MODULE_INDICATORS.some((indicator=>dependency.from.startsWith(indicator,1)))}#rewriteApplicationImport(dependency,filename){return this.#rewriteImport(dependency,"application",filename)}#rewriteRuntimeImport(dependency,filename){return this.#rewriteImport(dependency,"runtime",filename)}#rewriteImport(dependency,scope,filename){const from=this.#rewriteImportFrom(dependency,filename);if(0===dependency.members.length)return`await __import("${from}", "${scope}");`;return`const ${this.#rewriteImportMembers(dependency)} = await __import("${from}", "${scope}", ${this.#mustUseAs(dependency)?"true":"false"});`}#rewriteImportFrom(dependency,filename){const from=dependency.from.substring(1,dependency.from.length-1);return this.#isApplicationModule(dependency)?this.#mergeFilenames(filename,from):from}#rewriteImportMembers(dependency){if(this.#mustUseAs(dependency))return dependency.members[0].as;return`{ ${dependency.members.map((member=>member.name!==member.as?`${member.name} : ${member.as}`:member.name)).join(", ")} }`}#mergeFilenames(sourceFilename,importFilename){const concatenated=`${this.#extractFilepath(sourceFilename)}/${importFilename}`,translated=this.#translateFilename(concatenated),rooted=this.#ensureRoot(translated);return this.#ensureExtension(rooted)}#extractFilepath(filename){return filename.split("/").slice(0,-1).join("/")}#translateFilename(filename){const parts=filename.split("/"),translated=[];translated.push(parts[0]);for(let index=1;index<parts.length;index++){const part=parts[index].trim();switch(part){case"":case".":continue;case"..":translated.pop();continue}translated.push(part)}return translated.join("/")}#ensureRoot(filename){return filename.startsWith("./")?filename:(filename.startsWith("//")&&(filename=filename.substring(1)),filename.startsWith("/")?`.${filename}`:`./${filename}`)}#ensureExtension(filename){return filename.endsWith(".js")?filename:`${filename}.js`}#mustUseAs(dependency){return this.#doesImportAll(dependency)||this.#doesImportDefault(dependency)}#doesImportAll(dependency){return 1===dependency.members.length&&"*"===dependency.members[0].name}#doesImportDefault(dependency){return 1===dependency.members.length&&dependency.members[0].name===Keyword.DEFAULT}},remoteBuilder=new class{build(module){let code="";for(const procedure of module.procedures)for(const implementation of procedure.implementations){const asDefault=implementation.importKey===Keyword.DEFAULT;code+=implementation.access===AccessLevels.PRIVATE?this.#createPrivateCode(procedure.fqn,implementation,asDefault):this.#createPublicCode(procedure.fqn,implementation,asDefault)}return code.trim()}#createPrivateCode(fqn,implementation,asDefault){const version=implementation.version,declaration=this.#createDeclaration(implementation,asDefault),body=`throw new ProcedureNotAccessible('${fqn}', '${version}');`;return this.#createFunction(declaration,body)}#createPublicCode(fqn,implementation,asDefault){const version=implementation.version,args=this.#createArguments(implementation.executable.parameters),declaration=this.#createDeclaration(implementation,asDefault),body=`return __run('${fqn}', '${version}', { ${args} }, this);`;return this.#createFunction(declaration,body)}#createParameters(parameters){const result=[];for(const parameter of parameters)parameter instanceof ReflectionField?result.push(parameter.name):(parameter instanceof ReflectionDestructuredArray||parameter instanceof ReflectionDestructuredObject)&&result.push(parameter.toString());return result.join(", ")}#createArguments(parameters){return this.#extractArguments(parameters).join(", ")}#extractArguments(parameters){const result=[];for(const parameter of parameters)if(parameter instanceof ReflectionDestructuredValue){const argumentz=this.#extractArguments(parameter.members);result.push(...argumentz)}else if(parameter instanceof ReflectionField){const argument=this.#createNamedArgument(parameter);result.push(argument)}return result}#createNamedArgument(parameter){const key=parameter.name,value=key.startsWith("...")?key.substring(3):key;return`'${key}': ${value}`}#createDeclaration(implementation,asDefault){const name=implementation.executable.name,parameters=this.#createParameters(implementation.executable.parameters);return`\nexport ${asDefault?`${Keyword.DEFAULT} `:""}async function ${name}(${parameters})`}#createFunction(declaration,body){return`${declaration} {\n\t${body}\n}\n`}};class ModuleCacheWriter{#fileManager;constructor(fileManager){this.#fileManager=fileManager}async write(cache){return Promise.all([this.#writeLocal(cache),this.#writeRemote(cache)]).then((()=>{}))}async#writeLocal(cache){const importCode=this.#rewriteAllImports(cache.module),sourceCode=this.#createSourceCode(cache.module),filename=convertToLocalFilename(cache.module.filename),code=`${importCode}\n${sourceCode}`;return this.#fileManager.write(filename,code.trim())}#rewriteAllImports(module){return importRewriter.rewrite(module.code,module.filename)}#createSourceCode(module){const filename=module.filename;return module.content.exportedClasses.map((clazz=>clazz.name)).map((className=>`${className}.source = "./${filename}";`)).join("\n")}async#writeRemote(cache){if(void 0===cache.segment)return;const filename=convertToRemoteFilename(cache.module.filename),code=this.#createRemoteCode(cache.segment);return this.#fileManager.write(filename,code.trim())}#createRemoteCode(module){return remoteBuilder.build(module)}}class SegmentCacheWriter{#fileManager;constructor(fileManager){this.#fileManager=fileManager}async write(cache){return Promise.all([this.#writeWorkerCache(cache),this.#writeRepositoryCache(cache)]).then((()=>{}))}async#writeWorkerCache(cache){const importCode=this.#createImportCode(cache.imports),segmentCode=this.#createSegmentCode(cache.name,cache.procedures),filename=createWorkerFilename(cache.name),code=`${importCode}\n${segmentCode}`;return this.#fileManager.write(filename,code)}async#writeRepositoryCache(cache){const filename=createRepositoryFilename(cache.name),code=`export const files = [\n\t"${[...cache.files].join('",\n\t"')}"\n];`;return this.#fileManager.write(filename,code)}#createImportCode(imports){const codes=[];for(const{members:members,from:from}of imports)codes.push(`const { ${members.join(", ")} } = await __import("./${from}", "application", false);`);return codes.join("\n")}#createSegmentCode(name,procedures){const codes=[];codes.push('const { Segment, Procedure, Implementation, Version, NamedParameter, ArrayParameter, ObjectParameter } = await __import("jitar", "runtime", false);'),codes.push(`export const segment = new Segment("${name}")`);for(const procedure of procedures){codes.push(`\t.addProcedure(new Procedure("${procedure.fqn}")`);for(const implementation of procedure.implementations){const version=this.#createVersionCode(implementation.version),parameters=this.#createParametersCode(implementation.executable);codes.push(`\t\t.addImplementation(new Implementation(${version}, "${implementation.access}", ${parameters}, ${implementation.id}))`)}codes.push("\t)")}return codes.join("\n")}#createVersionCode(versionString){const version=VersionParser.parse(versionString);return`new Version(${version.major}, ${version.minor}, ${version.patch})`}#createParametersCode(executable){return`[${this.#extractParameters(executable.parameters).join(", ")}]`}#extractParameters(parameters){const result=[];for(const parameter of parameters)result.push(this.#extractParameter(parameter));return result}#extractParameter(parameter){return parameter instanceof ReflectionDestructuredArray?this.#createArrayParameter(parameter):parameter instanceof ReflectionDestructuredObject?this.#createObjectParameter(parameter):this.#createNamedParameter(parameter)}#createNamedParameter(parameter){return`new NamedParameter("${parameter.name}", ${void 0!==parameter.value})`}#createArrayParameter(parameter){return`new ArrayParameter([${this.#extractParameters(parameter.members).join(", ")}])`}#createObjectParameter(parameter){return`new ObjectParameter([${this.#extractParameters(parameter.members).join(", ")}])`}}class ApplicationCacheWriter{#moduleWriter;#segmentWriter;constructor(fileManager){this.#moduleWriter=new ModuleCacheWriter(fileManager),this.#segmentWriter=new SegmentCacheWriter(fileManager)}async write(cache){return Promise.all([this.#writeSegmentCache(cache.segments),this.#writeModuleCache(cache.modules)]).then((()=>{}))}async#writeSegmentCache(segments){return Promise.all(segments.map((async segment=>this.#segmentWriter.write(segment)))).then((()=>{}))}async#writeModuleCache(modules){return Promise.all(modules.map((async module=>this.#moduleWriter.write(module)))).then((()=>{}))}}class Application{#segments;#modules;constructor(segments,modules){this.#segments=segments,this.#modules=modules}get segments(){return this.#segments}get modules(){return this.#modules}getSegmentModule(filename){const segment=this.#segments.find((segment=>segment.hasModule(filename)));if(void 0!==segment)return segment.getModule(filename)}}class ModuleFileNotLoaded extends Error{constructor(filename,message){super(`Failed to load module file '${filename}' because of: ${message}`)}}class Module{#filename;#code;#content;constructor(filename,code,content){this.#code=code,this.#filename=filename,this.#content=content}get filename(){return this.#filename}get code(){return this.#code}get content(){return this.#content}}const reflector$1=new Reflector;class ModuleReader{#fileManager;constructor(fileManager){this.#fileManager=fileManager}async read(filename){const relativeLocation=this.#fileManager.getRelativeLocation(filename),code=await this.#loadCode(filename),module=reflector$1.parse(code);return new Module(relativeLocation,code,module)}async#loadCode(filename){try{return(await this.#fileManager.getContent(filename)).toString()}catch(error){const message=error instanceof Error?error.message:String(error);throw new ModuleFileNotLoaded(filename,message)}}}class FunctionNotAsync extends Error{constructor(filename,functionName){super(`Function '${functionName}' from file '${filename}' is not async`)}}class InvalidSegmentFilename extends Error{constructor(filename){super(`Segment filename '${filename}' is invalid`)}}class MissingModuleExport extends Error{constructor(filename,key){super(`Module '${filename}' does not export '${key}'`)}}class SegmentFileNotLoaded extends Error{constructor(filename,message){super(`Failed to load segment file '${filename}' because of: ${message}`)}}class SegmentModuleNotLoaded extends Error{constructor(filename,message){super(`Segment module could not be loaded from '${filename}' because of: ${message}`)}}class Segment{#name;#modules;constructor(name,modules){this.#name=name,this.#modules=modules}get name(){return this.#name}get modules(){return this.#modules}hasModule(filename){return this.#modules.some((module=>module.filename===filename))}getModule(filename){return this.#modules.find((module=>module.filename===filename))}}class SegmentImplementation{#id;#importKey;#access;#version;#executable;constructor(id,importKey,access,version,executable){this.#id=id,this.#importKey=importKey,this.#access=access,this.#version=version,this.#executable=executable}get id(){return this.#id}get importKey(){return this.#importKey}get access(){return this.#access}get version(){return this.#version}get executable(){return this.#executable}}class SegmentModule{#filename;#procedures=[];constructor(filename,procedures){this.#filename=filename,this.#procedures=procedures}get filename(){return this.#filename}get procedures(){return this.#procedures}}class IdGenerator{#id=0;next(){return"$"+ ++this.#id}}const reflector=new Reflector;class SegmentReader{#fileManager;constructor(fileManager){this.#fileManager=fileManager}async read(filename){const name=this.#extractSegmentName(filename),definition=await this.#loadSegmentDefinition(filename),modules=await this.#createSegmentModules(definition);return new Segment(name,modules)}#extractSegmentName(filename){const file=filename.split("/").pop();if(void 0===file||""===file)throw new InvalidSegmentFilename(filename);return file.replace(".segment.json","")}async#loadSegmentDefinition(filename){try{const content=await this.#fileManager.getContent(filename);return JSON.parse(content.toString())}catch(error){const message=error instanceof Error?error.message:String(error);throw new SegmentFileNotLoaded(filename,message)}}async#createSegmentModules(definition){const modules=[],idGenerator=new IdGenerator;for(const[filename,moduleImports]of Object.entries(definition)){const fullFilename=filename.endsWith(".js")?filename:`${filename}.js`,absoluteFilename=this.#fileManager.getAbsoluteLocation(fullFilename),module=await this.#createSegmentModule(absoluteFilename,moduleImports,idGenerator);modules.push(module)}return modules}async#createSegmentModule(absoluteFilename,imports,idGenerator){const module=await this.#loadSegmentModule(absoluteFilename),relativeFilename=this.#fileManager.getRelativeLocation(absoluteFilename),moduleName=this.#extractModuleName(relativeFilename),procedures=this.#extractSegmentProcedures(module,moduleName,imports,idGenerator);return new SegmentModule(relativeFilename,procedures)}async#loadSegmentModule(absoluteLocation){try{const code=await this.#fileManager.getContent(absoluteLocation);return reflector.parse(code.toString())}catch(error){const message=error instanceof Error?error.message:String(error);throw new SegmentModuleNotLoaded(absoluteLocation,message)}}#extractModuleName(relativeFilename){const moduleParts=relativeFilename.split("/");return moduleParts.pop(),moduleParts.join("/")}#extractSegmentProcedures(module,moduleName,imports,idGenerator){const procedures=new Map;for(const[importKey,properties]of Object.entries(imports)){const executable=module.getExported(importKey);if(void 0===executable)throw new MissingModuleExport(moduleName,importKey);if(executable instanceof ReflectionFunction==!1)continue;if(!1===executable.isAsync)throw new FunctionNotAsync(moduleName,executable.name);const procedureName=properties.as??executable.name,access=properties.access??"private",version=properties.version??"0.0.0",id=idGenerator.next(),fqn=""!==moduleName?`${moduleName}/${procedureName}`:procedureName,implementation=new SegmentImplementation(id,importKey,access,version,executable),procedure=procedures.has(fqn)?procedures.get(fqn):new SegmentProcedure(fqn);procedure.addImplementation(implementation),procedures.set(fqn,procedure)}return[...procedures.values()]}}class ApplicationReader{#moduleReader;#segmentReader;constructor(fileManager){this.#moduleReader=new ModuleReader(fileManager),this.#segmentReader=new SegmentReader(fileManager)}async read(segmentFiles,moduleFiles){return Promise.all([this.#readSegments(segmentFiles),this.#readModules(moduleFiles)]).then((([segments,modules])=>new Application(segments,modules)))}async#readSegments(segmentFiles){return Promise.all(segmentFiles.map((async segmentFile=>this.#segmentReader.read(segmentFile))))}async#readModules(moduleFiles){return Promise.all(moduleFiles.map((async moduleFile=>this.#moduleReader.read(moduleFile))))}}class CacheManager{#projectFileManager;#appFileManager;#reader;#builder;#writer;constructor(projectFileManager,appFileManager){this.#projectFileManager=projectFileManager,this.#appFileManager=appFileManager,this.#reader=new ApplicationReader(appFileManager),this.#builder=new ApplicationCacheBuilder,this.#writer=new ApplicationCacheWriter(appFileManager)}async build(){const segmentFiles=await this.#projectFileManager.filter(Files.SEGMENT_PATTERN),moduleFiles=await this.#appFileManager.filter(Files.MODULE_PATTERN),application=await this.#reader.read(segmentFiles,moduleFiles),cache=this.#builder.build(application);return this.#writer.write(cache)}}const RuntimeDefaults={URL:"http://localhost:3000",SOURCE:"./dist",CACHE:"./.jitar",INDEX:"index.html",SERVE_INDEX_ON_NOT_FOUND:!1};Object.freeze(RuntimeDefaults);class UnknownRuntimeMode extends Error{constructor(){super("Unknown runtime mode")}}class LocalFileManager{#location;constructor(location){this.#location=location}getRootLocation(){return path.resolve(this.#location)}getAbsoluteLocation(filename){const location=filename.startsWith("/")?filename:path.join(this.#location,filename);return path.resolve(location)}getRelativeLocation(filename){return path.relative(this.#location,filename)}async getType(filename){const location=this.getAbsoluteLocation(filename);return mime.lookup(location)||"application/octet-stream"}async getContent(filename){const location=this.getAbsoluteLocation(filename);if(!1===fs.existsSync(location))throw new FileNotFound(filename);return fs.readFile(location)}async read(filename){const rootPath=this.getRootLocation(),absoluteFilename=this.getAbsoluteLocation(filename);if(!1===absoluteFilename.startsWith(rootPath))throw new FileNotFound(filename);const type=await this.getType(absoluteFilename),content=await this.getContent(absoluteFilename);return new File(filename,type,content)}async write(filename,content){const location=this.getAbsoluteLocation(filename),directory=path.dirname(location);return await fs.mkdir(directory,{recursive:!0}),fs.writeFile(location,content)}async copy(source,destination){const sourceLocation=this.getAbsoluteLocation(source),destinationLocation=this.getAbsoluteLocation(destination);return fs.copy(sourceLocation,destinationLocation,{overwrite:!0})}async delete(filename){const location=this.getAbsoluteLocation(filename);return fs.remove(location)}async filter(pattern){const location=this.getAbsoluteLocation("./");return glob(`${location}/${pattern}`)}async getWorkerSegmentFiles(){return this.filter("**/*.segment.worker.js")}async getRepositorySegmentFiles(){return this.filter("**/*.segment.repository.js")}async getAssetFiles(patterns){const promises=patterns.map((pattern=>this.filter(pattern)));return(await Promise.all(promises)).flat().map((filename=>this.getRelativeLocation(filename))).filter((filename=>!1===this.#isGeneratedFile(filename)))}#isGeneratedFile(filename){return filename.endsWith(".local.js")||filename.endsWith(".worker.js")||filename.endsWith(".repository.js")||filename.endsWith(".remote.js")}}class RuntimeConfigurator{static async configure(configuration){const url=configuration.url??RuntimeDefaults.URL,healthChecks=configuration.healthChecks??[];if(void 0!==configuration.standalone)return this.#configureStandAlone(url,healthChecks,configuration.standalone);if(void 0!==configuration.repository)return this.#configureRepository(url,healthChecks,configuration.repository);if(void 0!==configuration.gateway)return this.#configureGateway(url,healthChecks,configuration.gateway);if(void 0!==configuration.worker)return this.#configureWorker(url,healthChecks,configuration.worker);if(void 0!==configuration.proxy)return this.#configureProxy(url,healthChecks,configuration.proxy);throw new UnknownRuntimeMode}static async#configureStandAlone(url,healthChecks,configuration){const sourceLocation=configuration.source??RuntimeDefaults.SOURCE,cacheLocation=configuration.cache??RuntimeDefaults.CACHE,overrides=configuration.overrides??{},middlewares=configuration.middlewares??[],fileManager=new LocalFileManager(cacheLocation),trustKey=configuration.trustKey;await this.#buildCache(sourceLocation,cacheLocation);const segmentNames=void 0===configuration.segments?await this.#getWorkerSegmentNames(fileManager):configuration.segments,assets=void 0!==configuration.assets?await fileManager.getAssetFiles(configuration.assets):[];return(new RuntimeBuilder).url(url).healthCheck(...healthChecks).middleware(...middlewares).segment(...segmentNames).asset(...assets).override(overrides).fileManager(fileManager).buildStandalone(trustKey)}static async#configureRepository(url,healthChecks,configuration){const sourceLocation=configuration.source??RuntimeDefaults.SOURCE,cacheLocation=configuration.cache??RuntimeDefaults.CACHE,overrides=configuration.overrides??{},fileManager=new LocalFileManager(cacheLocation);await this.#buildCache(sourceLocation,cacheLocation);const segmentNames=await this.#getRepositorySegmentNames(fileManager),assets=void 0!==configuration.assets?await fileManager.getAssetFiles(configuration.assets):[];return(new RuntimeBuilder).url(url).healthCheck(...healthChecks).segment(...segmentNames).asset(...assets).override(overrides).fileManager(fileManager).buildRepository()}static async#configureGateway(url,healthChecks,configuration){const repositoryUrl=configuration.repository,middlewares=configuration.middlewares??[];configuration.monitor;const trustKey=configuration.trustKey;return(new RuntimeBuilder).url(url).healthCheck(...healthChecks).middleware(...middlewares).repository(repositoryUrl).buildGateway(trustKey)}static async#configureWorker(url,healthChecks,configuration){const repositoryUrl=configuration.repository,gatewayUrl=configuration.gateway,segmentNames=configuration.segments??[],middlewares=configuration.middlewares??[],trustKey=configuration.trustKey;return(new RuntimeBuilder).url(url).healthCheck(...healthChecks).middleware(...middlewares).repository(repositoryUrl).gateway(gatewayUrl).segment(...segmentNames).buildWorker(trustKey)}static async#configureProxy(url,healthChecks,configuration){const repositoryUrl=configuration.repository,gatewayUrl=configuration.gateway,workerUrl=configuration.worker,middlewares=configuration.middlewares??[];return(new RuntimeBuilder).url(url).healthCheck(...healthChecks).middleware(...middlewares).repository(repositoryUrl).gateway(gatewayUrl).worker(workerUrl).buildProxy()}static async#buildCache(sourceLocation,cacheLocation){const projectFileManager=new LocalFileManager("./");await projectFileManager.delete(cacheLocation),await projectFileManager.copy(sourceLocation,cacheLocation);const appFileManager=new LocalFileManager(cacheLocation),cacheManager=new CacheManager(projectFileManager,appFileManager);await cacheManager.build()}static async#getWorkerSegmentNames(fileManager){return(await fileManager.getWorkerSegmentFiles()).map((filename=>this.#extractSegmentName(filename)))}static async#getRepositorySegmentNames(fileManager){return(await fileManager.getRepositorySegmentFiles()).map((filename=>this.#extractSegmentName(filename)))}static#extractSegmentName(filename){const name=filename.split("/").pop()??"",endIndex=name.indexOf(".segment");return name.substring(0,endIndex)}}var LogLevel;!function(LogLevel){LogLevel.DEBUG="debug",LogLevel.INFO="info",LogLevel.WARN="warn",LogLevel.ERROR="error",LogLevel.FATAL="fatal"}(LogLevel||(LogLevel={}));class LogBuilder{static build(level){const logConfiguration=this.#getLogConfiguration(level);return new Logger(logConfiguration)}static#getLogConfiguration(level){return{prettyLogTemplate:"{{dateIsoStr}}\t{{logLevelName}}\t",minLevel:this.#getLogLevel(level)}}static#getLogLevel(level){switch(level){case LogLevel.FATAL:return 6;case LogLevel.ERROR:return 5;case LogLevel.WARN:return 4;case LogLevel.INFO:return 3;default:return 2}}}const serverOptionsSchema=z.object({loglevel:z.nativeEnum(LogLevel).optional(),config:z.string().endsWith(".json")}).transform((value=>new ServerOptions(value.config,value.loglevel)));class ServerOptions{#config;#loglevel;constructor(config,loglevel="info"){this.#config=config,this.#loglevel=loglevel}get config(){return this.#config}get loglevel(){return this.#loglevel}}class ServerOptionsReader{static read(){const args=yargs(process.argv).argv;return DataConverter.convert(serverOptionsSchema,args)}}const Headers={CONTENT_TYPE:"Content-Type",CONTENT_TYPE_OPTIONS:"X-Content-Type-Options",FRAME_OPTIONS:"X-Frame-Options",LOCATION:"Location"};Object.freeze(Headers);const ContentTypes={BOOLEAN:"application/boolean",NUMBER:"application/number",JSON:"application/json",TEXT:"text/plain",HTML:"text/html"};Object.freeze(ContentTypes);class AssetsController{#repository;#indexFile;#serveIndexOnNotFound;#logger;constructor(app,repository,indexFile,serveIndexOnNotFound,logger){this.#repository=repository,this.#indexFile=indexFile,this.#serveIndexOnNotFound=serveIndexOnNotFound,this.#logger=logger,app.get("*",((request,response)=>{this.#getContent(request,response)}))}async#getContent(request,response){const path=request.path.substring(1).trim(),decodedPath=decodeURIComponent(path),filename=0===decodedPath.length?this.#indexFile:decodedPath;this.#loadContent(filename,response)}async#loadContent(filename,response){try{const file=await this.#repository.readAsset(filename);this.#logger.info(`Got asset -> '${filename}'`),file.type===ContentTypes.HTML&&response.setHeader(Headers.FRAME_OPTIONS,"DENY"),response.setHeader(Headers.CONTENT_TYPE,file.type),response.status(200).send(file.content)}catch(error){if(error instanceof FileNotFound)return this.#serveIndexOnNotFound&&filename!==this.#indexFile?this.#loadContent(this.#indexFile,response):(this.#logger.warn(`Failed to get asset -> '${filename}'`),void response.status(404).type("text").send("Not found"));const message=error instanceof Error?error.message:"Internal server error";this.#logger.error(`Failed to get asset -> ${message}`),response.status(500).type("text").send(message)}}}class HealthController{#worker;#logger;constructor(app,worker,logger){this.#worker=worker,this.#logger=logger,app.get("/health",((request,response)=>{this.getHealth(request,response)})),app.get("/health/status",((request,response)=>{this.isHealthy(request,response)}))}async getHealth(request,response){const health=await this.#worker.getHealth(),data=Object.fromEntries(health);return this.#logger.debug("Got health"),response.status(200).send(data)}async isHealthy(request,response){const healthy=await this.#worker.isHealthy();return this.#logger.debug("Got health status"),response.setHeader(Headers.CONTENT_TYPE,ContentTypes.TEXT),response.status(200).send(healthy)}}const filePath=fileURLToPath(import.meta.url),fileLocation=path.dirname(filePath);class JitarController{constructor(app){app.use("/jitar",express.static(fileLocation))}}const clientIdHelper=new ClientIdHelper;class ModulesController{#repository;#serializer;#logger;constructor(app,repository,serializer,logger){this.#repository=repository,this.#serializer=serializer,this.#logger=logger,app.post("/modules",((request,response)=>{this.registerClient(request,response)})),app.get("/modules/:clientId/*",((request,response)=>{this.getModule(request,response)}))}async registerClient(request,response){if(this.#logger.info("Register client"),request.body instanceof Array==!1)return response.status(400).send("Invalid segment file list.");const segmentFiles=request.body;for(const segmentFile of segmentFiles)if("string"!=typeof segmentFile)return response.status(400).send("Invalid segment file list.");const clientId=await this.#repository.registerClient(segmentFiles);return this.#logger.info(`Registered client -> ${clientId} [${segmentFiles.join(",")}]`),response.setHeader(Headers.CONTENT_TYPE,ContentTypes.TEXT),response.status(200).send(clientId)}async getModule(request,response){const clientId=request.params.clientId;if("string"!=typeof clientId||!1===clientIdHelper.validate(clientId))return response.status(400).send("Invalid client id.");this.#logger.info(`Get module for -> '${request.params.clientId}'`);const pathKey=`/${clientId}/`,pathIndex=request.path.indexOf(pathKey)+pathKey.length,filename=request.path.substring(pathIndex);try{const file=await this.#repository.readModule(filename,clientId);return this.#logger.info(`Got module -> '${filename}' (${clientId})`),response.setHeader(Headers.CONTENT_TYPE,file.type),response.status(200).send(file.content)}catch(error){const message=error instanceof Error?error.message:String(error);this.#logger.error(`Failed to get module -> '${filename}' (${clientId}) | ${message}`);const data=this.#serializer.serialize(error);return response.setHeader(Headers.CONTENT_TYPE,ContentTypes.JSON),response.status(500).send(data)}}}const workerDtoSchema=z.object({url:z.string().url(),procedureNames:z.array(z.string()).optional(),trustKey:z.string().optional()}).strict().transform((value=>new WorkerDto(value.url,value.procedureNames,value.trustKey)));class WorkerDto{url;procedureNames;trustKey;constructor(url,procedureNames=[],trustKey){this.url=url,this.procedureNames=procedureNames,this.trustKey=trustKey}}class WorkerController{#gateway;#logger;constructor(app,gateway,logger){this.#gateway=gateway,this.#logger=logger,app.get("/workers",((request,response)=>{this.getWorkers(request,response)})),app.post("/workers",((request,response)=>{this.addWorker(request,response)}))}async getWorkers(request,response){const workers=this.#gateway.workers.map((worker=>({url:worker.url,procedureNames:worker.getProcedureNames()})));return this.#logger.info("Got workers"),response.setHeader(Headers.CONTENT_TYPE,ContentTypes.JSON),response.status(200).send(workers)}async addWorker(request,response){try{const workerDto=DataConverter.convert(workerDtoSchema,request.body),worker=new RemoteWorker(workerDto.url);return worker.procedureNames=new Set(workerDto.procedureNames),await this.#gateway.addWorker(worker,workerDto.trustKey),this.#logger.info(`Added worker -> ${worker.url}`),response.status(201).send()}catch(error){if(error instanceof ConversionError){const message=error.message;return this.#logger.warn(`Failed to add worker | ${message}`),response.status(400).type("text").send(message)}const message=error instanceof Error?error.message:"Internal server error";return this.#logger.error(`Failed to add worker | ${message}`),response.status(500).type("text").send(message)}}}class ProceduresController{#runtime;#logger;constructor(app,runtime,logger){this.#runtime=runtime,this.#logger=logger,app.get("/procedures",((request,response)=>{this.getProcedureNames(request,response)}))}async getProcedureNames(request,response){const names=this.#runtime.getProcedureNames();return this.#logger.info("Got procedure names"),response.setHeader(Headers.CONTENT_TYPE,ContentTypes.JSON),response.status(200).send(names)}}class ProxyController{#logger;#repositoryUrl;#runnerUrl;constructor(app,proxy,logger){this.#logger=logger,this.#repositoryUrl=proxy.repository.url??"",this.#runnerUrl=proxy.runner.url??"",app.use("/",expressProxy((message=>this.#selectProxy(message))))}#selectProxy(message){const url=message.url??"";return this.#logger.info(`Forwarding -> ${url}`),url.startsWith("/rpc")?this.#runnerUrl:this.#repositoryUrl}}const RPC_PARAMETERS=["version","serialize"],IGNORED_HEADER_KEYS=["host","connection","content-length","accept-encoding","user-agent","keep-alive"],BAD_REQUEST_NAME=BadRequest.name,UNAUTHORIZED_NAME=Unauthorized.name,PAYMENT_REQUIRED_NAME=PaymentRequired.name,FORBIDDEN_NAME=Forbidden.name,NOT_FOUND_NAME=NotFound.name,TEAPOT_NAME=Teapot.name,NOT_IMPLEMENTED_NAME=NotImplemented.name;class RPCController{#runtime;#serializer;#logger;constructor(app,runtime,serializer,logger){this.#runtime=runtime,this.#serializer=serializer,this.#logger=logger,app.get("/rpc/*",((request,response)=>{this.runGet(request,response)})),app.post("/rpc/*",((request,response)=>{this.runPost(request,response)})),app.options("/rpc/*",((request,response)=>{this.runOptions(request,response)}))}async runGet(request,response){try{const fqn=this.#extractFqn(request),version=this.#extractVersion(request),args=this.#extractQueryArguments(request),headers=this.#extractHeaders(request),serialize=this.#extractSerialize(request);return this.#run(fqn,version,args,headers,response,serialize)}catch(error){const message=error instanceof Error?error.message:String(error);return this.#logger.warn(`Invalid request -> ${message}`),response.status(400).type("text").send(`Invalid request -> ${message}`)}}async runPost(request,response){try{const fqn=this.#extractFqn(request),version=this.#extractVersion(request),args=this.#extractBodyArguments(request),headers=this.#extractHeaders(request),serialize=this.#extractSerialize(request);return this.#run(fqn,version,args,headers,response,serialize)}catch(error){const message=error instanceof Error?error.message:String(error);return this.#logger.warn(`Invalid request -> ${message}`),response.status(400).type("text").send(`Invalid request -> ${message}`)}}async runOptions(request,response){return this.#setCors(response)}#extractFqn(request){const fqn=decodeURIComponent(request.path.trim()).substring(5).trim();if(0===fqn.length)throw new BadRequest("Missing procedure name");if(fqn.includes(".."))throw new BadRequest("Invalid procedure name");return fqn}#extractVersion(request){const version=void 0!==request.query.version?request.query.version:"";if("string"!=typeof version)throw new BadRequest("Invalid version number");return VersionParser.parse(version)}#extractSerialize(request){return"true"===request.query.serialize}#extractQueryArguments(request){const args={};for(const[key,value]of Object.entries(request.query))RPC_PARAMETERS.includes(key)||(args[key]=value);return args}#extractBodyArguments(request){return request.body}#extractHeaders(request){const headers=new Map;for(const[key,value]of Object.entries(request.headers)){if(void 0===value)continue;const lowerKey=key.toLowerCase(),stringValue=value.toString();IGNORED_HEADER_KEYS.includes(lowerKey)||headers.set(lowerKey,stringValue)}return headers}async#run(fqn,version,args,headers,response,serialize){if(!1===this.#runtime.hasProcedure(fqn))return response.setHeader(Headers.CONTENT_TYPE,ContentTypes.TEXT),response.status(404).send(`Procedure not found -> ${fqn}`);try{const deserializedArgs=await this.#serializer.deserialize(args),argsMap=new Map(Object.entries(deserializedArgs)),runtimeRequest=new Request(fqn,version,argsMap,headers),runtimeResponse=await this.#runtime.handle(runtimeRequest);return this.#logger.info(`Ran procedure -> ${fqn} (v${version.toString()})`),this.#setResponseHeaders(response,runtimeResponse.headers),this.#createResultResponse(runtimeResponse.result,response,serialize)}catch(error){const message=error instanceof Error?error.message:String(error),errorData=serialize?error:message;return this.#logger.error(`Failed to run procedure -> ${fqn} (v${version.toString()}) | ${message}`),this.#createErrorResponse(error,errorData,response,serialize)}}async#setCors(response){const cors=this.#runtime.getMiddleware(CorsMiddleware);return void 0===cors||(response.setHeader("Access-Control-Allow-Origin",cors.allowOrigin),response.setHeader("Access-Control-Allow-Methods",cors.allowMethods),response.setHeader("Access-Control-Allow-Headers",cors.allowHeaders),response.setHeader("Access-Control-Max-Age",86400)),response.status(204).send()}async#createResultResponse(result,response,serialize){const content=await this.#createResponseContent(result,serialize),contentType=this.#createResponseContentType(content),responseContent=contentType===ContentTypes.JSON?content:String(content),statusCode=this.#createResponseResultStatusCode(result,response);return response.setHeader(Headers.CONTENT_TYPE,contentType),response.status(statusCode).send(responseContent)}async#createErrorResponse(error,errorData,response,serialize){const content=await this.#createResponseContent(errorData,serialize),contentType=this.#createResponseContentType(content),statusCode=this.#createResponseErrorStatusCode(error);return response.setHeader(Headers.CONTENT_TYPE,contentType),response.status(statusCode).send(content)}async#createResponseContent(data,serialize){return serialize?this.#serializer.serialize(data):data}#createResponseContentType(content){switch(typeof content){case"boolean":return ContentTypes.BOOLEAN;case"number":return ContentTypes.NUMBER;case"object":return ContentTypes.JSON;default:return ContentTypes.TEXT}}#setResponseHeaders(response,headers){for(const[key,value]of headers.entries())void 0!==value&&(IGNORED_HEADER_KEYS.includes(key)||response.set(key,value))}#createResponseResultStatusCode(result,response){return response.hasHeader(Headers.LOCATION)?302:void 0===result?204:200}#createResponseErrorStatusCode(error){if(error instanceof Object==!1)return 500;const errorClass=error.constructor;return this.#isClassType(errorClass,BAD_REQUEST_NAME)?400:this.#isClassType(errorClass,UNAUTHORIZED_NAME)?401:this.#isClassType(errorClass,PAYMENT_REQUIRED_NAME)?402:this.#isClassType(errorClass,FORBIDDEN_NAME)?403:this.#isClassType(errorClass,NOT_FOUND_NAME)?404:this.#isClassType(errorClass,TEAPOT_NAME)?418:this.#isClassType(errorClass,NOT_IMPLEMENTED_NAME)?501:500}#isClassType(clazz,className){if(clazz.name===className)return!0;const parentClass=Object.getPrototypeOf(clazz);return""!==parentClass.name&&this.#isClassType(parentClass,className)}}class RuntimeNotAvailable extends Error{constructor(){super("Runtime is not available")}}class JitarServer{#app;#server;#runtime;#serializer;#classLoader;#options;#configuration;#logger;constructor(){this.#classLoader=new RemoteClassLoader,this.#serializer=SerializerBuilder.build(this.#classLoader),this.#app=express(),this.#app.use(express.json()),this.#app.use(express.urlencoded({extended:!0})),this.#app.use(((request,response,next)=>this.#addDefaultHeaders(request,response,next))),this.#app.disable("x-powered-by"),this.#options=ServerOptionsReader.read(),this.#configuration=RuntimeConfigurationLoader.load(this.#options.config),this.#logger=LogBuilder.build(this.#options.loglevel),this.#printStartupMessage()}get classLoader(){return this.#classLoader}async build(){this.#runtime=await RuntimeConfigurator.configure(this.#configuration),this.#addControllers()}async start(){const url=new URL(this.#configuration.url??RuntimeDefaults.URL);try{await this.#startApplication(),await this.#startServer(url.port)}catch(error){const message=error instanceof Error?error.message:String(error);return this.#logger.error(`Failed to start server -> ${message}`),void await this.stop()}this.#printProcedureInfo(),this.#logger.info(`Server started and listening at port ${url.port}`)}async stop(){try{await this.#stopServer(),await this.#stopApplication()}catch(error){const message=error instanceof Error?error.message:String(error);return void this.#logger.error(`Caught error while stopping server -> ${message}`)}this.#logger.info("Server stopped")}addSerializer(serializer){this.#serializer.addSerializer(serializer)}#getRuntime(){if(void 0===this.#runtime)throw new RuntimeNotAvailable;return this.#runtime}#addControllers(){if(void 0!==this.#configuration.standalone&&this.#runtime instanceof Standalone){const index=this.#configuration.standalone.index??RuntimeDefaults.INDEX,serveIndexOnNotFound=this.#configuration.standalone.serveIndexOnNotFound??RuntimeDefaults.SERVE_INDEX_ON_NOT_FOUND;this.#addStandAloneControllers(this.#runtime,index,serveIndexOnNotFound)}else if(void 0!==this.#configuration.repository&&this.#runtime instanceof LocalRepository){const index=this.#configuration.repository.index??RuntimeDefaults.INDEX,serveIndexOnNotFound=this.#configuration.repository.serveIndexOnNotFound??RuntimeDefaults.SERVE_INDEX_ON_NOT_FOUND;this.#addRepositoryControllers(this.#runtime,index,serveIndexOnNotFound)}else void 0!==this.#configuration.gateway&&this.#runtime instanceof LocalGateway?this.#addGatewayControllers(this.#runtime):void 0!==this.#configuration.worker&&this.#runtime instanceof LocalWorker?this.#addWorkerControllers(this.#runtime):void 0!==this.#configuration.proxy&&this.#runtime instanceof Proxy&&this.#addProxyControllers(this.#runtime)}#addStandAloneControllers(standalone,index,serveIndexOnNotFound){new HealthController(this.#app,standalone,this.#logger),new JitarController(this.#app),new ModulesController(this.#app,standalone,this.#serializer,this.#logger),new ProceduresController(this.#app,standalone,this.#logger),new RPCController(this.#app,standalone,this.#serializer,this.#logger),new AssetsController(this.#app,standalone,index,serveIndexOnNotFound,this.#logger)}#addRepositoryControllers(repository,index,serveIndexOnNotFound){new JitarController(this.#app),new ModulesController(this.#app,repository,this.#serializer,this.#logger),new AssetsController(this.#app,repository,index,serveIndexOnNotFound,this.#logger)}#addGatewayControllers(gateway){new WorkerController(this.#app,gateway,this.#logger),new ProceduresController(this.#app,gateway,this.#logger),new RPCController(this.#app,gateway,this.#serializer,this.#logger)}#addWorkerControllers(worker){new HealthController(this.#app,worker,this.#logger),new ProceduresController(this.#app,worker,this.#logger),new RPCController(this.#app,worker,this.#serializer,this.#logger)}#addProxyControllers(proxy){new ProxyController(this.#app,proxy,this.#logger)}async#startApplication(){const runtime=this.#getRuntime();await runtime.start();const setUpScripts=this.#configuration.setUp;if(void 0!==setUpScripts)for(const setUpScript of setUpScripts)await runtime.import(setUpScript,ExecutionScopes.APPLICATION)}async#stopApplication(){const runtime=this.#getRuntime();await runtime.stop();const tearDownScripts=this.#configuration.tearDown;if(void 0!==tearDownScripts)for(const tearDownScript of tearDownScripts)await runtime.import(tearDownScript,ExecutionScopes.APPLICATION)}#startServer(port){return void 0!==this.#server?Promise.resolve():new Promise(((resolve,reject)=>{this.#server=this.#app.listen(port,resolve),this.#server.on("error",reject)}))}#stopServer(){return void 0===this.#server?Promise.resolve():new Promise((resolve=>{this.#server?.close((()=>resolve()))}))}#printStartupMessage(){console.log("\n ██ ██ ████████ █████ ██████ \n ██ ██ ██ ██ ██ ██ ██ \n ██ ██ ██ ███████ ██████ \n ██ ██ ██ ██ ██ ██ ██ ██ \n █████ ██ ██ ██ ██ ██ ██\n ____________________________________\n By Masking Technology (masking.tech)\n")}#printProcedureInfo(){const runtime=this.#getRuntime();if(runtime instanceof LocalWorker==!1&&runtime instanceof Standalone==!1)return;const procedureNames=runtime.getProcedureNames();0!==procedureNames.length&&(procedureNames.sort(),this.#logger.info("Registered RPC entries",procedureNames))}#addDefaultHeaders(request,response,next){response.setHeader(Headers.CONTENT_TYPE_OPTIONS,"nosniff"),next()}}async function buildServer(moduleImporter){ModuleLoader.setImporter(moduleImporter);const server=new JitarServer;return await server.build(),server}export{CorsMiddleware,buildServer};
|
|
1
|
+
import{N as NoWorkerAvailable,G as Gateway,I as InvalidTrustKey,P as ProcedureNotFound,R as Repository,M as ModuleLoader,a as InvalidSegmentFile,F as FileNotFound,b as InvalidClientId,C as ClientNotFound,c as File,d as convertToRemoteFilename,i as isSegmentFilename,e as convertToLocalFilename,f as createRepositoryFilename,g as ProcedureRuntime,W as Worker,D as DummyRepository,h as Remote,j as RemoteRepository,k as RemoteGateway,l as RuntimeNotBuilt,L as LocalWorker,m as Reflector,A as AccessLevels,n as ReflectionField,o as ReflectionDestructuredArray,p as ReflectionDestructuredObject,q as ReflectionDestructuredValue,r as createWorkerFilename,V as VersionParser,s as ReflectionFunction,t as Files,B as BadRequest,U as Unauthorized,u as PaymentRequired,v as Forbidden,w as NotFound,T as Teapot,x as NotImplemented,y as Request,z as RemoteClassLoader,S as SerializerBuilder,E as ExecutionScopes}from"./globals-B8DxiuDw.js";import express from"express";import{readFileSync}from"fs";import{z}from"zod";import fs from"fs-extra";import glob from"glob-promise";import mime from"mime-types";import path from"path";import yargs from"yargs";import{Logger}from"tslog";import{fileURLToPath}from"url";import expressProxy from"express-http-proxy";class CorsMiddleware{#allowOrigin;#allowMethods="GET, POST";#allowHeaders;constructor(origin="*",headers="*"){this.#allowOrigin=origin,this.#allowHeaders=headers}get allowOrigin(){return this.#allowOrigin}get allowMethods(){return this.#allowMethods}get allowHeaders(){return this.#allowHeaders}async handle(request,next){const response=await next();return this.#setHeaders(response),response}#setHeaders(response){response.setHeader("Access-Control-Allow-Origin",this.#allowOrigin),response.setHeader("Access-Control-Allow-Methods",this.#allowMethods),response.setHeader("Access-Control-Allow-Headers",this.#allowHeaders)}}class WorkerBalancer{#workers=[];#currentIndex=0;addWorker(worker){this.#workers.includes(worker)||this.#workers.push(worker)}removeWorker(worker){const index=this.#workers.indexOf(worker);-1!==index&&this.#workers.splice(index,1)}getNextWorker(){if(0!==this.#workers.length)return this.#currentIndex>=this.#workers.length&&(this.#currentIndex=0),this.#workers[this.#currentIndex++]}run(request){const worker=this.getNextWorker();if(void 0===worker)throw new NoWorkerAvailable(request.fqn);return worker.run(request)}}class LocalGateway extends Gateway{#workers=new Set;#balancers=new Map;#trustKey;constructor(repository,url,trustKey){super(repository,url),this.#trustKey=trustKey}get workers(){return[...this.#workers.values()]}getProcedureNames(){const procedureNames=this.workers.map((worker=>worker.getProcedureNames()));return[...new Set(procedureNames.flat()).values()]}hasProcedure(fqn){return this.getProcedureNames().includes(fqn)}async addWorker(worker,trustKey){if(void 0!==trustKey&&this.#trustKey!==trustKey)throw new InvalidTrustKey;this.#workers.add(worker);for(const name of worker.getProcedureNames()){this.#getOrCreateBalancer(name).addWorker(worker)}}removeWorker(worker){this.#workers.delete(worker);for(const name of worker.getProcedureNames()){const balancer=this.#getBalancer(name);void 0!==balancer&&balancer.removeWorker(worker)}}#getBalancer(fqn){return this.#balancers.get(fqn)}#getOrCreateBalancer(fqn){let balancer=this.#getBalancer(fqn);return void 0===balancer&&(balancer=new WorkerBalancer,this.#balancers.set(fqn,balancer)),balancer}run(request){const balancer=this.#getBalancer(request.fqn);if(void 0===balancer)throw new ProcedureNotFound(request.fqn);return balancer.run(request)}}const CLIENT_ID_REGEX=/^CLIENT_\d+$/;let lastClientId=0;class ClientIdHelper{generate(){return"CLIENT_"+lastClientId++}validate(clientId){return CLIENT_ID_REGEX.test(clientId)}}const clientIdHelper$1=new ClientIdHelper;class LocalRepository extends Repository{#fileManager;#segments=new Map;#clients=new Map;#segmentNames=new Set;#assets=new Set;#overrides=new Map;constructor(fileManager,url){super(url),this.#fileManager=fileManager,ModuleLoader.setBaseUrl(fileManager.getRootLocation())}set segmentNames(names){this.#segmentNames=new Set(names)}set assets(patterns){this.#assets=patterns}set overrides(overrides){this.#overrides=overrides}async start(){await super.start(),await this.#loadSegments(),this.#translateOverrides()}stop(){return this.#unregisterClients(),this.#unloadSegments(),super.stop()}async#loadSegments(){for(const name of this.#segmentNames)await this.#loadSegment(name)}async#loadSegment(name){const relativeFilename=`./${createRepositoryFilename(name)}`,absoluteFilename=this.#fileManager.getAbsoluteLocation(relativeFilename),files=(await ModuleLoader.load(absoluteFilename)).files;if(void 0===files)throw new InvalidSegmentFile(absoluteFilename);this.registerSegment(name,files)}#unloadSegments(){this.#segments.clear()}async registerSegment(name,filenames){filenames.forEach((filename=>this.#segments.set(filename,name)))}#translateOverrides(){const translated=new Map;for(const[targetName,destinationName]of this.#overrides){const relativeTargetFilename=ModuleLoader.assureExtension(targetName),relativeDestinationFilename=ModuleLoader.assureExtension(destinationName),absoluteTargetFilename=this.#fileManager.getAbsoluteLocation(relativeTargetFilename),absoluteDestinationFilename=this.#fileManager.getAbsoluteLocation(relativeDestinationFilename);translated.set(absoluteTargetFilename,absoluteDestinationFilename)}this.#overrides=translated}async registerClient(segmentFilenames){const clientId=clientIdHelper$1.generate();return this.#clients.set(clientId,segmentFilenames),clientId}#unregisterClients(){this.#clients.clear()}readAsset(filename){if(!1===this.#assets.has(filename))throw new FileNotFound(filename);return this.#readFile(filename)}readModule(name,clientId){clientId=this.#validateClientId(clientId);const segmentFilename=this.#segments.get(name);return void 0===segmentFilename||this.#hasClientSegmentFile(clientId,segmentFilename)?this.#readWorkerModule(name):this.#readRemoteModule(name)}loadModule(name){const filename=this.#getModuleFilename(name);return ModuleLoader.load(filename)}#validateClientId(clientId){if(!1===clientIdHelper$1.validate(clientId))throw new InvalidClientId(clientId);if(!1===this.#clients.has(clientId))throw new ClientNotFound(clientId);return clientId}#hasClientSegmentFile(clientId,segmentFilename){const clientSegmentFiles=this.#clients.get(clientId);if(void 0===clientSegmentFiles)throw new ClientNotFound(clientId);return clientSegmentFiles.some((clientSegmentFilename=>segmentFilename.endsWith(clientSegmentFilename)))}async#readWorkerModule(name){const filename=this.#getModuleFilename(name),code=(await this.#readFile(filename)).content.toString();return new File(filename,"application/javascript",code)}#readRemoteModule(name){const remoteFilename=convertToRemoteFilename(name);return this.#readFile(remoteFilename)}#getModuleFilename(name){const relativeFilename=ModuleLoader.assureExtension(name),absoluteFilename=this.#fileManager.getAbsoluteLocation(relativeFilename);if(isSegmentFilename(absoluteFilename))return absoluteFilename;const assignedFilename=this.#getAssignedFilename(absoluteFilename);return convertToLocalFilename(assignedFilename)}#getAssignedFilename(filename){return this.#overrides.get(filename)??filename}#readFile(filename){return this.#fileManager.read(filename)}}class Proxy extends ProcedureRuntime{#runner;constructor(repository,runner,url){super(repository,url),this.#runner=runner}get runner(){return this.#runner}async start(){await super.start(),await this.#runner.start()}async stop(){await this.#runner.stop(),await super.stop()}getProcedureNames(){return this.#runner.getProcedureNames()}hasProcedure(fqn){return this.getProcedureNames().includes(fqn)}readAsset(filename){return this.repository.readAsset(filename)}registerClient(segmentFiles){return this.repository.registerClient(segmentFiles)}readModule(filename,clientId){return this.repository.readModule(filename,clientId)}run(request){return this.#runner.run(request)}}class RemoteWorker extends Worker{#remote;#procedureNames=new Set;constructor(url){super(new DummyRepository,url),this.#remote=new Remote(url)}get trustKey(){}set procedureNames(names){this.#procedureNames=names}getProcedureNames(){return[...this.#procedureNames.values()]}hasProcedure(name){return this.#procedureNames.has(name)}isHealthy(){return this.#remote.isHealthy()}getHealth(){return this.#remote.getHealth()}run(request){return this.#remote.run(request)}}class Standalone extends ProcedureRuntime{#worker;constructor(repository,worker,url){super(repository,url),this.#worker=worker}get worker(){return this.#worker}async start(){await this.#worker.start(),await super.start()}async stop(){await this.#worker.stop(),await super.stop()}getProcedureNames(){return this.#worker.getProcedureNames()}hasProcedure(fqn){return this.getProcedureNames().includes(fqn)}readAsset(filename){return this.repository.readAsset(filename)}registerClient(segmentFiles){return this.repository.registerClient(segmentFiles)}readModule(filename,clientId){return this.repository.readModule(filename,clientId)}run(request){return this.#worker.run(request)}}class RuntimeBuilder{#url;#fileManager;#segments=new Set;#healthChecks=new Set;#middlewares=new Set;#assets=new Set;#overrides=new Map;#repository;#gateway;#worker;url(url){return this.#url=url,this}fileManager(fileManager){return this.#fileManager=fileManager,this}segment(...names){return names.forEach((name=>this.#segments.add(name))),this}healthCheck(...filenames){return filenames.forEach((filename=>this.#healthChecks.add(filename))),this}middleware(...filenames){return filenames.forEach((filename=>this.#middlewares.add(filename))),this}asset(...patterns){return patterns.forEach((pattern=>this.#assets.add(pattern))),this}override(...mappings){for(const map of mappings)for(const[key,value]of Object.entries(map))this.#overrides.set(key,value);return this}repository(url){return this.#repository=void 0!==url?new RemoteRepository(url):void 0,this}gateway(url){return this.#gateway=void 0!==url?new RemoteGateway(url):void 0,this}worker(url){return this.#worker=void 0!==url?new RemoteWorker(url):void 0,this}buildRepository(){if(void 0===this.#fileManager)throw new RuntimeNotBuilt("File manager is not set for the repository");const repository=new LocalRepository(this.#fileManager,this.#url);return repository.healthCheckFiles=this.#healthChecks,repository.segmentNames=this.#segments,repository.assets=this.#assets,repository.overrides=this.#overrides,repository}buildGateway(trustKey){if(void 0===this.#repository)throw new RuntimeNotBuilt("Repository is not set for the gateway");const gateway=new LocalGateway(this.#repository,this.#url,trustKey);return gateway.healthCheckFiles=this.#healthChecks,gateway.middlewareFiles=this.#middlewares,gateway}buildWorker(trustKey){if(void 0===this.#repository)throw new RuntimeNotBuilt("Repository is not set for the worker");const worker=new LocalWorker(this.#repository,this.#gateway,this.#url,trustKey);return worker.segmentNames=this.#segments,worker.healthCheckFiles=this.#healthChecks,worker.middlewareFiles=this.#middlewares,this.#repository.segmentNames=this.#segments,void 0!==this.gateway&&(this.#gateway.worker=worker),worker}buildProxy(){if(void 0===this.#repository)throw new RuntimeNotBuilt("Repository is not set for the proxy");const runner=this.#gateway??this.#worker;if(void 0===runner)throw new RuntimeNotBuilt("Runner (gateway or worker) is not set for the proxy");const proxy=new Proxy(this.#repository,runner,this.#url);return proxy.healthCheckFiles=this.#healthChecks,proxy.middlewareFiles=this.#middlewares,proxy}buildStandalone(trustKey){if(void 0===this.#fileManager)throw new RuntimeNotBuilt("File manager is not set for the standalone");const repository=new LocalRepository(this.#fileManager,this.#url);repository.segmentNames=this.#segments,repository.assets=this.#assets,repository.overrides=this.#overrides;const worker=new LocalWorker(repository,this.#gateway,this.#url,trustKey);worker.segmentNames=this.#segments;const standalone=new Standalone(repository,worker,this.#url);return standalone.healthCheckFiles=this.#healthChecks,standalone.middlewareFiles=this.#middlewares,standalone}}class ProcedureRuntimeConfiguration{#middlewares;constructor(middlewares){this.#middlewares=middlewares}get middlewares(){return this.#middlewares}}const gatewaySchema=z.object({repository:z.string().url(),middlewares:z.array(z.string()).optional(),monitor:z.number().optional(),trustKey:z.string().optional()}).strict().transform((value=>new GatewayConfiguration(value.repository,value.middlewares,value.monitor,value.trustKey)));class GatewayConfiguration extends ProcedureRuntimeConfiguration{#monitor;#repository;#trustKey;constructor(repository,middlewares,monitor,trustKey){super(middlewares),this.#monitor=monitor,this.#repository=repository,this.#trustKey=trustKey}get monitor(){return this.#monitor}get repository(){return this.#repository}get trustKey(){return this.#trustKey}}const workerSchema=z.object({gateway:z.string().url().optional(),repository:z.string().url().optional(),segments:z.array(z.string()).nonempty(),middlewares:z.array(z.string()).optional(),trustKey:z.string().optional()}).strict().transform((value=>new WorkerConfiguration(value.gateway,value.repository,value.segments,value.middlewares,value.trustKey)));class WorkerConfiguration extends ProcedureRuntimeConfiguration{#gateway;#repository;#segments;#trustKey;constructor(gateway,repository,segments,middlewares,trustKey){super(middlewares),this.#gateway=gateway,this.#repository=repository,this.#segments=segments,this.#trustKey=trustKey}get gateway(){return this.#gateway}get repository(){return this.#repository}get segments(){return this.#segments}get trustKey(){return this.#trustKey}}const proxySchema=z.object({worker:z.string().url().optional(),gateway:z.string().url().optional(),repository:z.string().url(),middlewares:z.array(z.string()).optional()}).strict().superRefine(((value,ctx)=>{void 0===value.worker&&void 0===value.gateway&&ctx.addIssue({code:z.ZodIssueCode.custom,message:"Either worker or gateway must be defined",path:["worker","gateway"]}),void 0!==value.worker&&void 0!==value.gateway&&ctx.addIssue({code:z.ZodIssueCode.custom,message:"Only worker or gateway must be defined",path:["worker","gateway"]})})).transform((value=>new ProxyConfiguration(value.worker,value.gateway,value.repository,value.middlewares)));class ProxyConfiguration extends ProcedureRuntimeConfiguration{#worker;#gateway;#repository;constructor(worker,gateway,repository,middlewares){super(middlewares),this.#worker=worker,this.#gateway=gateway,this.#repository=repository}get worker(){return this.#worker}get gateway(){return this.#gateway}get repository(){return this.#repository}}const repositorySchema=z.object({source:z.string().optional(),cache:z.string().optional(),index:z.string().optional(),serveIndexOnNotFound:z.boolean().optional(),assets:z.array(z.string()).optional(),overrides:z.record(z.string(),z.string()).optional()}).strict().transform((value=>new RepositoryConfiguration(value.source,value.cache,value.index,value.serveIndexOnNotFound,value.assets,value.overrides)));class RepositoryConfiguration{#source;#cache;#index;#serveIndexOnNotFound;#assets;#overrides;constructor(source,cache,index,serveIndexOnNotFound,assets,overrides){this.#source=source,this.#cache=cache,this.#index=index,this.#serveIndexOnNotFound=serveIndexOnNotFound,this.#assets=assets,this.#overrides=overrides}get source(){return this.#source}get cache(){return this.#cache}get index(){return this.#index}get serveIndexOnNotFound(){return this.#serveIndexOnNotFound}get assets(){return this.#assets}get overrides(){return this.#overrides}}const standaloneSchema=z.object({source:z.string().optional(),cache:z.string().optional(),index:z.string().optional(),serveIndexOnNotFound:z.boolean().optional(),segments:z.array(z.string()).optional(),assets:z.array(z.string()).optional(),middlewares:z.array(z.string()).optional(),overrides:z.record(z.string(),z.string()).optional(),trustKey:z.string().optional()}).strict().transform((value=>new StandaloneConfiguration(value.source,value.cache,value.index,value.serveIndexOnNotFound,value.segments,value.assets,value.middlewares,value.overrides,value.trustKey)));class StandaloneConfiguration extends ProcedureRuntimeConfiguration{#source;#cache;#index;#serveIndexOnNotFound;#segments;#assets;#overrides;#trustKey;constructor(source,cache,index,serveIndexOnNotFound,segments,assets,middlewares,overrides,trustKey){super(middlewares),this.#source=source,this.#cache=cache,this.#index=index,this.#serveIndexOnNotFound=serveIndexOnNotFound,this.#segments=segments,this.#assets=assets,this.#overrides=overrides,this.#trustKey=trustKey}get source(){return this.#source}get cache(){return this.#cache}get index(){return this.#index}get serveIndexOnNotFound(){return this.#serveIndexOnNotFound}get segments(){return this.#segments}get assets(){return this.#assets}get overrides(){return this.#overrides}get trustKey(){return this.#trustKey}}const runtimeSchema=z.object({url:z.string().optional(),setUp:z.array(z.string()).optional(),tearDown:z.array(z.string()).optional(),healthChecks:z.array(z.string()).optional(),standalone:standaloneSchema.optional(),repository:repositorySchema.optional(),gateway:gatewaySchema.optional(),worker:workerSchema.optional(),proxy:proxySchema.optional()}).strict().transform((value=>new RuntimeConfiguration(value.url,value.setUp,value.tearDown,value.healthChecks,value.standalone,value.repository,value.gateway,value.worker,value.proxy)));class RuntimeConfiguration{#url;#setUp;#tearDown;#healthChecks;#standalone;#repository;#gateway;#worker;#proxy;constructor(url,setUp,tearDown,healthChecks,standalone,repository,gateway,worker,proxy){this.#url=url,this.#setUp=setUp,this.#tearDown=tearDown,this.#healthChecks=healthChecks,this.#standalone=standalone,this.#repository=repository,this.#gateway=gateway,this.#worker=worker,this.#proxy=proxy}get url(){return this.#url}get setUp(){return this.#setUp}get tearDown(){return this.#tearDown}get healthChecks(){return this.#healthChecks}get standalone(){return this.#standalone}get repository(){return this.#repository}get gateway(){return this.#gateway}get worker(){return this.#worker}get proxy(){return this.#proxy}}class EnvironmentVariableNotFound extends Error{constructor(name){super(`The environment variable '${name}' is not found`)}}class ConversionError extends Error{constructor(message){super(message)}}class DataConverter{static convert(schema,dataObject){try{return schema.parse(dataObject)}catch(error){const errors=error.errors,message=JSON.stringify(errors);throw new ConversionError(message)}}}const ENVIRONMENT_VARIABLE_REGEX=/\${([^}]*)}/g;class RuntimeConfigurationLoader{static load(filename){const plainContents=readFileSync(filename,"utf-8"),replacedContents=this.#replaceEnvironmentVariables(plainContents),parsedContents=JSON.parse(replacedContents);return DataConverter.convert(runtimeSchema,parsedContents)}static#replaceEnvironmentVariables(contents){return contents.replace(ENVIRONMENT_VARIABLE_REGEX,((match,group)=>{const value=process.env[group];if(void 0===value)throw new EnvironmentVariableNotFound(group);return value}))}}class ApplicationCache{#segments;#modules;constructor(segments,modules){this.#segments=segments,this.#modules=modules}get segments(){return this.#segments}get modules(){return this.#modules}}class ModuleCache{#module;#segment;constructor(module,segment){this.#module=module,this.#segment=segment}get module(){return this.#module}get segment(){return this.#segment}}class ModuleCacheBuilder{build(application,module){const segment=application.getSegmentModule(module.filename);return new ModuleCache(module,segment)}}class DuplicateImplementation extends Error{constructor(fqn,version){super(`Duplicate implementation found for '${fqn}' with version '${version}'.`)}}class SegmentCache{#name;#imports;#procedures;#files;constructor(name,files,imports,procedures){this.#name=name,this.#files=files,this.#imports=imports,this.#procedures=procedures}get name(){return this.#name}get files(){return this.#files}get imports(){return this.#imports}get procedures(){return this.#procedures}}class SegmentImport{#members;#from;constructor(members,from){this.#members=members,this.#from=from}get members(){return this.#members}get from(){return this.#from}}class SegmentProcedure{#fqn;#implementations=[];constructor(fqn,implementations=[]){this.#fqn=fqn,this.#implementations=implementations}get fqn(){return this.#fqn}get implementations(){return this.#implementations}addImplementation(implementation){this.#implementations.push(implementation)}}class SegmentCacheBuilder{build(segment){const files=this.#extractFiles(segment),imports=this.#createImports(segment),procedures=this.#mergeProcedures(segment);return this.#validateProcedures(procedures),new SegmentCache(segment.name,files,imports,procedures)}#extractFiles(segment){return segment.modules.map((module=>module.filename))}#createImports(segment){const imports=[];for(const module of segment.modules){let members=[];for(const procedure of module.procedures){const ids=procedure.implementations.map((implementation=>this.#createImportMember(implementation)));members=[...members,...ids]}imports.push(new SegmentImport(members,module.filename))}return imports}#createImportMember(implementation){return`${implementation.importKey} : ${implementation.id}`}#mergeProcedures(segment){const procedures=new Map;for(const module of segment.modules)for(const procedure of module.procedures){if(procedures.has(procedure.fqn)){const existingProcedure=procedures.get(procedure.fqn);for(const implementation of procedure.implementations)existingProcedure.addImplementation(implementation);continue}const procedureCopy=new SegmentProcedure(procedure.fqn,[...procedure.implementations]);procedures.set(procedure.fqn,procedureCopy)}return[...procedures.values()]}#validateProcedures(procedures){for(const procedure of procedures)this.#checkForDuplicateImplementations(procedure)}#checkForDuplicateImplementations(procedure){for(const implementation of procedure.implementations){if(void 0!==procedure.implementations.find((other=>other.id!==implementation.id&&other.version===implementation.version)))throw new DuplicateImplementation(procedure.fqn,implementation.version)}}}class ApplicationCacheBuilder{#moduleCacheBuilder;#segmentCacheBuilder;constructor(){this.#moduleCacheBuilder=new ModuleCacheBuilder,this.#segmentCacheBuilder=new SegmentCacheBuilder}build(application){const segments=this.#buildSegmentCaches(application),modules=this.#buildModuleCaches(application);return new ApplicationCache(segments,modules)}#buildSegmentCaches(application){return application.segments.map((segment=>this.#segmentCacheBuilder.build(segment)))}#buildModuleCaches(application){return application.modules.map((module=>this.#moduleCacheBuilder.build(application,module)))}}const Keyword={DEFAULT:"default"};Object.freeze(Keyword);const IMPORT_PATTERN=/import\s(?:["'\s]*([\w*{}\n, ]+)from\s*)?["'\s]*([@\w/._-]+)["'\s].*/g,APPLICATION_MODULE_INDICATORS=[".","/","http:","https:"],reflector$2=new Reflector;const importRewriter=new class{rewrite(code,filename){return code.replaceAll(IMPORT_PATTERN,(statement=>this.#replaceImport(statement,filename)))}#replaceImport(statement,filename){const dependency=reflector$2.parseImport(statement);return this.#isApplicationModule(dependency)?this.#rewriteApplicationImport(dependency,filename):this.#rewriteRuntimeImport(dependency,filename)}#isApplicationModule(dependency){return APPLICATION_MODULE_INDICATORS.some((indicator=>dependency.from.startsWith(indicator,1)))}#rewriteApplicationImport(dependency,filename){return this.#rewriteImport(dependency,"application",filename)}#rewriteRuntimeImport(dependency,filename){return this.#rewriteImport(dependency,"runtime",filename)}#rewriteImport(dependency,scope,filename){const from=this.#rewriteImportFrom(dependency,filename);if(0===dependency.members.length)return`await __import("${from}", "${scope}");`;return`const ${this.#rewriteImportMembers(dependency)} = await __import("${from}", "${scope}", ${this.#mustUseAs(dependency)?"true":"false"});`}#rewriteImportFrom(dependency,filename){const from=dependency.from.substring(1,dependency.from.length-1);return this.#isApplicationModule(dependency)?this.#mergeFilenames(filename,from):from}#rewriteImportMembers(dependency){if(this.#mustUseAs(dependency))return dependency.members[0].as;return`{ ${dependency.members.map((member=>member.name!==member.as?`${member.name} : ${member.as}`:member.name)).join(", ")} }`}#mergeFilenames(sourceFilename,importFilename){const concatenated=`${this.#extractFilepath(sourceFilename)}/${importFilename}`,translated=this.#translateFilename(concatenated),rooted=this.#ensureRoot(translated);return this.#ensureExtension(rooted)}#extractFilepath(filename){return filename.split("/").slice(0,-1).join("/")}#translateFilename(filename){const parts=filename.split("/"),translated=[];translated.push(parts[0]);for(let index=1;index<parts.length;index++){const part=parts[index].trim();switch(part){case"":case".":continue;case"..":translated.pop();continue}translated.push(part)}return translated.join("/")}#ensureRoot(filename){return filename.startsWith("./")?filename:(filename.startsWith("//")&&(filename=filename.substring(1)),filename.startsWith("/")?`.${filename}`:`./${filename}`)}#ensureExtension(filename){return filename.endsWith(".js")?filename:`${filename}.js`}#mustUseAs(dependency){return this.#doesImportAll(dependency)||this.#doesImportDefault(dependency)}#doesImportAll(dependency){return 1===dependency.members.length&&"*"===dependency.members[0].name}#doesImportDefault(dependency){return 1===dependency.members.length&&dependency.members[0].name===Keyword.DEFAULT}},remoteBuilder=new class{build(module){let code="";for(const procedure of module.procedures)for(const implementation of procedure.implementations){const asDefault=implementation.importKey===Keyword.DEFAULT;code+=implementation.access===AccessLevels.PRIVATE?this.#createPrivateCode(procedure.fqn,implementation,asDefault):this.#createPublicCode(procedure.fqn,implementation,asDefault)}return code.trim()}#createPrivateCode(fqn,implementation,asDefault){const version=implementation.version,declaration=this.#createDeclaration(implementation,asDefault),body=`throw new ProcedureNotAccessible('${fqn}', '${version}');`;return this.#createFunction(declaration,body)}#createPublicCode(fqn,implementation,asDefault){const version=implementation.version,args=this.#createArguments(implementation.executable.parameters),declaration=this.#createDeclaration(implementation,asDefault),body=`return __run('${fqn}', '${version}', { ${args} }, this);`;return this.#createFunction(declaration,body)}#createParameters(parameters){const result=[];for(const parameter of parameters)parameter instanceof ReflectionField?result.push(parameter.name):(parameter instanceof ReflectionDestructuredArray||parameter instanceof ReflectionDestructuredObject)&&result.push(parameter.toString());return result.join(", ")}#createArguments(parameters){return this.#extractArguments(parameters).join(", ")}#extractArguments(parameters){const result=[];for(const parameter of parameters)if(parameter instanceof ReflectionDestructuredValue){const argumentz=this.#extractArguments(parameter.members);result.push(...argumentz)}else if(parameter instanceof ReflectionField){const argument=this.#createNamedArgument(parameter);result.push(argument)}return result}#createNamedArgument(parameter){const key=parameter.name,value=key.startsWith("...")?key.substring(3):key;return`'${key}': ${value}`}#createDeclaration(implementation,asDefault){const name=implementation.executable.name,parameters=this.#createParameters(implementation.executable.parameters);return`\nexport ${asDefault?`${Keyword.DEFAULT} `:""}async function ${name}(${parameters})`}#createFunction(declaration,body){return`${declaration} {\n\t${body}\n}\n`}};class ModuleCacheWriter{#fileManager;constructor(fileManager){this.#fileManager=fileManager}async write(cache){return Promise.all([this.#writeLocal(cache),this.#writeRemote(cache)]).then((()=>{}))}async#writeLocal(cache){const importCode=this.#rewriteAllImports(cache.module),sourceCode=this.#createSourceCode(cache.module),filename=convertToLocalFilename(cache.module.filename),code=`${importCode}\n${sourceCode}`;return this.#fileManager.write(filename,code.trim())}#rewriteAllImports(module){return importRewriter.rewrite(module.code,module.filename)}#createSourceCode(module){const filename=module.filename;return module.content.exportedClasses.map((clazz=>clazz.name)).map((className=>`${className}.source = "./${filename}";`)).join("\n")}async#writeRemote(cache){if(void 0===cache.segment)return;const filename=convertToRemoteFilename(cache.module.filename),code=this.#createRemoteCode(cache.segment);return this.#fileManager.write(filename,code.trim())}#createRemoteCode(module){return remoteBuilder.build(module)}}class SegmentCacheWriter{#fileManager;constructor(fileManager){this.#fileManager=fileManager}async write(cache){return Promise.all([this.#writeWorkerCache(cache),this.#writeRepositoryCache(cache)]).then((()=>{}))}async#writeWorkerCache(cache){const importCode=this.#createImportCode(cache.imports),segmentCode=this.#createSegmentCode(cache.name,cache.procedures),filename=createWorkerFilename(cache.name),code=`${importCode}\n${segmentCode}`;return this.#fileManager.write(filename,code)}async#writeRepositoryCache(cache){const filename=createRepositoryFilename(cache.name),code=`export const files = [\n\t"${[...cache.files].join('",\n\t"')}"\n];`;return this.#fileManager.write(filename,code)}#createImportCode(imports){const codes=[];for(const{members:members,from:from}of imports)codes.push(`const { ${members.join(", ")} } = await __import("./${from}", "application", false);`);return codes.join("\n")}#createSegmentCode(name,procedures){const codes=[];codes.push('const { Segment, Procedure, Implementation, Version, NamedParameter, ArrayParameter, ObjectParameter } = await __import("jitar", "runtime", false);'),codes.push(`export const segment = new Segment("${name}")`);for(const procedure of procedures){codes.push(`\t.addProcedure(new Procedure("${procedure.fqn}")`);for(const implementation of procedure.implementations){const version=this.#createVersionCode(implementation.version),parameters=this.#createParametersCode(implementation.executable);codes.push(`\t\t.addImplementation(new Implementation(${version}, "${implementation.access}", ${parameters}, ${implementation.id}))`)}codes.push("\t)")}return codes.join("\n")}#createVersionCode(versionString){const version=VersionParser.parse(versionString);return`new Version(${version.major}, ${version.minor}, ${version.patch})`}#createParametersCode(executable){return`[${this.#extractParameters(executable.parameters).join(", ")}]`}#extractParameters(parameters){const result=[];for(const parameter of parameters)result.push(this.#extractParameter(parameter));return result}#extractParameter(parameter){return parameter instanceof ReflectionDestructuredArray?this.#createArrayParameter(parameter):parameter instanceof ReflectionDestructuredObject?this.#createObjectParameter(parameter):this.#createNamedParameter(parameter)}#createNamedParameter(parameter){return`new NamedParameter("${parameter.name}", ${void 0!==parameter.value})`}#createArrayParameter(parameter){return`new ArrayParameter([${this.#extractParameters(parameter.members).join(", ")}])`}#createObjectParameter(parameter){return`new ObjectParameter([${this.#extractParameters(parameter.members).join(", ")}])`}}class ApplicationCacheWriter{#moduleWriter;#segmentWriter;constructor(fileManager){this.#moduleWriter=new ModuleCacheWriter(fileManager),this.#segmentWriter=new SegmentCacheWriter(fileManager)}async write(cache){return Promise.all([this.#writeSegmentCache(cache.segments),this.#writeModuleCache(cache.modules)]).then((()=>{}))}async#writeSegmentCache(segments){return Promise.all(segments.map((async segment=>this.#segmentWriter.write(segment)))).then((()=>{}))}async#writeModuleCache(modules){return Promise.all(modules.map((async module=>this.#moduleWriter.write(module)))).then((()=>{}))}}class Application{#segments;#modules;constructor(segments,modules){this.#segments=segments,this.#modules=modules}get segments(){return this.#segments}get modules(){return this.#modules}getSegmentModule(filename){const segment=this.#segments.find((segment=>segment.hasModule(filename)));if(void 0!==segment)return segment.getModule(filename)}}class ModuleFileNotLoaded extends Error{constructor(filename,message){super(`Failed to load module file '${filename}' because of: ${message}`)}}class Module{#filename;#code;#content;constructor(filename,code,content){this.#code=code,this.#filename=filename,this.#content=content}get filename(){return this.#filename}get code(){return this.#code}get content(){return this.#content}}const reflector$1=new Reflector;class ModuleReader{#fileManager;constructor(fileManager){this.#fileManager=fileManager}async read(filename){const relativeLocation=this.#fileManager.getRelativeLocation(filename),code=await this.#loadCode(filename),module=reflector$1.parse(code);return new Module(relativeLocation,code,module)}async#loadCode(filename){try{return(await this.#fileManager.getContent(filename)).toString()}catch(error){const message=error instanceof Error?error.message:String(error);throw new ModuleFileNotLoaded(filename,message)}}}class FunctionNotAsync extends Error{constructor(filename,functionName){super(`Function '${functionName}' from file '${filename}' is not async`)}}class InvalidSegmentFilename extends Error{constructor(filename){super(`Segment filename '${filename}' is invalid`)}}class MissingModuleExport extends Error{constructor(filename,key){super(`Module '${filename}' does not export '${key}'`)}}class SegmentFileNotLoaded extends Error{constructor(filename,message){super(`Failed to load segment file '${filename}' because of: ${message}`)}}class SegmentModuleNotLoaded extends Error{constructor(filename,message){super(`Segment module could not be loaded from '${filename}' because of: ${message}`)}}class Segment{#name;#modules;constructor(name,modules){this.#name=name,this.#modules=modules}get name(){return this.#name}get modules(){return this.#modules}hasModule(filename){return this.#modules.some((module=>module.filename===filename))}getModule(filename){return this.#modules.find((module=>module.filename===filename))}}class SegmentImplementation{#id;#importKey;#access;#version;#executable;constructor(id,importKey,access,version,executable){this.#id=id,this.#importKey=importKey,this.#access=access,this.#version=version,this.#executable=executable}get id(){return this.#id}get importKey(){return this.#importKey}get access(){return this.#access}get version(){return this.#version}get executable(){return this.#executable}}class SegmentModule{#filename;#procedures=[];constructor(filename,procedures){this.#filename=filename,this.#procedures=procedures}get filename(){return this.#filename}get procedures(){return this.#procedures}}class IdGenerator{#id=0;next(){return"$"+ ++this.#id}}const reflector=new Reflector;class SegmentReader{#fileManager;constructor(fileManager){this.#fileManager=fileManager}async read(filename){const name=this.#extractSegmentName(filename),definition=await this.#loadSegmentDefinition(filename),modules=await this.#createSegmentModules(definition);return new Segment(name,modules)}#extractSegmentName(filename){const file=filename.split("/").pop();if(void 0===file||""===file)throw new InvalidSegmentFilename(filename);return file.replace(".segment.json","")}async#loadSegmentDefinition(filename){try{const content=await this.#fileManager.getContent(filename);return JSON.parse(content.toString())}catch(error){const message=error instanceof Error?error.message:String(error);throw new SegmentFileNotLoaded(filename,message)}}async#createSegmentModules(definition){const modules=[],idGenerator=new IdGenerator;for(const[filename,moduleImports]of Object.entries(definition)){const fullFilename=filename.endsWith(".js")?filename:`${filename}.js`,absoluteFilename=this.#fileManager.getAbsoluteLocation(fullFilename),module=await this.#createSegmentModule(absoluteFilename,moduleImports,idGenerator);modules.push(module)}return modules}async#createSegmentModule(absoluteFilename,imports,idGenerator){const module=await this.#loadSegmentModule(absoluteFilename),relativeFilename=this.#fileManager.getRelativeLocation(absoluteFilename),moduleName=this.#extractModuleName(relativeFilename),procedures=this.#extractSegmentProcedures(module,moduleName,imports,idGenerator);return new SegmentModule(relativeFilename,procedures)}async#loadSegmentModule(absoluteLocation){try{const code=await this.#fileManager.getContent(absoluteLocation);return reflector.parse(code.toString())}catch(error){const message=error instanceof Error?error.message:String(error);throw new SegmentModuleNotLoaded(absoluteLocation,message)}}#extractModuleName(relativeFilename){const moduleParts=relativeFilename.split("/");return moduleParts.pop(),moduleParts.join("/")}#extractSegmentProcedures(module,moduleName,imports,idGenerator){const procedures=new Map;for(const[importKey,properties]of Object.entries(imports)){const executable=module.getExported(importKey);if(void 0===executable)throw new MissingModuleExport(moduleName,importKey);if(executable instanceof ReflectionFunction==!1)continue;if(!1===executable.isAsync)throw new FunctionNotAsync(moduleName,executable.name);const procedureName=properties.as??executable.name,access=properties.access??"private",version=properties.version??"0.0.0",id=idGenerator.next(),fqn=""!==moduleName?`${moduleName}/${procedureName}`:procedureName,implementation=new SegmentImplementation(id,importKey,access,version,executable),procedure=procedures.has(fqn)?procedures.get(fqn):new SegmentProcedure(fqn);procedure.addImplementation(implementation),procedures.set(fqn,procedure)}return[...procedures.values()]}}class ApplicationReader{#moduleReader;#segmentReader;constructor(fileManager){this.#moduleReader=new ModuleReader(fileManager),this.#segmentReader=new SegmentReader(fileManager)}async read(segmentFiles,moduleFiles){return Promise.all([this.#readSegments(segmentFiles),this.#readModules(moduleFiles)]).then((([segments,modules])=>new Application(segments,modules)))}async#readSegments(segmentFiles){return Promise.all(segmentFiles.map((async segmentFile=>this.#segmentReader.read(segmentFile))))}async#readModules(moduleFiles){return Promise.all(moduleFiles.map((async moduleFile=>this.#moduleReader.read(moduleFile))))}}class CacheManager{#projectFileManager;#appFileManager;#reader;#builder;#writer;constructor(projectFileManager,appFileManager){this.#projectFileManager=projectFileManager,this.#appFileManager=appFileManager,this.#reader=new ApplicationReader(appFileManager),this.#builder=new ApplicationCacheBuilder,this.#writer=new ApplicationCacheWriter(appFileManager)}async build(){const segmentFiles=await this.#projectFileManager.filter(Files.SEGMENT_PATTERN),moduleFiles=await this.#appFileManager.filter(Files.MODULE_PATTERN),application=await this.#reader.read(segmentFiles,moduleFiles),cache=this.#builder.build(application);return this.#writer.write(cache)}}const RuntimeDefaults={URL:"http://localhost:3000",SOURCE:"./dist",CACHE:"./.jitar",INDEX:"index.html",SERVE_INDEX_ON_NOT_FOUND:!1};Object.freeze(RuntimeDefaults);class UnknownRuntimeMode extends Error{constructor(){super("Unknown runtime mode")}}class LocalFileManager{#location;constructor(location){this.#location=location}getRootLocation(){return path.resolve(this.#location)}getAbsoluteLocation(filename){const location=filename.startsWith("/")?filename:path.join(this.#location,filename);return path.resolve(location)}getRelativeLocation(filename){return path.relative(this.#location,filename)}async getType(filename){const location=this.getAbsoluteLocation(filename);return mime.lookup(location)||"application/octet-stream"}async getContent(filename){const location=this.getAbsoluteLocation(filename);if(!1===fs.existsSync(location))throw new FileNotFound(filename);return fs.readFile(location)}async read(filename){const rootPath=this.getRootLocation(),absoluteFilename=this.getAbsoluteLocation(filename);if(!1===absoluteFilename.startsWith(rootPath))throw new FileNotFound(filename);const type=await this.getType(absoluteFilename),content=await this.getContent(absoluteFilename);return new File(filename,type,content)}async write(filename,content){const location=this.getAbsoluteLocation(filename),directory=path.dirname(location);return await fs.mkdir(directory,{recursive:!0}),fs.writeFile(location,content)}async copy(source,destination){const sourceLocation=this.getAbsoluteLocation(source),destinationLocation=this.getAbsoluteLocation(destination);return fs.copy(sourceLocation,destinationLocation,{overwrite:!0})}async delete(filename){const location=this.getAbsoluteLocation(filename);return fs.remove(location)}async filter(pattern){const location=this.getAbsoluteLocation("./");return glob(`${location}/${pattern}`)}async getWorkerSegmentFiles(){return this.filter("**/*.segment.worker.js")}async getRepositorySegmentFiles(){return this.filter("**/*.segment.repository.js")}async getAssetFiles(patterns){const promises=patterns.map((pattern=>this.filter(pattern)));return(await Promise.all(promises)).flat().map((filename=>this.getRelativeLocation(filename))).filter((filename=>!1===this.#isGeneratedFile(filename)))}#isGeneratedFile(filename){return filename.endsWith(".local.js")||filename.endsWith(".worker.js")||filename.endsWith(".repository.js")||filename.endsWith(".remote.js")}}class RuntimeConfigurator{static async configure(configuration){const url=configuration.url??RuntimeDefaults.URL,healthChecks=configuration.healthChecks??[];if(void 0!==configuration.standalone)return this.#configureStandAlone(url,healthChecks,configuration.standalone);if(void 0!==configuration.repository)return this.#configureRepository(url,healthChecks,configuration.repository);if(void 0!==configuration.gateway)return this.#configureGateway(url,healthChecks,configuration.gateway);if(void 0!==configuration.worker)return this.#configureWorker(url,healthChecks,configuration.worker);if(void 0!==configuration.proxy)return this.#configureProxy(url,healthChecks,configuration.proxy);throw new UnknownRuntimeMode}static async#configureStandAlone(url,healthChecks,configuration){const sourceLocation=configuration.source??RuntimeDefaults.SOURCE,cacheLocation=configuration.cache??RuntimeDefaults.CACHE,overrides=configuration.overrides??{},middlewares=configuration.middlewares??[],fileManager=new LocalFileManager(cacheLocation),trustKey=configuration.trustKey;await this.#buildCache(sourceLocation,cacheLocation);const segmentNames=void 0===configuration.segments?await this.#getWorkerSegmentNames(fileManager):configuration.segments,assets=void 0!==configuration.assets?await fileManager.getAssetFiles(configuration.assets):[];return(new RuntimeBuilder).url(url).healthCheck(...healthChecks).middleware(...middlewares).segment(...segmentNames).asset(...assets).override(overrides).fileManager(fileManager).buildStandalone(trustKey)}static async#configureRepository(url,healthChecks,configuration){const sourceLocation=configuration.source??RuntimeDefaults.SOURCE,cacheLocation=configuration.cache??RuntimeDefaults.CACHE,overrides=configuration.overrides??{},fileManager=new LocalFileManager(cacheLocation);await this.#buildCache(sourceLocation,cacheLocation);const segmentNames=await this.#getRepositorySegmentNames(fileManager),assets=void 0!==configuration.assets?await fileManager.getAssetFiles(configuration.assets):[];return(new RuntimeBuilder).url(url).healthCheck(...healthChecks).segment(...segmentNames).asset(...assets).override(overrides).fileManager(fileManager).buildRepository()}static async#configureGateway(url,healthChecks,configuration){const repositoryUrl=configuration.repository,middlewares=configuration.middlewares??[];configuration.monitor;const trustKey=configuration.trustKey;return(new RuntimeBuilder).url(url).healthCheck(...healthChecks).middleware(...middlewares).repository(repositoryUrl).buildGateway(trustKey)}static async#configureWorker(url,healthChecks,configuration){const repositoryUrl=configuration.repository,gatewayUrl=configuration.gateway,segmentNames=configuration.segments??[],middlewares=configuration.middlewares??[],trustKey=configuration.trustKey;return(new RuntimeBuilder).url(url).healthCheck(...healthChecks).middleware(...middlewares).repository(repositoryUrl).gateway(gatewayUrl).segment(...segmentNames).buildWorker(trustKey)}static async#configureProxy(url,healthChecks,configuration){const repositoryUrl=configuration.repository,gatewayUrl=configuration.gateway,workerUrl=configuration.worker,middlewares=configuration.middlewares??[];return(new RuntimeBuilder).url(url).healthCheck(...healthChecks).middleware(...middlewares).repository(repositoryUrl).gateway(gatewayUrl).worker(workerUrl).buildProxy()}static async#buildCache(sourceLocation,cacheLocation){const projectFileManager=new LocalFileManager("./");await projectFileManager.delete(cacheLocation),await projectFileManager.copy(sourceLocation,cacheLocation);const appFileManager=new LocalFileManager(cacheLocation),cacheManager=new CacheManager(projectFileManager,appFileManager);await cacheManager.build()}static async#getWorkerSegmentNames(fileManager){return(await fileManager.getWorkerSegmentFiles()).map((filename=>this.#extractSegmentName(filename)))}static async#getRepositorySegmentNames(fileManager){return(await fileManager.getRepositorySegmentFiles()).map((filename=>this.#extractSegmentName(filename)))}static#extractSegmentName(filename){const name=filename.split("/").pop()??"",endIndex=name.indexOf(".segment");return name.substring(0,endIndex)}}var LogLevel;!function(LogLevel){LogLevel.DEBUG="debug",LogLevel.INFO="info",LogLevel.WARN="warn",LogLevel.ERROR="error",LogLevel.FATAL="fatal"}(LogLevel||(LogLevel={}));class LogBuilder{static build(level){const logConfiguration=this.#getLogConfiguration(level);return new Logger(logConfiguration)}static#getLogConfiguration(level){return{prettyLogTemplate:"{{dateIsoStr}}\t{{logLevelName}}\t",minLevel:this.#getLogLevel(level)}}static#getLogLevel(level){switch(level){case LogLevel.FATAL:return 6;case LogLevel.ERROR:return 5;case LogLevel.WARN:return 4;case LogLevel.INFO:return 3;default:return 2}}}const serverOptionsSchema=z.object({loglevel:z.nativeEnum(LogLevel).optional(),config:z.string().endsWith(".json"),bodylimit:z.number().positive().optional()}).transform((value=>new ServerOptions(value.config,value.loglevel,value.bodylimit)));class ServerOptions{#config;#loglevel;#bodylimit;constructor(config,loglevel="info",bodylimit=204800){this.#config=config,this.#loglevel=loglevel,this.#bodylimit=bodylimit}get config(){return this.#config}get loglevel(){return this.#loglevel}get bodylimit(){return this.#bodylimit}}class ServerOptionsReader{static read(){const args=yargs(process.argv).argv;return DataConverter.convert(serverOptionsSchema,args)}}const Headers={CONTENT_TYPE:"Content-Type",CONTENT_TYPE_OPTIONS:"X-Content-Type-Options",FRAME_OPTIONS:"X-Frame-Options",LOCATION:"Location"};Object.freeze(Headers);const ContentTypes={BOOLEAN:"application/boolean",NUMBER:"application/number",JSON:"application/json",TEXT:"text/plain",HTML:"text/html"};Object.freeze(ContentTypes);class AssetsController{#repository;#indexFile;#serveIndexOnNotFound;#logger;constructor(app,repository,indexFile,serveIndexOnNotFound,logger){this.#repository=repository,this.#indexFile=indexFile,this.#serveIndexOnNotFound=serveIndexOnNotFound,this.#logger=logger,app.get("*",((request,response)=>{this.#getContent(request,response)}))}async#getContent(request,response){const path=request.path.substring(1).trim(),decodedPath=decodeURIComponent(path),filename=0===decodedPath.length?this.#indexFile:decodedPath;this.#loadContent(filename,response)}async#loadContent(filename,response){try{const file=await this.#repository.readAsset(filename);this.#logger.info(`Got asset -> '${filename}'`),file.type===ContentTypes.HTML&&response.setHeader(Headers.FRAME_OPTIONS,"DENY"),response.setHeader(Headers.CONTENT_TYPE,file.type),response.status(200).send(file.content)}catch(error){if(error instanceof FileNotFound)return this.#serveIndexOnNotFound&&filename!==this.#indexFile?this.#loadContent(this.#indexFile,response):(this.#logger.warn(`Failed to get asset -> '${filename}'`),void response.status(404).type("text").send("Not found"));const message=error instanceof Error?error.message:"Internal server error";this.#logger.error(`Failed to get asset -> ${message}`),response.status(500).type("text").send(message)}}}class HealthController{#worker;#logger;constructor(app,worker,logger){this.#worker=worker,this.#logger=logger,app.get("/health",((request,response)=>{this.getHealth(request,response)})),app.get("/health/status",((request,response)=>{this.isHealthy(request,response)}))}async getHealth(request,response){const health=await this.#worker.getHealth(),data=Object.fromEntries(health);return this.#logger.debug("Got health"),response.status(200).send(data)}async isHealthy(request,response){const healthy=await this.#worker.isHealthy();return this.#logger.debug("Got health status"),response.setHeader(Headers.CONTENT_TYPE,ContentTypes.TEXT),response.status(200).send(healthy)}}const filePath=fileURLToPath(import.meta.url),fileLocation=path.dirname(filePath);class JitarController{constructor(app){app.use("/jitar",express.static(fileLocation))}}const clientIdHelper=new ClientIdHelper;class ModulesController{#repository;#serializer;#logger;constructor(app,repository,serializer,logger){this.#repository=repository,this.#serializer=serializer,this.#logger=logger,app.post("/modules",((request,response)=>{this.registerClient(request,response)})),app.get("/modules/:clientId/*",((request,response)=>{this.getModule(request,response)}))}async registerClient(request,response){if(this.#logger.info("Register client"),request.body instanceof Array==!1)return response.status(400).send("Invalid segment file list.");const segmentFiles=request.body;for(const segmentFile of segmentFiles)if("string"!=typeof segmentFile)return response.status(400).send("Invalid segment file list.");const clientId=await this.#repository.registerClient(segmentFiles);return this.#logger.info(`Registered client -> ${clientId} [${segmentFiles.join(",")}]`),response.setHeader(Headers.CONTENT_TYPE,ContentTypes.TEXT),response.status(200).send(clientId)}async getModule(request,response){const clientId=request.params.clientId;if("string"!=typeof clientId||!1===clientIdHelper.validate(clientId))return response.status(400).send("Invalid client id.");this.#logger.info(`Get module for -> '${request.params.clientId}'`);const pathKey=`/${clientId}/`,pathIndex=request.path.indexOf(pathKey)+pathKey.length,filename=request.path.substring(pathIndex);try{const file=await this.#repository.readModule(filename,clientId);return this.#logger.info(`Got module -> '${filename}' (${clientId})`),response.setHeader(Headers.CONTENT_TYPE,file.type),response.status(200).send(file.content)}catch(error){const message=error instanceof Error?error.message:String(error);this.#logger.error(`Failed to get module -> '${filename}' (${clientId}) | ${message}`);const data=this.#serializer.serialize(error);return response.setHeader(Headers.CONTENT_TYPE,ContentTypes.JSON),response.status(500).send(data)}}}const workerDtoSchema=z.object({url:z.string().url(),procedureNames:z.array(z.string()).optional(),trustKey:z.string().optional()}).strict().transform((value=>new WorkerDto(value.url,value.procedureNames,value.trustKey)));class WorkerDto{url;procedureNames;trustKey;constructor(url,procedureNames=[],trustKey){this.url=url,this.procedureNames=procedureNames,this.trustKey=trustKey}}class WorkerController{#gateway;#logger;constructor(app,gateway,logger){this.#gateway=gateway,this.#logger=logger,app.get("/workers",((request,response)=>{this.getWorkers(request,response)})),app.post("/workers",((request,response)=>{this.addWorker(request,response)}))}async getWorkers(request,response){const workers=this.#gateway.workers.map((worker=>({url:worker.url,procedureNames:worker.getProcedureNames()})));return this.#logger.info("Got workers"),response.setHeader(Headers.CONTENT_TYPE,ContentTypes.JSON),response.status(200).send(workers)}async addWorker(request,response){try{const workerDto=DataConverter.convert(workerDtoSchema,request.body),worker=new RemoteWorker(workerDto.url);return worker.procedureNames=new Set(workerDto.procedureNames),await this.#gateway.addWorker(worker,workerDto.trustKey),this.#logger.info(`Added worker -> ${worker.url}`),response.status(201).send()}catch(error){if(error instanceof ConversionError){const message=error.message;return this.#logger.warn(`Failed to add worker | ${message}`),response.status(400).type("text").send(message)}const message=error instanceof Error?error.message:"Internal server error";return this.#logger.error(`Failed to add worker | ${message}`),response.status(500).type("text").send(message)}}}class ProceduresController{#runtime;#logger;constructor(app,runtime,logger){this.#runtime=runtime,this.#logger=logger,app.get("/procedures",((request,response)=>{this.getProcedureNames(request,response)}))}async getProcedureNames(request,response){const names=this.#runtime.getProcedureNames();return this.#logger.info("Got procedure names"),response.setHeader(Headers.CONTENT_TYPE,ContentTypes.JSON),response.status(200).send(names)}}class ProxyController{#logger;#repositoryUrl;#runnerUrl;constructor(app,proxy,logger){this.#logger=logger,this.#repositoryUrl=proxy.repository.url??"",this.#runnerUrl=proxy.runner.url??"",app.use("/",expressProxy((message=>this.#selectProxy(message))))}#selectProxy(message){const url=message.url??"";return this.#logger.info(`Forwarding -> ${url}`),url.startsWith("/rpc")?this.#runnerUrl:this.#repositoryUrl}}const RPC_PARAMETERS=["version","serialize"],IGNORED_HEADER_KEYS=["host","connection","content-length","accept-encoding","user-agent","keep-alive"],BAD_REQUEST_NAME=BadRequest.name,UNAUTHORIZED_NAME=Unauthorized.name,PAYMENT_REQUIRED_NAME=PaymentRequired.name,FORBIDDEN_NAME=Forbidden.name,NOT_FOUND_NAME=NotFound.name,TEAPOT_NAME=Teapot.name,NOT_IMPLEMENTED_NAME=NotImplemented.name;class RPCController{#runtime;#serializer;#logger;constructor(app,runtime,serializer,logger){this.#runtime=runtime,this.#serializer=serializer,this.#logger=logger,app.get("/rpc/*",((request,response)=>{this.runGet(request,response)})),app.post("/rpc/*",((request,response)=>{this.runPost(request,response)})),app.options("/rpc/*",((request,response)=>{this.runOptions(request,response)}))}async runGet(request,response){try{const fqn=this.#extractFqn(request),version=this.#extractVersion(request),args=this.#extractQueryArguments(request),headers=this.#extractHeaders(request),serialize=this.#extractSerialize(request);return this.#run(fqn,version,args,headers,response,serialize)}catch(error){const message=error instanceof Error?error.message:String(error);return this.#logger.warn(`Invalid request -> ${message}`),response.status(400).type("text").send(`Invalid request -> ${message}`)}}async runPost(request,response){try{const fqn=this.#extractFqn(request),version=this.#extractVersion(request),args=this.#extractBodyArguments(request),headers=this.#extractHeaders(request),serialize=this.#extractSerialize(request);return this.#run(fqn,version,args,headers,response,serialize)}catch(error){const message=error instanceof Error?error.message:String(error);return this.#logger.warn(`Invalid request -> ${message}`),response.status(400).type("text").send(`Invalid request -> ${message}`)}}async runOptions(request,response){return this.#setCors(response)}#extractFqn(request){const fqn=decodeURIComponent(request.path.trim()).substring(5).trim();if(0===fqn.length)throw new BadRequest("Missing procedure name");if(fqn.includes(".."))throw new BadRequest("Invalid procedure name");return fqn}#extractVersion(request){const version=void 0!==request.query.version?request.query.version:"";if("string"!=typeof version)throw new BadRequest("Invalid version number");return VersionParser.parse(version)}#extractSerialize(request){return"true"===request.query.serialize}#extractQueryArguments(request){const args={};for(const[key,value]of Object.entries(request.query))RPC_PARAMETERS.includes(key)||(args[key]=value);return args}#extractBodyArguments(request){return request.body}#extractHeaders(request){const headers=new Map;for(const[key,value]of Object.entries(request.headers)){if(void 0===value)continue;const lowerKey=key.toLowerCase(),stringValue=value.toString();IGNORED_HEADER_KEYS.includes(lowerKey)||headers.set(lowerKey,stringValue)}return headers}async#run(fqn,version,args,headers,response,serialize){if(!1===this.#runtime.hasProcedure(fqn))return response.setHeader(Headers.CONTENT_TYPE,ContentTypes.TEXT),response.status(404).send(`Procedure not found -> ${fqn}`);try{const deserializedArgs=await this.#serializer.deserialize(args),argsMap=new Map(Object.entries(deserializedArgs)),runtimeRequest=new Request(fqn,version,argsMap,headers),runtimeResponse=await this.#runtime.handle(runtimeRequest);return this.#logger.info(`Ran procedure -> ${fqn} (v${version.toString()})`),this.#setResponseHeaders(response,runtimeResponse.headers),this.#createResultResponse(runtimeResponse.result,response,serialize)}catch(error){const message=error instanceof Error?error.message:String(error),errorData=serialize?error:message;return this.#logger.error(`Failed to run procedure -> ${fqn} (v${version.toString()}) | ${message}`),this.#createErrorResponse(error,errorData,response,serialize)}}async#setCors(response){const cors=this.#runtime.getMiddleware(CorsMiddleware);return void 0===cors||(response.setHeader("Access-Control-Allow-Origin",cors.allowOrigin),response.setHeader("Access-Control-Allow-Methods",cors.allowMethods),response.setHeader("Access-Control-Allow-Headers",cors.allowHeaders),response.setHeader("Access-Control-Max-Age",86400)),response.status(204).send()}async#createResultResponse(result,response,serialize){const content=await this.#createResponseContent(result,serialize),contentType=this.#createResponseContentType(content),responseContent=contentType===ContentTypes.JSON?content:String(content),statusCode=this.#createResponseResultStatusCode(result,response);return response.setHeader(Headers.CONTENT_TYPE,contentType),response.status(statusCode).send(responseContent)}async#createErrorResponse(error,errorData,response,serialize){const content=await this.#createResponseContent(errorData,serialize),contentType=this.#createResponseContentType(content),statusCode=this.#createResponseErrorStatusCode(error);return response.setHeader(Headers.CONTENT_TYPE,contentType),response.status(statusCode).send(content)}async#createResponseContent(data,serialize){return serialize?this.#serializer.serialize(data):data}#createResponseContentType(content){switch(typeof content){case"boolean":return ContentTypes.BOOLEAN;case"number":return ContentTypes.NUMBER;case"object":return ContentTypes.JSON;default:return ContentTypes.TEXT}}#setResponseHeaders(response,headers){for(const[key,value]of headers.entries())void 0!==value&&(IGNORED_HEADER_KEYS.includes(key)||response.set(key,value))}#createResponseResultStatusCode(result,response){return response.hasHeader(Headers.LOCATION)?302:void 0===result?204:200}#createResponseErrorStatusCode(error){if(error instanceof Object==!1)return 500;const errorClass=error.constructor;return this.#isClassType(errorClass,BAD_REQUEST_NAME)?400:this.#isClassType(errorClass,UNAUTHORIZED_NAME)?401:this.#isClassType(errorClass,PAYMENT_REQUIRED_NAME)?402:this.#isClassType(errorClass,FORBIDDEN_NAME)?403:this.#isClassType(errorClass,NOT_FOUND_NAME)?404:this.#isClassType(errorClass,TEAPOT_NAME)?418:this.#isClassType(errorClass,NOT_IMPLEMENTED_NAME)?501:500}#isClassType(clazz,className){if(clazz.name===className)return!0;const parentClass=Object.getPrototypeOf(clazz);return""!==parentClass.name&&this.#isClassType(parentClass,className)}}class RuntimeNotAvailable extends Error{constructor(){super("Runtime is not available")}}class JitarServer{#app;#server;#runtime;#serializer;#classLoader;#options;#configuration;#logger;constructor(){this.#classLoader=new RemoteClassLoader,this.#serializer=SerializerBuilder.build(this.#classLoader),this.#options=ServerOptionsReader.read(),this.#configuration=RuntimeConfigurationLoader.load(this.#options.config),this.#logger=LogBuilder.build(this.#options.loglevel),this.#app=express(),this.#app.use(express.json({limit:this.#options.bodylimit})),this.#app.use(express.urlencoded({extended:!0})),this.#app.use(((request,response,next)=>this.#addDefaultHeaders(request,response,next))),this.#app.disable("x-powered-by"),this.#printStartupMessage()}get classLoader(){return this.#classLoader}async build(){this.#runtime=await RuntimeConfigurator.configure(this.#configuration),this.#addControllers()}async start(){const url=new URL(this.#configuration.url??RuntimeDefaults.URL);try{await this.#startApplication(),await this.#startServer(url.port)}catch(error){const message=error instanceof Error?error.message:String(error);return this.#logger.error(`Failed to start server -> ${message}`),void await this.stop()}this.#printProcedureInfo(),this.#logger.info(`Server started and listening at port ${url.port}`)}async stop(){try{await this.#stopServer(),await this.#stopApplication()}catch(error){const message=error instanceof Error?error.message:String(error);return void this.#logger.error(`Caught error while stopping server -> ${message}`)}this.#logger.info("Server stopped")}addSerializer(serializer){this.#serializer.addSerializer(serializer)}#getRuntime(){if(void 0===this.#runtime)throw new RuntimeNotAvailable;return this.#runtime}#addControllers(){if(void 0!==this.#configuration.standalone&&this.#runtime instanceof Standalone){const index=this.#configuration.standalone.index??RuntimeDefaults.INDEX,serveIndexOnNotFound=this.#configuration.standalone.serveIndexOnNotFound??RuntimeDefaults.SERVE_INDEX_ON_NOT_FOUND;this.#addStandAloneControllers(this.#runtime,index,serveIndexOnNotFound)}else if(void 0!==this.#configuration.repository&&this.#runtime instanceof LocalRepository){const index=this.#configuration.repository.index??RuntimeDefaults.INDEX,serveIndexOnNotFound=this.#configuration.repository.serveIndexOnNotFound??RuntimeDefaults.SERVE_INDEX_ON_NOT_FOUND;this.#addRepositoryControllers(this.#runtime,index,serveIndexOnNotFound)}else void 0!==this.#configuration.gateway&&this.#runtime instanceof LocalGateway?this.#addGatewayControllers(this.#runtime):void 0!==this.#configuration.worker&&this.#runtime instanceof LocalWorker?this.#addWorkerControllers(this.#runtime):void 0!==this.#configuration.proxy&&this.#runtime instanceof Proxy&&this.#addProxyControllers(this.#runtime)}#addStandAloneControllers(standalone,index,serveIndexOnNotFound){new HealthController(this.#app,standalone,this.#logger),new JitarController(this.#app),new ModulesController(this.#app,standalone,this.#serializer,this.#logger),new ProceduresController(this.#app,standalone,this.#logger),new RPCController(this.#app,standalone,this.#serializer,this.#logger),new AssetsController(this.#app,standalone,index,serveIndexOnNotFound,this.#logger)}#addRepositoryControllers(repository,index,serveIndexOnNotFound){new JitarController(this.#app),new ModulesController(this.#app,repository,this.#serializer,this.#logger),new AssetsController(this.#app,repository,index,serveIndexOnNotFound,this.#logger)}#addGatewayControllers(gateway){new WorkerController(this.#app,gateway,this.#logger),new ProceduresController(this.#app,gateway,this.#logger),new RPCController(this.#app,gateway,this.#serializer,this.#logger)}#addWorkerControllers(worker){new HealthController(this.#app,worker,this.#logger),new ProceduresController(this.#app,worker,this.#logger),new RPCController(this.#app,worker,this.#serializer,this.#logger)}#addProxyControllers(proxy){new ProxyController(this.#app,proxy,this.#logger)}async#startApplication(){const runtime=this.#getRuntime();await runtime.start();const setUpScripts=this.#configuration.setUp;if(void 0!==setUpScripts)for(const setUpScript of setUpScripts)await runtime.import(setUpScript,ExecutionScopes.APPLICATION)}async#stopApplication(){const runtime=this.#getRuntime();await runtime.stop();const tearDownScripts=this.#configuration.tearDown;if(void 0!==tearDownScripts)for(const tearDownScript of tearDownScripts)await runtime.import(tearDownScript,ExecutionScopes.APPLICATION)}#startServer(port){return void 0!==this.#server?Promise.resolve():new Promise(((resolve,reject)=>{this.#server=this.#app.listen(port,resolve),this.#server.on("error",reject)}))}#stopServer(){return void 0===this.#server?Promise.resolve():new Promise((resolve=>{this.#server?.close((()=>resolve()))}))}#printStartupMessage(){console.log("\n ██ ██ ████████ █████ ██████ \n ██ ██ ██ ██ ██ ██ ██ \n ██ ██ ██ ███████ ██████ \n ██ ██ ██ ██ ██ ██ ██ ██ \n █████ ██ ██ ██ ██ ██ ██\n ____________________________________\n By Masking Technology (masking.tech)\n")}#printProcedureInfo(){const runtime=this.#getRuntime();if(runtime instanceof LocalWorker==!1&&runtime instanceof Standalone==!1)return;const procedureNames=runtime.getProcedureNames();0!==procedureNames.length&&(procedureNames.sort(),this.#logger.info("Registered RPC entries",procedureNames))}#addDefaultHeaders(request,response,next){response.setHeader(Headers.CONTENT_TYPE_OPTIONS,"nosniff"),next()}}async function buildServer(moduleImporter){ModuleLoader.setImporter(moduleImporter);const server=new JitarServer;return await server.build(),server}export{CorsMiddleware,buildServer};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jitar",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
4
4
|
"description": "Distributed runtime for JavaScript and TypeScript to chop monolithic applications into micros.",
|
|
5
5
|
"author": "Masking Technology <info@masking.tech> (https://jitar.dev)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"zod": "^3.22.4"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@jitar/runtime": "^0.7.
|
|
40
|
-
"@jitar/server-nodejs": "^0.7.
|
|
39
|
+
"@jitar/runtime": "^0.7.1",
|
|
40
|
+
"@jitar/server-nodejs": "^0.7.3"
|
|
41
41
|
},
|
|
42
42
|
"engines": {
|
|
43
43
|
"node": ">=20.0"
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"full stack",
|
|
61
61
|
"web applications"
|
|
62
62
|
],
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "3d6d5946c7cf345fd62ba34d28d6bba1a2a2df8e"
|
|
64
64
|
}
|
|
File without changes
|