@serve.zone/interfaces 7.28.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +10 -0
- package/dist_ts/00_commitinfo_data.js +2 -2
- package/dist_ts/appstore/types.d.ts +9 -1
- package/dist_ts/data/gateway.d.ts +10 -2
- package/dist_ts/data/index.d.ts +1 -0
- package/dist_ts/data/index.js +2 -1
- package/dist_ts/data/service.d.ts +17 -24
- package/dist_ts/data/serviceports.d.ts +76 -0
- package/dist_ts/data/serviceports.js +210 -0
- package/dist_ts/data/traffic.d.ts +7 -0
- package/dist_ts/data/traffic.js +1 -1
- package/dist_ts/requests/routing.d.ts +3 -1
- package/dist_ts/requests/routing.js +1 -1
- package/package.json +2 -2
- package/readme.md +44 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/appstore/types.ts +14 -1
- package/ts/data/gateway.ts +10 -2
- package/ts/data/index.ts +1 -0
- package/ts/data/service.ts +21 -24
- package/ts/data/serviceports.ts +338 -0
- package/ts/data/traffic.ts +8 -0
- package/ts/requests/routing.ts +3 -1
package/changelog.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2026-07-04 - 8.0.0
|
|
4
|
+
|
|
5
|
+
### Breaking Changes
|
|
6
|
+
|
|
7
|
+
- introduce canonical service port routing contracts (service-ports)
|
|
8
|
+
- Add shared service target port, domain route, public port mapping, and normalization helper contracts.
|
|
9
|
+
- Update service and App Store interfaces to support targetPorts, domain target references, and publicPortMappings.
|
|
10
|
+
- Extend gateway and Coretraffic routing contracts with transport-aware port routes, routeRef ownership, and remoteIngress intent.
|
|
11
|
+
- Breaking change: legacy SSH domain routing is no longer represented through domain protocol values; public TCP/UDP exposure now uses publicPortMappings.
|
|
12
|
+
|
|
3
13
|
## 2026-06-24 - 7.28.0
|
|
4
14
|
|
|
5
15
|
### Features
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@serve.zone/interfaces',
|
|
6
|
-
version: '
|
|
6
|
+
version: '8.0.0',
|
|
7
7
|
description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
|
|
8
8
|
};
|
|
9
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
9
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx3QkFBd0I7SUFDOUIsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLHVGQUF1RjtDQUNyRyxDQUFBIn0=
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { IServiceDomainRoute, IServicePublicPortMapping, IServiceTargetPort } from '../data/serviceports.js';
|
|
1
2
|
export type TAppStorePlatformRequirement = 'mongodb' | 's3' | 'clickhouse' | 'valkey' | 'mariadb';
|
|
2
3
|
export type TAppStoreSourceType = 'inline' | 'repoManifest' | 'dockerImage';
|
|
3
4
|
export type TAppStoreTrackingMode = 'tag' | 'digest';
|
|
@@ -98,7 +99,12 @@ export interface IAppStoreAppMeta {
|
|
|
98
99
|
}
|
|
99
100
|
export interface IAppStoreVersionConfig {
|
|
100
101
|
image: string;
|
|
101
|
-
|
|
102
|
+
/** Legacy shorthand for targetPorts.web. New templates should prefer targetPorts. */
|
|
103
|
+
port?: number;
|
|
104
|
+
targetPorts?: IServiceTargetPort[];
|
|
105
|
+
domains?: IServiceDomainRoute[];
|
|
106
|
+
/** Edge/coretraffic public TCP/UDP exposure, distinct from Docker publishedPorts. */
|
|
107
|
+
publicPortMappings?: IServicePublicPortMapping[];
|
|
102
108
|
envVars?: IAppStoreEnvVar[];
|
|
103
109
|
volumes?: TAppStoreVolumeSpec[];
|
|
104
110
|
publishedPorts?: IAppStorePublishedPort[];
|
|
@@ -155,8 +161,10 @@ export interface IAppStoreInstallRequest {
|
|
|
155
161
|
version?: string;
|
|
156
162
|
serviceName: string;
|
|
157
163
|
domain?: string;
|
|
164
|
+
/** Legacy install-time override for the web target port. */
|
|
158
165
|
port?: number;
|
|
159
166
|
publishedPorts?: IAppStorePublishedPort[];
|
|
167
|
+
publicPortMappings?: IServicePublicPortMapping[];
|
|
160
168
|
envVars?: Record<string, string>;
|
|
161
169
|
}
|
|
162
170
|
export interface IUpgradeableAppStoreService {
|
|
@@ -173,7 +173,10 @@ export interface IGatewayClientOwnership {
|
|
|
173
173
|
gatewayClientId?: string;
|
|
174
174
|
/** App identifier within the owning gateway client platform. */
|
|
175
175
|
appId: string;
|
|
176
|
-
|
|
176
|
+
/** Hostname ownership for domain routes. Preserved for existing route keys. */
|
|
177
|
+
hostname?: string;
|
|
178
|
+
/** Stable non-domain route key, for example public-port mapping exposure refs. */
|
|
179
|
+
routeRef?: string;
|
|
177
180
|
}
|
|
178
181
|
/**
|
|
179
182
|
* A single forward target of a gateway route.
|
|
@@ -187,7 +190,8 @@ export interface IGatewayRouteTarget {
|
|
|
187
190
|
*/
|
|
188
191
|
export interface IGatewayRouteMatch {
|
|
189
192
|
ports: number[];
|
|
190
|
-
domains
|
|
193
|
+
domains?: string[];
|
|
194
|
+
transport?: 'tcp' | 'udp' | 'all';
|
|
191
195
|
path?: string;
|
|
192
196
|
}
|
|
193
197
|
/**
|
|
@@ -220,6 +224,10 @@ export interface IGatewayRouteConfig {
|
|
|
220
224
|
name: string;
|
|
221
225
|
match: IGatewayRouteMatch;
|
|
222
226
|
action: IGatewayRouteAction;
|
|
227
|
+
remoteIngress?: {
|
|
228
|
+
enabled: boolean;
|
|
229
|
+
edgeFilter?: string[];
|
|
230
|
+
};
|
|
223
231
|
}
|
|
224
232
|
/**
|
|
225
233
|
* Result of an idempotent gateway route sync.
|
package/dist_ts/data/index.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export * from './backup.js';
|
|
|
21
21
|
export * from './clusternode.js';
|
|
22
22
|
export * from './settings.js';
|
|
23
23
|
export * from './service.js';
|
|
24
|
+
export * from './serviceports.js';
|
|
24
25
|
export * from './status.js';
|
|
25
26
|
export * from './taskexecution.js';
|
|
26
27
|
export * from './traffic.js';
|
package/dist_ts/data/index.js
CHANGED
|
@@ -21,9 +21,10 @@ export * from './backup.js';
|
|
|
21
21
|
export * from './clusternode.js';
|
|
22
22
|
export * from './settings.js';
|
|
23
23
|
export * from './service.js';
|
|
24
|
+
export * from './serviceports.js';
|
|
24
25
|
export * from './status.js';
|
|
25
26
|
export * from './taskexecution.js';
|
|
26
27
|
export * from './traffic.js';
|
|
27
28
|
export * from './user.js';
|
|
28
29
|
export * from './version.js';
|
|
29
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
30
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9kYXRhL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWMsb0JBQW9CLENBQUM7QUFDbkMsY0FBYyxjQUFjLENBQUM7QUFDN0IsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxpQkFBaUIsQ0FBQztBQUNoQyxjQUFjLGdCQUFnQixDQUFDO0FBQy9CLGNBQWMsVUFBVSxDQUFDO0FBQ3pCLGNBQWMsYUFBYSxDQUFDO0FBQzVCLGNBQWMsYUFBYSxDQUFDO0FBQzVCLGNBQWMsWUFBWSxDQUFDO0FBQzNCLGNBQWMsdUJBQXVCLENBQUM7QUFDdEMsY0FBYyxjQUFjLENBQUM7QUFDN0IsY0FBYyxnQkFBZ0IsQ0FBQztBQUMvQixjQUFjLFlBQVksQ0FBQztBQUMzQixjQUFjLFdBQVcsQ0FBQztBQUMxQixjQUFjLGVBQWUsQ0FBQztBQUM5QixjQUFjLG1CQUFtQixDQUFDO0FBQ2xDLGNBQWMsa0JBQWtCLENBQUM7QUFDakMsY0FBYyxnQkFBZ0IsQ0FBQztBQUMvQixjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLGFBQWEsQ0FBQztBQUM1QixjQUFjLGtCQUFrQixDQUFDO0FBQ2pDLGNBQWMsZUFBZSxDQUFDO0FBQzlCLGNBQWMsY0FBYyxDQUFDO0FBQzdCLGNBQWMsbUJBQW1CLENBQUM7QUFDbEMsY0FBYyxhQUFhLENBQUM7QUFDNUIsY0FBYyxvQkFBb0IsQ0FBQztBQUNuQyxjQUFjLGNBQWMsQ0FBQztBQUM3QixjQUFjLFdBQVcsQ0FBQztBQUMxQixjQUFjLGNBQWMsQ0FBQyJ9
|
|
@@ -3,6 +3,7 @@ import type { IRegistryTarget } from './registry.js';
|
|
|
3
3
|
import type { IAppStorePublishedPort } from '../appstore/index.js';
|
|
4
4
|
import type { IHostedAppLifecycleState } from './hostedapp.js';
|
|
5
5
|
import type { IServiceMailConfig } from './mail.js';
|
|
6
|
+
import type { IServiceDomainRoute, IServicePublicPortMapping, IServiceTargetPort } from './serviceports.js';
|
|
6
7
|
export interface IServiceVolume {
|
|
7
8
|
/** Stable Docker volume name. If omitted, Coreflow derives one from service id and mount path. */
|
|
8
9
|
name?: string;
|
|
@@ -109,8 +110,21 @@ export interface IService {
|
|
|
109
110
|
antiAffinity?: boolean;
|
|
110
111
|
scaleFactor: number;
|
|
111
112
|
balancingStrategy: 'round-robin' | 'least-connections';
|
|
113
|
+
/**
|
|
114
|
+
* Canonical backend/container ports for this service. New domain routes
|
|
115
|
+
* and public mappings reference these targets by name or exact port.
|
|
116
|
+
*/
|
|
117
|
+
targetPorts?: IServiceTargetPort[];
|
|
112
118
|
ports: {
|
|
119
|
+
/**
|
|
120
|
+
* Legacy main backend port. New writes should declare targetPorts and
|
|
121
|
+
* keep this only as compatibility shorthand during migration.
|
|
122
|
+
*/
|
|
113
123
|
web: number;
|
|
124
|
+
/**
|
|
125
|
+
* Legacy implicit domain-to-port routes. Runtime code must not treat
|
|
126
|
+
* this as a permissive routing source for new services.
|
|
127
|
+
*/
|
|
114
128
|
custom?: {
|
|
115
129
|
[domain: string]: string;
|
|
116
130
|
};
|
|
@@ -118,31 +132,10 @@ export interface IService {
|
|
|
118
132
|
mail?: IServiceMailConfig;
|
|
119
133
|
volumes?: IServiceVolume[];
|
|
120
134
|
publishedPorts?: IAppStorePublishedPort[];
|
|
135
|
+
/** Edge/coretraffic public TCP/UDP exposure, distinct from Docker Swarm publishedPorts. */
|
|
136
|
+
publicPortMappings?: IServicePublicPortMapping[];
|
|
121
137
|
resources?: IServiceRessources;
|
|
122
|
-
domains:
|
|
123
|
-
/**
|
|
124
|
-
* Optional domain ID to specify which domain to use
|
|
125
|
-
* If not specified, will use the default domain or require manual configuration
|
|
126
|
-
*/
|
|
127
|
-
domainId?: string;
|
|
128
|
-
/**
|
|
129
|
-
* The subdomain name (e.g., 'api', 'www', '@' for root)
|
|
130
|
-
*/
|
|
131
|
-
name: string;
|
|
132
|
-
/**
|
|
133
|
-
* The port to expose (defaults to ports.web if not specified)
|
|
134
|
-
*/
|
|
135
|
-
port?: number;
|
|
136
|
-
/**
|
|
137
|
-
* The protocol for this domain entry
|
|
138
|
-
*/
|
|
139
|
-
protocol?: 'http' | 'https' | 'ssh';
|
|
140
|
-
/**
|
|
141
|
-
* Gateway source profile for this domain's route (e.g. 'standard'
|
|
142
|
-
* for internal-only access). Defaults to the gateway's public profile.
|
|
143
|
-
*/
|
|
144
|
-
sourceProfileRef?: string;
|
|
145
|
-
}[];
|
|
138
|
+
domains: IServiceDomainRoute[];
|
|
146
139
|
deploymentIds: string[];
|
|
147
140
|
};
|
|
148
141
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export type TServiceTargetProtocol = 'http' | 'https' | 'ssh' | 'tcp' | 'udp' | `custom:${string}`;
|
|
2
|
+
export type TServiceTargetPortRef = string | number;
|
|
3
|
+
export interface IServiceTargetPort {
|
|
4
|
+
name: string;
|
|
5
|
+
port: number;
|
|
6
|
+
portEnd?: number;
|
|
7
|
+
protocol: TServiceTargetProtocol;
|
|
8
|
+
description?: string;
|
|
9
|
+
default?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface IServiceDomainRoute {
|
|
12
|
+
domainId?: string;
|
|
13
|
+
name: string;
|
|
14
|
+
targetPort?: TServiceTargetPortRef;
|
|
15
|
+
/**
|
|
16
|
+
* Legacy backend target port. New writes must use targetPort.
|
|
17
|
+
*/
|
|
18
|
+
port?: number;
|
|
19
|
+
protocol?: 'http' | 'https';
|
|
20
|
+
sourceProfileRef?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface IServicePublicPortMapping {
|
|
23
|
+
name: string;
|
|
24
|
+
publicPort: number;
|
|
25
|
+
publicPortEnd?: number;
|
|
26
|
+
targetPort: TServiceTargetPortRef;
|
|
27
|
+
protocol: 'tcp' | 'udp';
|
|
28
|
+
sourceProfileRef?: string;
|
|
29
|
+
exclusive: true;
|
|
30
|
+
}
|
|
31
|
+
export interface IResolvedServiceDomainRoute extends IServiceDomainRoute {
|
|
32
|
+
targetPort: TServiceTargetPortRef;
|
|
33
|
+
target: IServiceTargetPort;
|
|
34
|
+
}
|
|
35
|
+
export interface IResolvedServicePublicPortMapping extends IServicePublicPortMapping {
|
|
36
|
+
target: IServiceTargetPort;
|
|
37
|
+
targetPortStart: number;
|
|
38
|
+
targetPortEnd: number;
|
|
39
|
+
publicPortEnd: number;
|
|
40
|
+
}
|
|
41
|
+
export interface IServicePortSourceData {
|
|
42
|
+
name?: string;
|
|
43
|
+
ports?: {
|
|
44
|
+
web?: number;
|
|
45
|
+
custom?: Record<string, string>;
|
|
46
|
+
};
|
|
47
|
+
targetPorts?: IServiceTargetPort[];
|
|
48
|
+
domains?: IServiceDomainRoute[];
|
|
49
|
+
publicPortMappings?: IServicePublicPortMapping[];
|
|
50
|
+
}
|
|
51
|
+
export interface INormalizedServicePortConfig {
|
|
52
|
+
targetPorts: IServiceTargetPort[];
|
|
53
|
+
defaultTargetPort?: IServiceTargetPort;
|
|
54
|
+
domains: IResolvedServiceDomainRoute[];
|
|
55
|
+
publicPortMappings: IResolvedServicePublicPortMapping[];
|
|
56
|
+
}
|
|
57
|
+
export interface IServicePortNormalizationOptions {
|
|
58
|
+
/**
|
|
59
|
+
* Accept legacy domains[].port only when it resolves to a declared target.
|
|
60
|
+
* Defaults to true for staged migration reads.
|
|
61
|
+
*/
|
|
62
|
+
allowLegacyDomainPort?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Domain routes are HTTP(S) virtual-host routes. Defaults to true.
|
|
65
|
+
*/
|
|
66
|
+
requireHttpDomainTargets?: boolean;
|
|
67
|
+
}
|
|
68
|
+
export declare function normalizeServicePortConfig(serviceDataArg: IServicePortSourceData, optionsArg?: IServicePortNormalizationOptions): INormalizedServicePortConfig;
|
|
69
|
+
export declare function normalizeServicePortData<T extends IServicePortSourceData>(serviceDataArg: T, optionsArg?: IServicePortNormalizationOptions): T & {
|
|
70
|
+
targetPorts: IServiceTargetPort[];
|
|
71
|
+
domains?: IServiceDomainRoute[];
|
|
72
|
+
publicPortMappings?: IServicePublicPortMapping[];
|
|
73
|
+
};
|
|
74
|
+
export declare function resolveServiceTargetPort(targetPortsArg: IServiceTargetPort[], refArg: TServiceTargetPortRef): IServiceTargetPort | undefined;
|
|
75
|
+
export declare function resolveDefaultServiceTargetPort(targetPortsArg: IServiceTargetPort[]): IServiceTargetPort | undefined;
|
|
76
|
+
export declare function isHttpServiceTargetProtocol(protocolArg: TServiceTargetProtocol): boolean;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
export function normalizeServicePortConfig(serviceDataArg, optionsArg = {}) {
|
|
2
|
+
const options = {
|
|
3
|
+
allowLegacyDomainPort: optionsArg.allowLegacyDomainPort ?? true,
|
|
4
|
+
requireHttpDomainTargets: optionsArg.requireHttpDomainTargets ?? true,
|
|
5
|
+
};
|
|
6
|
+
const serviceName = serviceDataArg.name || 'service';
|
|
7
|
+
const targetPorts = normalizeServiceTargetPorts(serviceDataArg, serviceName);
|
|
8
|
+
const defaultTargetPort = resolveDefaultServiceTargetPort(targetPorts);
|
|
9
|
+
const domains = (serviceDataArg.domains || []).map((domainArg) => {
|
|
10
|
+
const target = resolveServiceDomainTarget(domainArg, targetPorts, defaultTargetPort, serviceName, options);
|
|
11
|
+
if (options.requireHttpDomainTargets && !isHttpServiceTargetProtocol(target.protocol)) {
|
|
12
|
+
throw new Error(`Domain ${domainArg.name} on ${serviceName} references non-HTTP target '${target.name}' (${target.protocol})`);
|
|
13
|
+
}
|
|
14
|
+
if (target.portEnd && target.portEnd !== target.port) {
|
|
15
|
+
throw new Error(`Domain ${domainArg.name} on ${serviceName} references ranged target '${target.name}'`);
|
|
16
|
+
}
|
|
17
|
+
const normalizedDomain = {
|
|
18
|
+
...domainArg,
|
|
19
|
+
targetPort: domainArg.targetPort ?? target.name,
|
|
20
|
+
target,
|
|
21
|
+
};
|
|
22
|
+
delete normalizedDomain.port;
|
|
23
|
+
return normalizedDomain;
|
|
24
|
+
});
|
|
25
|
+
const publicPortMappings = normalizeServicePublicPortMappings(serviceDataArg.publicPortMappings || [], targetPorts, serviceName);
|
|
26
|
+
return {
|
|
27
|
+
targetPorts,
|
|
28
|
+
defaultTargetPort,
|
|
29
|
+
domains,
|
|
30
|
+
publicPortMappings,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function normalizeServicePortData(serviceDataArg, optionsArg = {}) {
|
|
34
|
+
const normalized = normalizeServicePortConfig(serviceDataArg, optionsArg);
|
|
35
|
+
return {
|
|
36
|
+
...serviceDataArg,
|
|
37
|
+
targetPorts: normalized.targetPorts,
|
|
38
|
+
domains: normalized.domains.map(({ target, ...domainArg }) => domainArg),
|
|
39
|
+
publicPortMappings: normalized.publicPortMappings.map(({ target, targetPortStart, targetPortEnd, ...mappingArg }) => mappingArg),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function resolveServiceTargetPort(targetPortsArg, refArg) {
|
|
43
|
+
if (typeof refArg === 'string') {
|
|
44
|
+
const normalizedRef = refArg.trim().toLowerCase();
|
|
45
|
+
return targetPortsArg.find((targetArg) => targetArg.name.trim().toLowerCase() === normalizedRef);
|
|
46
|
+
}
|
|
47
|
+
return targetPortsArg.find((targetArg) => targetArg.port === refArg && !targetArg.portEnd);
|
|
48
|
+
}
|
|
49
|
+
export function resolveDefaultServiceTargetPort(targetPortsArg) {
|
|
50
|
+
const explicitDefaults = targetPortsArg.filter((targetArg) => targetArg.default);
|
|
51
|
+
if (explicitDefaults.length > 1) {
|
|
52
|
+
throw new Error(`Multiple default target ports declared: ${explicitDefaults.map((targetArg) => targetArg.name).join(', ')}`);
|
|
53
|
+
}
|
|
54
|
+
return explicitDefaults[0]
|
|
55
|
+
|| targetPortsArg.find((targetArg) => targetArg.name === 'web')
|
|
56
|
+
|| targetPortsArg.find((targetArg) => isHttpServiceTargetProtocol(targetArg.protocol))
|
|
57
|
+
|| targetPortsArg[0];
|
|
58
|
+
}
|
|
59
|
+
export function isHttpServiceTargetProtocol(protocolArg) {
|
|
60
|
+
return protocolArg === 'http' || protocolArg === 'https';
|
|
61
|
+
}
|
|
62
|
+
function normalizeServiceTargetPorts(serviceDataArg, serviceNameArg) {
|
|
63
|
+
const declaredTargets = [...(serviceDataArg.targetPorts || [])];
|
|
64
|
+
const legacyWebPort = Number(serviceDataArg.ports?.web);
|
|
65
|
+
if (declaredTargets.length === 0 && Number.isInteger(legacyWebPort) && legacyWebPort > 0) {
|
|
66
|
+
declaredTargets.push({
|
|
67
|
+
name: 'web',
|
|
68
|
+
port: legacyWebPort,
|
|
69
|
+
protocol: 'http',
|
|
70
|
+
default: true,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
if (declaredTargets.length === 0) {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
const seenNames = new Set();
|
|
77
|
+
const seenTuples = new Set();
|
|
78
|
+
const normalizedTargets = declaredTargets.map((targetArg) => {
|
|
79
|
+
const name = targetArg.name?.trim();
|
|
80
|
+
if (!name || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,62}$/.test(name)) {
|
|
81
|
+
throw new Error(`Invalid target port name '${targetArg.name}' on ${serviceNameArg}`);
|
|
82
|
+
}
|
|
83
|
+
const normalizedName = name.toLowerCase();
|
|
84
|
+
if (seenNames.has(normalizedName)) {
|
|
85
|
+
throw new Error(`Duplicate target port name '${name}' on ${serviceNameArg}`);
|
|
86
|
+
}
|
|
87
|
+
seenNames.add(normalizedName);
|
|
88
|
+
const port = assertValidServicePort(targetArg.port, `target port ${name} on ${serviceNameArg}`);
|
|
89
|
+
const portEnd = targetArg.portEnd === undefined
|
|
90
|
+
? undefined
|
|
91
|
+
: assertValidServicePort(targetArg.portEnd, `target port end ${name} on ${serviceNameArg}`);
|
|
92
|
+
if (portEnd !== undefined && portEnd < port) {
|
|
93
|
+
throw new Error(`target port range '${name}' on ${serviceNameArg} must be ascending`);
|
|
94
|
+
}
|
|
95
|
+
const protocol = targetArg.protocol || 'tcp';
|
|
96
|
+
const tuple = `${protocol}:${port}:${portEnd || port}`;
|
|
97
|
+
if (seenTuples.has(tuple)) {
|
|
98
|
+
throw new Error(`Duplicate target port tuple ${tuple} on ${serviceNameArg}`);
|
|
99
|
+
}
|
|
100
|
+
seenTuples.add(tuple);
|
|
101
|
+
return {
|
|
102
|
+
...targetArg,
|
|
103
|
+
name,
|
|
104
|
+
port,
|
|
105
|
+
portEnd,
|
|
106
|
+
protocol,
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
if (!normalizedTargets.some((targetArg) => targetArg.default)) {
|
|
110
|
+
const webTarget = normalizedTargets.find((targetArg) => targetArg.name === 'web');
|
|
111
|
+
if (webTarget) {
|
|
112
|
+
webTarget.default = true;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return normalizedTargets;
|
|
116
|
+
}
|
|
117
|
+
function resolveServiceDomainTarget(domainArg, targetPortsArg, defaultTargetPortArg, serviceNameArg, optionsArg) {
|
|
118
|
+
const domainName = domainArg.name || '(unnamed domain)';
|
|
119
|
+
if (!targetPortsArg.length) {
|
|
120
|
+
throw new Error(`Domain ${domainName} on ${serviceNameArg} requires at least one declared target port`);
|
|
121
|
+
}
|
|
122
|
+
if (domainArg.targetPort !== undefined) {
|
|
123
|
+
const target = resolveServiceTargetPort(targetPortsArg, domainArg.targetPort);
|
|
124
|
+
if (!target) {
|
|
125
|
+
throw new Error(`Domain ${domainName} on ${serviceNameArg} references undeclared targetPort '${domainArg.targetPort}'`);
|
|
126
|
+
}
|
|
127
|
+
return target;
|
|
128
|
+
}
|
|
129
|
+
if (domainArg.port !== undefined) {
|
|
130
|
+
if (!optionsArg.allowLegacyDomainPort) {
|
|
131
|
+
throw new Error(`Domain ${domainName} on ${serviceNameArg} uses legacy port; use targetPort instead`);
|
|
132
|
+
}
|
|
133
|
+
const legacyPort = Number(domainArg.port);
|
|
134
|
+
const target = resolveServiceTargetPort(targetPortsArg, legacyPort);
|
|
135
|
+
if (!target) {
|
|
136
|
+
throw new Error(`Domain ${domainName} on ${serviceNameArg} references undeclared legacy port ${domainArg.port}`);
|
|
137
|
+
}
|
|
138
|
+
return target;
|
|
139
|
+
}
|
|
140
|
+
if (!defaultTargetPortArg) {
|
|
141
|
+
throw new Error(`Domain ${domainName} on ${serviceNameArg} has no targetPort and no default target port exists`);
|
|
142
|
+
}
|
|
143
|
+
return defaultTargetPortArg;
|
|
144
|
+
}
|
|
145
|
+
function normalizeServicePublicPortMappings(mappingsArg, targetPortsArg, serviceNameArg) {
|
|
146
|
+
const seenNames = new Set();
|
|
147
|
+
const seenPublicPorts = new Set();
|
|
148
|
+
return mappingsArg.map((mappingArg) => {
|
|
149
|
+
const name = mappingArg.name?.trim();
|
|
150
|
+
if (!name || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,62}$/.test(name)) {
|
|
151
|
+
throw new Error(`Invalid public port mapping name '${mappingArg.name}' on ${serviceNameArg}`);
|
|
152
|
+
}
|
|
153
|
+
const normalizedName = name.toLowerCase();
|
|
154
|
+
if (seenNames.has(normalizedName)) {
|
|
155
|
+
throw new Error(`Duplicate public port mapping '${name}' on ${serviceNameArg}`);
|
|
156
|
+
}
|
|
157
|
+
seenNames.add(normalizedName);
|
|
158
|
+
if (mappingArg.exclusive !== true) {
|
|
159
|
+
throw new Error(`Public port mapping '${name}' on ${serviceNameArg} must be exclusive`);
|
|
160
|
+
}
|
|
161
|
+
if (!['tcp', 'udp'].includes(mappingArg.protocol)) {
|
|
162
|
+
throw new Error(`Public port mapping '${name}' on ${serviceNameArg} has unsupported protocol '${mappingArg.protocol}'`);
|
|
163
|
+
}
|
|
164
|
+
const target = resolveServiceTargetPort(targetPortsArg, mappingArg.targetPort);
|
|
165
|
+
if (!target) {
|
|
166
|
+
throw new Error(`Public port mapping '${name}' on ${serviceNameArg} references undeclared targetPort '${mappingArg.targetPort}'`);
|
|
167
|
+
}
|
|
168
|
+
const publicPort = assertValidServicePort(mappingArg.publicPort, `public port ${name} on ${serviceNameArg}`);
|
|
169
|
+
const targetStart = target.port;
|
|
170
|
+
const targetEnd = target.portEnd || target.port;
|
|
171
|
+
const publicPortEnd = mappingArg.publicPortEnd === undefined
|
|
172
|
+
? publicPort + (targetEnd - targetStart)
|
|
173
|
+
: assertValidServicePort(mappingArg.publicPortEnd, `public port end ${name} on ${serviceNameArg}`);
|
|
174
|
+
if (publicPortEnd < publicPort) {
|
|
175
|
+
throw new Error(`Public port mapping '${name}' on ${serviceNameArg} must have an ascending public range`);
|
|
176
|
+
}
|
|
177
|
+
if ((publicPortEnd - publicPort) !== (targetEnd - targetStart)) {
|
|
178
|
+
throw new Error(`Public and target port ranges must have the same length for mapping '${name}' on ${serviceNameArg}`);
|
|
179
|
+
}
|
|
180
|
+
for (let offset = 0; offset <= publicPortEnd - publicPort; offset++) {
|
|
181
|
+
const key = `${mappingArg.protocol}:${publicPort + offset}`;
|
|
182
|
+
if (seenPublicPorts.has(key)) {
|
|
183
|
+
throw new Error(`Duplicate public port ${publicPort + offset}/${mappingArg.protocol} on ${serviceNameArg}`);
|
|
184
|
+
}
|
|
185
|
+
seenPublicPorts.add(key);
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
...mappingArg,
|
|
189
|
+
name,
|
|
190
|
+
publicPort,
|
|
191
|
+
publicPortEnd,
|
|
192
|
+
protocol: mappingArg.protocol,
|
|
193
|
+
target,
|
|
194
|
+
targetPortStart: targetStart,
|
|
195
|
+
targetPortEnd: targetEnd,
|
|
196
|
+
};
|
|
197
|
+
}).sort((a, b) => {
|
|
198
|
+
return a.protocol.localeCompare(b.protocol)
|
|
199
|
+
|| a.publicPort - b.publicPort
|
|
200
|
+
|| a.name.localeCompare(b.name);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
function assertValidServicePort(portArg, labelArg) {
|
|
204
|
+
const port = Number(portArg);
|
|
205
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
206
|
+
throw new Error(`Invalid ${labelArg}: ${portArg}. Expected an integer between 1 and 65535.`);
|
|
207
|
+
}
|
|
208
|
+
return port;
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmljZXBvcnRzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvZGF0YS9zZXJ2aWNlcG9ydHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBbUZBLE1BQU0sVUFBVSwwQkFBMEIsQ0FDeEMsY0FBc0MsRUFDdEMsYUFBK0MsRUFBRTtJQUVqRCxNQUFNLE9BQU8sR0FBK0M7UUFDMUQscUJBQXFCLEVBQUUsVUFBVSxDQUFDLHFCQUFxQixJQUFJLElBQUk7UUFDL0Qsd0JBQXdCLEVBQUUsVUFBVSxDQUFDLHdCQUF3QixJQUFJLElBQUk7S0FDdEUsQ0FBQztJQUNGLE1BQU0sV0FBVyxHQUFHLGNBQWMsQ0FBQyxJQUFJLElBQUksU0FBUyxDQUFDO0lBQ3JELE1BQU0sV0FBVyxHQUFHLDJCQUEyQixDQUFDLGNBQWMsRUFBRSxXQUFXLENBQUMsQ0FBQztJQUM3RSxNQUFNLGlCQUFpQixHQUFHLCtCQUErQixDQUFDLFdBQVcsQ0FBQyxDQUFDO0lBQ3ZFLE1BQU0sT0FBTyxHQUFHLENBQUMsY0FBYyxDQUFDLE9BQU8sSUFBSSxFQUFFLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxTQUFTLEVBQUUsRUFBRTtRQUMvRCxNQUFNLE1BQU0sR0FBRywwQkFBMEIsQ0FBQyxTQUFTLEVBQUUsV0FBVyxFQUFFLGlCQUFpQixFQUFFLFdBQVcsRUFBRSxPQUFPLENBQUMsQ0FBQztRQUMzRyxJQUFJLE9BQU8sQ0FBQyx3QkFBd0IsSUFBSSxDQUFDLDJCQUEyQixDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDO1lBQ3RGLE1BQU0sSUFBSSxLQUFLLENBQUMsVUFBVSxTQUFTLENBQUMsSUFBSSxPQUFPLFdBQVcsZ0NBQWdDLE1BQU0sQ0FBQyxJQUFJLE1BQU0sTUFBTSxDQUFDLFFBQVEsR0FBRyxDQUFDLENBQUM7UUFDakksQ0FBQztRQUNELElBQUksTUFBTSxDQUFDLE9BQU8sSUFBSSxNQUFNLENBQUMsT0FBTyxLQUFLLE1BQU0sQ0FBQyxJ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
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import * as plugins from '../plugins.js';
|
|
2
2
|
interface IReverseProxyConfig extends plugins.tsclass.network.IReverseProxyConfig {
|
|
3
3
|
}
|
|
4
|
+
export interface ICoretrafficPortRouteConfig {
|
|
5
|
+
name: string;
|
|
6
|
+
listenPort: number;
|
|
7
|
+
transport: 'tcp' | 'udp';
|
|
8
|
+
destinationIps: string[];
|
|
9
|
+
destinationPort: number;
|
|
10
|
+
}
|
|
4
11
|
export { type IReverseProxyConfig };
|
package/dist_ts/data/traffic.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import * as plugins from '../plugins.js';
|
|
2
2
|
export {};
|
|
3
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHJhZmZpYy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2RhdGEvdHJhZmZpYy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGVBQWUsQ0FBQztBQVl6QyxPQUFPLEVBQTRCLENBQUMifQ==
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { type IReverseProxyConfig } from '../data/traffic.js';
|
|
1
|
+
import { type ICoretrafficPortRouteConfig, type IReverseProxyConfig } from '../data/traffic.js';
|
|
2
2
|
export interface IRequest_Coreflow_Coretraffic_RoutingUpdate {
|
|
3
3
|
method: 'updateRouting';
|
|
4
4
|
request: {
|
|
5
5
|
reverseConfigs: IReverseProxyConfig[];
|
|
6
|
+
portRoutes?: ICoretrafficPortRouteConfig[];
|
|
6
7
|
};
|
|
7
8
|
response: {
|
|
8
9
|
status: 'ok' | 'error';
|
|
@@ -19,6 +20,7 @@ export interface IRequest_Coretraffic_Coreflow_GetCurrentRouting {
|
|
|
19
20
|
request: Record<string, never>;
|
|
20
21
|
response: {
|
|
21
22
|
reverseConfigs: IReverseProxyConfig[];
|
|
23
|
+
portRoutes?: ICoretrafficPortRouteConfig[];
|
|
22
24
|
};
|
|
23
25
|
}
|
|
24
26
|
/**
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import {} from '../data/traffic.js';
|
|
2
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
2
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGluZy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL3JlcXVlc3RzL3JvdXRpbmcudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUE4RCxNQUFNLG9CQUFvQixDQUFDIn0=
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@serve.zone/interfaces",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.",
|
|
6
6
|
"exports": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@git.zone/tsbuild": "^4.4.2",
|
|
19
19
|
"@git.zone/tsdoc": "^2.1.1",
|
|
20
|
-
"@git.zone/tsrun": "^2.0.
|
|
20
|
+
"@git.zone/tsrun": "^2.0.5",
|
|
21
21
|
"@git.zone/tstest": "^3.6.6",
|
|
22
22
|
"@types/node": "^25.9.2"
|
|
23
23
|
},
|
package/readme.md
CHANGED
|
@@ -52,13 +52,36 @@ const service: data.IService = {
|
|
|
52
52
|
deploymentStrategy: 'limited-replicas',
|
|
53
53
|
scaleFactor: 2,
|
|
54
54
|
balancingStrategy: 'round-robin',
|
|
55
|
+
targetPorts: [
|
|
56
|
+
{
|
|
57
|
+
name: 'web',
|
|
58
|
+
port: 3000,
|
|
59
|
+
protocol: 'http',
|
|
60
|
+
default: true,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'ssh',
|
|
64
|
+
port: 2222,
|
|
65
|
+
protocol: 'ssh',
|
|
66
|
+
},
|
|
67
|
+
],
|
|
55
68
|
ports: {
|
|
56
|
-
web: 3000,
|
|
69
|
+
web: 3000, // legacy compatibility shorthand during migration
|
|
57
70
|
},
|
|
58
71
|
domains: [
|
|
59
72
|
{
|
|
60
73
|
name: 'api',
|
|
61
74
|
protocol: 'https',
|
|
75
|
+
targetPort: 'web',
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
publicPortMappings: [
|
|
79
|
+
{
|
|
80
|
+
name: 'ssh-public',
|
|
81
|
+
publicPort: 2222,
|
|
82
|
+
targetPort: 'ssh',
|
|
83
|
+
protocol: 'tcp',
|
|
84
|
+
exclusive: true,
|
|
62
85
|
},
|
|
63
86
|
],
|
|
64
87
|
deploymentIds: [],
|
|
@@ -70,13 +93,29 @@ Common data contracts include:
|
|
|
70
93
|
|
|
71
94
|
- `ICluster` and `IClusterNode` for cluster membership and provisioning state.
|
|
72
95
|
- `IService`, `IDeployment`, `IImage`, `IRegistryTarget`, and `IExternalRegistry` for workload delivery.
|
|
96
|
+
- Service port contracts including `IServiceTargetPort`, `IServiceDomainRoute`, and `IServicePublicPortMapping` for canonical backend targets, domain target references, and edge/Coretraffic TCP/UDP public exposure.
|
|
73
97
|
- `IDomain`, `IDnsEntry`, and traffic contracts for routing and DNS management.
|
|
98
|
+
- Traffic and gateway route contracts including `ICoretrafficPortRouteConfig`, routing `portRoutes`, gateway route `routeRef` ownership, optional match `domains`, `transport`, and `remoteIngress` route intent.
|
|
74
99
|
- `ISecretBundle` and `ISecretGroup` for secret ownership and shared secret groups.
|
|
75
100
|
- Mail gateway contracts for domain authorities, address bindings, WorkApp bindings, managed SMTP/API credentials, spool items, delivery journals, and inbound/outbound message payloads.
|
|
76
101
|
- Service-level mail configuration through `IService.data.mail`, including per-address inbound `smtpForward` settings and outbound credential metadata. Cloudly settings include dcrouter gateway, SMTP submission, and inbound forward-target keys for reconciling those bindings.
|
|
77
102
|
- `IUser`, `IIdentity`, and token-related contracts for authentication context.
|
|
78
103
|
- `ICloudlyConfig`, `ICloudlySettings`, status, server, bare-metal, BaseOS, backup, and task execution interfaces for control-plane state.
|
|
79
104
|
|
|
105
|
+
Shared service port helpers are exported from `data` so Cloudly, App Store resolution, Coreflow, Coretraffic, Onebox, and dcrouter agree on the same normalization rules:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
const normalizedPorts = data.normalizeServicePortConfig(service.data);
|
|
109
|
+
const defaultTarget = data.resolveDefaultServiceTargetPort(normalizedPorts.targetPorts);
|
|
110
|
+
const webTarget = data.resolveServiceTargetPort(normalizedPorts.targetPorts, 'web');
|
|
111
|
+
|
|
112
|
+
if (webTarget && data.isHttpServiceTargetProtocol(webTarget.protocol)) {
|
|
113
|
+
// safe to use as a domain route target
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`normalizeServicePortData()` returns service data with canonical `targetPorts`, domain `targetPort` refs, and `publicPortMappings` while removing legacy domain `port` fields from normalized writes.
|
|
118
|
+
|
|
80
119
|
## TypedRequest Contracts
|
|
81
120
|
|
|
82
121
|
Use `requests` when registering handlers with `@api.global/typedrequest` or when creating typed requests through a TypedSocket client.
|
|
@@ -111,16 +150,19 @@ Request groups are exported by product area:
|
|
|
111
150
|
- `requests.certificate`
|
|
112
151
|
- `requests.cluster`
|
|
113
152
|
- `requests.config`
|
|
153
|
+
- `requests.corestore`
|
|
114
154
|
- `requests.deployment`
|
|
115
155
|
- `requests.dns`
|
|
116
156
|
- `requests.domain`
|
|
117
157
|
- `requests.externalRegistry`
|
|
158
|
+
- `requests.gateway`
|
|
118
159
|
- `requests.hostedapp`
|
|
119
160
|
- `requests.identity`
|
|
120
161
|
- `requests.image`
|
|
121
162
|
- `requests.inform`
|
|
122
163
|
- `requests.log`
|
|
123
164
|
- `requests.mail`
|
|
165
|
+
- `requests.migration`
|
|
124
166
|
- `requests.network`
|
|
125
167
|
- `requests.node`
|
|
126
168
|
- `requests.platform`
|
|
@@ -181,7 +223,7 @@ Good candidates for this package:
|
|
|
181
223
|
Poor candidates for this package:
|
|
182
224
|
|
|
183
225
|
- Private implementation details of one service.
|
|
184
|
-
- Runtime helpers or convenience wrappers.
|
|
226
|
+
- Runtime helpers or convenience wrappers, unless they are shared contract normalization or validation helpers used by multiple packages.
|
|
185
227
|
- Compatibility aliases without an active consumer.
|
|
186
228
|
|
|
187
229
|
## Development
|
package/ts/00_commitinfo_data.ts
CHANGED
package/ts/appstore/types.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IServiceDomainRoute,
|
|
3
|
+
IServicePublicPortMapping,
|
|
4
|
+
IServiceTargetPort,
|
|
5
|
+
} from '../data/serviceports.js';
|
|
6
|
+
|
|
1
7
|
export type TAppStorePlatformRequirement = 'mongodb' | 's3' | 'clickhouse' | 'valkey' | 'mariadb';
|
|
2
8
|
export type TAppStoreSourceType = 'inline' | 'repoManifest' | 'dockerImage';
|
|
3
9
|
export type TAppStoreTrackingMode = 'tag' | 'digest';
|
|
@@ -114,7 +120,12 @@ export interface IAppStoreAppMeta {
|
|
|
114
120
|
|
|
115
121
|
export interface IAppStoreVersionConfig {
|
|
116
122
|
image: string;
|
|
117
|
-
|
|
123
|
+
/** Legacy shorthand for targetPorts.web. New templates should prefer targetPorts. */
|
|
124
|
+
port?: number;
|
|
125
|
+
targetPorts?: IServiceTargetPort[];
|
|
126
|
+
domains?: IServiceDomainRoute[];
|
|
127
|
+
/** Edge/coretraffic public TCP/UDP exposure, distinct from Docker publishedPorts. */
|
|
128
|
+
publicPortMappings?: IServicePublicPortMapping[];
|
|
118
129
|
envVars?: IAppStoreEnvVar[];
|
|
119
130
|
volumes?: TAppStoreVolumeSpec[];
|
|
120
131
|
publishedPorts?: IAppStorePublishedPort[];
|
|
@@ -175,8 +186,10 @@ export interface IAppStoreInstallRequest {
|
|
|
175
186
|
version?: string;
|
|
176
187
|
serviceName: string;
|
|
177
188
|
domain?: string;
|
|
189
|
+
/** Legacy install-time override for the web target port. */
|
|
178
190
|
port?: number;
|
|
179
191
|
publishedPorts?: IAppStorePublishedPort[];
|
|
192
|
+
publicPortMappings?: IServicePublicPortMapping[];
|
|
180
193
|
envVars?: Record<string, string>;
|
|
181
194
|
}
|
|
182
195
|
|
package/ts/data/gateway.ts
CHANGED
|
@@ -185,7 +185,10 @@ export interface IGatewayClientOwnership {
|
|
|
185
185
|
gatewayClientId?: string;
|
|
186
186
|
/** App identifier within the owning gateway client platform. */
|
|
187
187
|
appId: string;
|
|
188
|
-
|
|
188
|
+
/** Hostname ownership for domain routes. Preserved for existing route keys. */
|
|
189
|
+
hostname?: string;
|
|
190
|
+
/** Stable non-domain route key, for example public-port mapping exposure refs. */
|
|
191
|
+
routeRef?: string;
|
|
189
192
|
}
|
|
190
193
|
|
|
191
194
|
/**
|
|
@@ -201,7 +204,8 @@ export interface IGatewayRouteTarget {
|
|
|
201
204
|
*/
|
|
202
205
|
export interface IGatewayRouteMatch {
|
|
203
206
|
ports: number[];
|
|
204
|
-
domains
|
|
207
|
+
domains?: string[];
|
|
208
|
+
transport?: 'tcp' | 'udp' | 'all';
|
|
205
209
|
path?: string;
|
|
206
210
|
}
|
|
207
211
|
|
|
@@ -237,6 +241,10 @@ export interface IGatewayRouteConfig {
|
|
|
237
241
|
name: string;
|
|
238
242
|
match: IGatewayRouteMatch;
|
|
239
243
|
action: IGatewayRouteAction;
|
|
244
|
+
remoteIngress?: {
|
|
245
|
+
enabled: boolean;
|
|
246
|
+
edgeFilter?: string[];
|
|
247
|
+
};
|
|
240
248
|
}
|
|
241
249
|
|
|
242
250
|
/**
|
package/ts/data/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ export * from './backup.js';
|
|
|
21
21
|
export * from './clusternode.js';
|
|
22
22
|
export * from './settings.js';
|
|
23
23
|
export * from './service.js';
|
|
24
|
+
export * from './serviceports.js';
|
|
24
25
|
export * from './status.js';
|
|
25
26
|
export * from './taskexecution.js';
|
|
26
27
|
export * from './traffic.js';
|
package/ts/data/service.ts
CHANGED
|
@@ -3,6 +3,11 @@ import type { IRegistryTarget } from './registry.js';
|
|
|
3
3
|
import type { IAppStorePublishedPort } from '../appstore/index.js';
|
|
4
4
|
import type { IHostedAppLifecycleState } from './hostedapp.js';
|
|
5
5
|
import type { IServiceMailConfig } from './mail.js';
|
|
6
|
+
import type {
|
|
7
|
+
IServiceDomainRoute,
|
|
8
|
+
IServicePublicPortMapping,
|
|
9
|
+
IServiceTargetPort,
|
|
10
|
+
} from './serviceports.js';
|
|
6
11
|
|
|
7
12
|
export interface IServiceVolume {
|
|
8
13
|
/** Stable Docker volume name. If omitted, Coreflow derives one from service id and mount path. */
|
|
@@ -119,38 +124,30 @@ export interface IService {
|
|
|
119
124
|
|
|
120
125
|
scaleFactor: number;
|
|
121
126
|
balancingStrategy: 'round-robin' | 'least-connections';
|
|
127
|
+
/**
|
|
128
|
+
* Canonical backend/container ports for this service. New domain routes
|
|
129
|
+
* and public mappings reference these targets by name or exact port.
|
|
130
|
+
*/
|
|
131
|
+
targetPorts?: IServiceTargetPort[];
|
|
122
132
|
ports: {
|
|
133
|
+
/**
|
|
134
|
+
* Legacy main backend port. New writes should declare targetPorts and
|
|
135
|
+
* keep this only as compatibility shorthand during migration.
|
|
136
|
+
*/
|
|
123
137
|
web: number;
|
|
138
|
+
/**
|
|
139
|
+
* Legacy implicit domain-to-port routes. Runtime code must not treat
|
|
140
|
+
* this as a permissive routing source for new services.
|
|
141
|
+
*/
|
|
124
142
|
custom?: { [domain: string]: string };
|
|
125
143
|
};
|
|
126
144
|
mail?: IServiceMailConfig;
|
|
127
145
|
volumes?: IServiceVolume[];
|
|
128
146
|
publishedPorts?: IAppStorePublishedPort[];
|
|
147
|
+
/** Edge/coretraffic public TCP/UDP exposure, distinct from Docker Swarm publishedPorts. */
|
|
148
|
+
publicPortMappings?: IServicePublicPortMapping[];
|
|
129
149
|
resources?: IServiceRessources;
|
|
130
|
-
domains:
|
|
131
|
-
/**
|
|
132
|
-
* Optional domain ID to specify which domain to use
|
|
133
|
-
* If not specified, will use the default domain or require manual configuration
|
|
134
|
-
*/
|
|
135
|
-
domainId?: string;
|
|
136
|
-
/**
|
|
137
|
-
* The subdomain name (e.g., 'api', 'www', '@' for root)
|
|
138
|
-
*/
|
|
139
|
-
name: string;
|
|
140
|
-
/**
|
|
141
|
-
* The port to expose (defaults to ports.web if not specified)
|
|
142
|
-
*/
|
|
143
|
-
port?: number;
|
|
144
|
-
/**
|
|
145
|
-
* The protocol for this domain entry
|
|
146
|
-
*/
|
|
147
|
-
protocol?: 'http' | 'https' | 'ssh';
|
|
148
|
-
/**
|
|
149
|
-
* Gateway source profile for this domain's route (e.g. 'standard'
|
|
150
|
-
* for internal-only access). Defaults to the gateway's public profile.
|
|
151
|
-
*/
|
|
152
|
-
sourceProfileRef?: string;
|
|
153
|
-
}[];
|
|
150
|
+
domains: IServiceDomainRoute[];
|
|
154
151
|
deploymentIds: string[];
|
|
155
152
|
};
|
|
156
153
|
}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
export type TServiceTargetProtocol =
|
|
2
|
+
| 'http'
|
|
3
|
+
| 'https'
|
|
4
|
+
| 'ssh'
|
|
5
|
+
| 'tcp'
|
|
6
|
+
| 'udp'
|
|
7
|
+
| `custom:${string}`;
|
|
8
|
+
|
|
9
|
+
export type TServiceTargetPortRef = string | number;
|
|
10
|
+
|
|
11
|
+
export interface IServiceTargetPort {
|
|
12
|
+
name: string;
|
|
13
|
+
port: number;
|
|
14
|
+
portEnd?: number;
|
|
15
|
+
protocol: TServiceTargetProtocol;
|
|
16
|
+
description?: string;
|
|
17
|
+
default?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface IServiceDomainRoute {
|
|
21
|
+
domainId?: string;
|
|
22
|
+
name: string;
|
|
23
|
+
targetPort?: TServiceTargetPortRef;
|
|
24
|
+
/**
|
|
25
|
+
* Legacy backend target port. New writes must use targetPort.
|
|
26
|
+
*/
|
|
27
|
+
port?: number;
|
|
28
|
+
protocol?: 'http' | 'https';
|
|
29
|
+
sourceProfileRef?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface IServicePublicPortMapping {
|
|
33
|
+
name: string;
|
|
34
|
+
publicPort: number;
|
|
35
|
+
publicPortEnd?: number;
|
|
36
|
+
targetPort: TServiceTargetPortRef;
|
|
37
|
+
protocol: 'tcp' | 'udp';
|
|
38
|
+
sourceProfileRef?: string;
|
|
39
|
+
exclusive: true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface IResolvedServiceDomainRoute extends IServiceDomainRoute {
|
|
43
|
+
targetPort: TServiceTargetPortRef;
|
|
44
|
+
target: IServiceTargetPort;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface IResolvedServicePublicPortMapping extends IServicePublicPortMapping {
|
|
48
|
+
target: IServiceTargetPort;
|
|
49
|
+
targetPortStart: number;
|
|
50
|
+
targetPortEnd: number;
|
|
51
|
+
publicPortEnd: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface IServicePortSourceData {
|
|
55
|
+
name?: string;
|
|
56
|
+
ports?: {
|
|
57
|
+
web?: number;
|
|
58
|
+
custom?: Record<string, string>;
|
|
59
|
+
};
|
|
60
|
+
targetPorts?: IServiceTargetPort[];
|
|
61
|
+
domains?: IServiceDomainRoute[];
|
|
62
|
+
publicPortMappings?: IServicePublicPortMapping[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface INormalizedServicePortConfig {
|
|
66
|
+
targetPorts: IServiceTargetPort[];
|
|
67
|
+
defaultTargetPort?: IServiceTargetPort;
|
|
68
|
+
domains: IResolvedServiceDomainRoute[];
|
|
69
|
+
publicPortMappings: IResolvedServicePublicPortMapping[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface IServicePortNormalizationOptions {
|
|
73
|
+
/**
|
|
74
|
+
* Accept legacy domains[].port only when it resolves to a declared target.
|
|
75
|
+
* Defaults to true for staged migration reads.
|
|
76
|
+
*/
|
|
77
|
+
allowLegacyDomainPort?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Domain routes are HTTP(S) virtual-host routes. Defaults to true.
|
|
80
|
+
*/
|
|
81
|
+
requireHttpDomainTargets?: boolean;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function normalizeServicePortConfig(
|
|
85
|
+
serviceDataArg: IServicePortSourceData,
|
|
86
|
+
optionsArg: IServicePortNormalizationOptions = {},
|
|
87
|
+
): INormalizedServicePortConfig {
|
|
88
|
+
const options: Required<IServicePortNormalizationOptions> = {
|
|
89
|
+
allowLegacyDomainPort: optionsArg.allowLegacyDomainPort ?? true,
|
|
90
|
+
requireHttpDomainTargets: optionsArg.requireHttpDomainTargets ?? true,
|
|
91
|
+
};
|
|
92
|
+
const serviceName = serviceDataArg.name || 'service';
|
|
93
|
+
const targetPorts = normalizeServiceTargetPorts(serviceDataArg, serviceName);
|
|
94
|
+
const defaultTargetPort = resolveDefaultServiceTargetPort(targetPorts);
|
|
95
|
+
const domains = (serviceDataArg.domains || []).map((domainArg) => {
|
|
96
|
+
const target = resolveServiceDomainTarget(domainArg, targetPorts, defaultTargetPort, serviceName, options);
|
|
97
|
+
if (options.requireHttpDomainTargets && !isHttpServiceTargetProtocol(target.protocol)) {
|
|
98
|
+
throw new Error(`Domain ${domainArg.name} on ${serviceName} references non-HTTP target '${target.name}' (${target.protocol})`);
|
|
99
|
+
}
|
|
100
|
+
if (target.portEnd && target.portEnd !== target.port) {
|
|
101
|
+
throw new Error(`Domain ${domainArg.name} on ${serviceName} references ranged target '${target.name}'`);
|
|
102
|
+
}
|
|
103
|
+
const normalizedDomain: IResolvedServiceDomainRoute = {
|
|
104
|
+
...domainArg,
|
|
105
|
+
targetPort: domainArg.targetPort ?? target.name,
|
|
106
|
+
target,
|
|
107
|
+
};
|
|
108
|
+
delete normalizedDomain.port;
|
|
109
|
+
return normalizedDomain;
|
|
110
|
+
});
|
|
111
|
+
const publicPortMappings = normalizeServicePublicPortMappings(
|
|
112
|
+
serviceDataArg.publicPortMappings || [],
|
|
113
|
+
targetPorts,
|
|
114
|
+
serviceName,
|
|
115
|
+
);
|
|
116
|
+
return {
|
|
117
|
+
targetPorts,
|
|
118
|
+
defaultTargetPort,
|
|
119
|
+
domains,
|
|
120
|
+
publicPortMappings,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function normalizeServicePortData<T extends IServicePortSourceData>(
|
|
125
|
+
serviceDataArg: T,
|
|
126
|
+
optionsArg: IServicePortNormalizationOptions = {},
|
|
127
|
+
): T & {
|
|
128
|
+
targetPorts: IServiceTargetPort[];
|
|
129
|
+
domains?: IServiceDomainRoute[];
|
|
130
|
+
publicPortMappings?: IServicePublicPortMapping[];
|
|
131
|
+
} {
|
|
132
|
+
const normalized = normalizeServicePortConfig(serviceDataArg, optionsArg);
|
|
133
|
+
return {
|
|
134
|
+
...serviceDataArg,
|
|
135
|
+
targetPorts: normalized.targetPorts,
|
|
136
|
+
domains: normalized.domains.map(({ target, ...domainArg }) => domainArg),
|
|
137
|
+
publicPortMappings: normalized.publicPortMappings.map(({ target, targetPortStart, targetPortEnd, ...mappingArg }) => mappingArg),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function resolveServiceTargetPort(
|
|
142
|
+
targetPortsArg: IServiceTargetPort[],
|
|
143
|
+
refArg: TServiceTargetPortRef,
|
|
144
|
+
): IServiceTargetPort | undefined {
|
|
145
|
+
if (typeof refArg === 'string') {
|
|
146
|
+
const normalizedRef = refArg.trim().toLowerCase();
|
|
147
|
+
return targetPortsArg.find((targetArg) => targetArg.name.trim().toLowerCase() === normalizedRef);
|
|
148
|
+
}
|
|
149
|
+
return targetPortsArg.find((targetArg) => targetArg.port === refArg && !targetArg.portEnd);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function resolveDefaultServiceTargetPort(
|
|
153
|
+
targetPortsArg: IServiceTargetPort[],
|
|
154
|
+
): IServiceTargetPort | undefined {
|
|
155
|
+
const explicitDefaults = targetPortsArg.filter((targetArg) => targetArg.default);
|
|
156
|
+
if (explicitDefaults.length > 1) {
|
|
157
|
+
throw new Error(`Multiple default target ports declared: ${explicitDefaults.map((targetArg) => targetArg.name).join(', ')}`);
|
|
158
|
+
}
|
|
159
|
+
return explicitDefaults[0]
|
|
160
|
+
|| targetPortsArg.find((targetArg) => targetArg.name === 'web')
|
|
161
|
+
|| targetPortsArg.find((targetArg) => isHttpServiceTargetProtocol(targetArg.protocol))
|
|
162
|
+
|| targetPortsArg[0];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function isHttpServiceTargetProtocol(protocolArg: TServiceTargetProtocol): boolean {
|
|
166
|
+
return protocolArg === 'http' || protocolArg === 'https';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function normalizeServiceTargetPorts(
|
|
170
|
+
serviceDataArg: IServicePortSourceData,
|
|
171
|
+
serviceNameArg: string,
|
|
172
|
+
): IServiceTargetPort[] {
|
|
173
|
+
const declaredTargets = [...(serviceDataArg.targetPorts || [])];
|
|
174
|
+
const legacyWebPort = Number(serviceDataArg.ports?.web);
|
|
175
|
+
if (declaredTargets.length === 0 && Number.isInteger(legacyWebPort) && legacyWebPort > 0) {
|
|
176
|
+
declaredTargets.push({
|
|
177
|
+
name: 'web',
|
|
178
|
+
port: legacyWebPort,
|
|
179
|
+
protocol: 'http',
|
|
180
|
+
default: true,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
if (declaredTargets.length === 0) {
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const seenNames = new Set<string>();
|
|
188
|
+
const seenTuples = new Set<string>();
|
|
189
|
+
const normalizedTargets = declaredTargets.map((targetArg) => {
|
|
190
|
+
const name = targetArg.name?.trim();
|
|
191
|
+
if (!name || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,62}$/.test(name)) {
|
|
192
|
+
throw new Error(`Invalid target port name '${targetArg.name}' on ${serviceNameArg}`);
|
|
193
|
+
}
|
|
194
|
+
const normalizedName = name.toLowerCase();
|
|
195
|
+
if (seenNames.has(normalizedName)) {
|
|
196
|
+
throw new Error(`Duplicate target port name '${name}' on ${serviceNameArg}`);
|
|
197
|
+
}
|
|
198
|
+
seenNames.add(normalizedName);
|
|
199
|
+
const port = assertValidServicePort(targetArg.port, `target port ${name} on ${serviceNameArg}`);
|
|
200
|
+
const portEnd = targetArg.portEnd === undefined
|
|
201
|
+
? undefined
|
|
202
|
+
: assertValidServicePort(targetArg.portEnd, `target port end ${name} on ${serviceNameArg}`);
|
|
203
|
+
if (portEnd !== undefined && portEnd < port) {
|
|
204
|
+
throw new Error(`target port range '${name}' on ${serviceNameArg} must be ascending`);
|
|
205
|
+
}
|
|
206
|
+
const protocol = targetArg.protocol || 'tcp';
|
|
207
|
+
const tuple = `${protocol}:${port}:${portEnd || port}`;
|
|
208
|
+
if (seenTuples.has(tuple)) {
|
|
209
|
+
throw new Error(`Duplicate target port tuple ${tuple} on ${serviceNameArg}`);
|
|
210
|
+
}
|
|
211
|
+
seenTuples.add(tuple);
|
|
212
|
+
return {
|
|
213
|
+
...targetArg,
|
|
214
|
+
name,
|
|
215
|
+
port,
|
|
216
|
+
portEnd,
|
|
217
|
+
protocol,
|
|
218
|
+
};
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
if (!normalizedTargets.some((targetArg) => targetArg.default)) {
|
|
222
|
+
const webTarget = normalizedTargets.find((targetArg) => targetArg.name === 'web');
|
|
223
|
+
if (webTarget) {
|
|
224
|
+
webTarget.default = true;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return normalizedTargets;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function resolveServiceDomainTarget(
|
|
232
|
+
domainArg: IServiceDomainRoute,
|
|
233
|
+
targetPortsArg: IServiceTargetPort[],
|
|
234
|
+
defaultTargetPortArg: IServiceTargetPort | undefined,
|
|
235
|
+
serviceNameArg: string,
|
|
236
|
+
optionsArg: Required<IServicePortNormalizationOptions>,
|
|
237
|
+
): IServiceTargetPort {
|
|
238
|
+
const domainName = domainArg.name || '(unnamed domain)';
|
|
239
|
+
if (!targetPortsArg.length) {
|
|
240
|
+
throw new Error(`Domain ${domainName} on ${serviceNameArg} requires at least one declared target port`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (domainArg.targetPort !== undefined) {
|
|
244
|
+
const target = resolveServiceTargetPort(targetPortsArg, domainArg.targetPort);
|
|
245
|
+
if (!target) {
|
|
246
|
+
throw new Error(`Domain ${domainName} on ${serviceNameArg} references undeclared targetPort '${domainArg.targetPort}'`);
|
|
247
|
+
}
|
|
248
|
+
return target;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (domainArg.port !== undefined) {
|
|
252
|
+
if (!optionsArg.allowLegacyDomainPort) {
|
|
253
|
+
throw new Error(`Domain ${domainName} on ${serviceNameArg} uses legacy port; use targetPort instead`);
|
|
254
|
+
}
|
|
255
|
+
const legacyPort = Number(domainArg.port);
|
|
256
|
+
const target = resolveServiceTargetPort(targetPortsArg, legacyPort);
|
|
257
|
+
if (!target) {
|
|
258
|
+
throw new Error(`Domain ${domainName} on ${serviceNameArg} references undeclared legacy port ${domainArg.port}`);
|
|
259
|
+
}
|
|
260
|
+
return target;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (!defaultTargetPortArg) {
|
|
264
|
+
throw new Error(`Domain ${domainName} on ${serviceNameArg} has no targetPort and no default target port exists`);
|
|
265
|
+
}
|
|
266
|
+
return defaultTargetPortArg;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function normalizeServicePublicPortMappings(
|
|
270
|
+
mappingsArg: IServicePublicPortMapping[],
|
|
271
|
+
targetPortsArg: IServiceTargetPort[],
|
|
272
|
+
serviceNameArg: string,
|
|
273
|
+
): IResolvedServicePublicPortMapping[] {
|
|
274
|
+
const seenNames = new Set<string>();
|
|
275
|
+
const seenPublicPorts = new Set<string>();
|
|
276
|
+
return mappingsArg.map((mappingArg) => {
|
|
277
|
+
const name = mappingArg.name?.trim();
|
|
278
|
+
if (!name || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,62}$/.test(name)) {
|
|
279
|
+
throw new Error(`Invalid public port mapping name '${mappingArg.name}' on ${serviceNameArg}`);
|
|
280
|
+
}
|
|
281
|
+
const normalizedName = name.toLowerCase();
|
|
282
|
+
if (seenNames.has(normalizedName)) {
|
|
283
|
+
throw new Error(`Duplicate public port mapping '${name}' on ${serviceNameArg}`);
|
|
284
|
+
}
|
|
285
|
+
seenNames.add(normalizedName);
|
|
286
|
+
if (mappingArg.exclusive !== true) {
|
|
287
|
+
throw new Error(`Public port mapping '${name}' on ${serviceNameArg} must be exclusive`);
|
|
288
|
+
}
|
|
289
|
+
if (!['tcp', 'udp'].includes(mappingArg.protocol)) {
|
|
290
|
+
throw new Error(`Public port mapping '${name}' on ${serviceNameArg} has unsupported protocol '${mappingArg.protocol}'`);
|
|
291
|
+
}
|
|
292
|
+
const target = resolveServiceTargetPort(targetPortsArg, mappingArg.targetPort);
|
|
293
|
+
if (!target) {
|
|
294
|
+
throw new Error(`Public port mapping '${name}' on ${serviceNameArg} references undeclared targetPort '${mappingArg.targetPort}'`);
|
|
295
|
+
}
|
|
296
|
+
const publicPort = assertValidServicePort(mappingArg.publicPort, `public port ${name} on ${serviceNameArg}`);
|
|
297
|
+
const targetStart = target.port;
|
|
298
|
+
const targetEnd = target.portEnd || target.port;
|
|
299
|
+
const publicPortEnd = mappingArg.publicPortEnd === undefined
|
|
300
|
+
? publicPort + (targetEnd - targetStart)
|
|
301
|
+
: assertValidServicePort(mappingArg.publicPortEnd, `public port end ${name} on ${serviceNameArg}`);
|
|
302
|
+
if (publicPortEnd < publicPort) {
|
|
303
|
+
throw new Error(`Public port mapping '${name}' on ${serviceNameArg} must have an ascending public range`);
|
|
304
|
+
}
|
|
305
|
+
if ((publicPortEnd - publicPort) !== (targetEnd - targetStart)) {
|
|
306
|
+
throw new Error(`Public and target port ranges must have the same length for mapping '${name}' on ${serviceNameArg}`);
|
|
307
|
+
}
|
|
308
|
+
for (let offset = 0; offset <= publicPortEnd - publicPort; offset++) {
|
|
309
|
+
const key = `${mappingArg.protocol}:${publicPort + offset}`;
|
|
310
|
+
if (seenPublicPorts.has(key)) {
|
|
311
|
+
throw new Error(`Duplicate public port ${publicPort + offset}/${mappingArg.protocol} on ${serviceNameArg}`);
|
|
312
|
+
}
|
|
313
|
+
seenPublicPorts.add(key);
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
...mappingArg,
|
|
317
|
+
name,
|
|
318
|
+
publicPort,
|
|
319
|
+
publicPortEnd,
|
|
320
|
+
protocol: mappingArg.protocol,
|
|
321
|
+
target,
|
|
322
|
+
targetPortStart: targetStart,
|
|
323
|
+
targetPortEnd: targetEnd,
|
|
324
|
+
};
|
|
325
|
+
}).sort((a, b) => {
|
|
326
|
+
return a.protocol.localeCompare(b.protocol)
|
|
327
|
+
|| a.publicPort - b.publicPort
|
|
328
|
+
|| a.name.localeCompare(b.name);
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function assertValidServicePort(portArg: number, labelArg: string): number {
|
|
333
|
+
const port = Number(portArg);
|
|
334
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
335
|
+
throw new Error(`Invalid ${labelArg}: ${portArg}. Expected an integer between 1 and 65535.`);
|
|
336
|
+
}
|
|
337
|
+
return port;
|
|
338
|
+
}
|
package/ts/data/traffic.ts
CHANGED
|
@@ -2,4 +2,12 @@ import * as plugins from '../plugins.js';
|
|
|
2
2
|
|
|
3
3
|
interface IReverseProxyConfig extends plugins.tsclass.network.IReverseProxyConfig {}
|
|
4
4
|
|
|
5
|
+
export interface ICoretrafficPortRouteConfig {
|
|
6
|
+
name: string;
|
|
7
|
+
listenPort: number;
|
|
8
|
+
transport: 'tcp' | 'udp';
|
|
9
|
+
destinationIps: string[];
|
|
10
|
+
destinationPort: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
5
13
|
export { type IReverseProxyConfig };
|
package/ts/requests/routing.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { type IReverseProxyConfig } from '../data/traffic.js';
|
|
1
|
+
import { type ICoretrafficPortRouteConfig, type IReverseProxyConfig } from '../data/traffic.js';
|
|
2
2
|
|
|
3
3
|
export interface IRequest_Coreflow_Coretraffic_RoutingUpdate {
|
|
4
4
|
method: 'updateRouting';
|
|
5
5
|
request: {
|
|
6
6
|
reverseConfigs: IReverseProxyConfig[];
|
|
7
|
+
portRoutes?: ICoretrafficPortRouteConfig[];
|
|
7
8
|
};
|
|
8
9
|
response: {
|
|
9
10
|
status: 'ok' | 'error';
|
|
@@ -21,6 +22,7 @@ export interface IRequest_Coretraffic_Coreflow_GetCurrentRouting {
|
|
|
21
22
|
request: Record<string, never>;
|
|
22
23
|
response: {
|
|
23
24
|
reverseConfigs: IReverseProxyConfig[];
|
|
25
|
+
portRoutes?: ICoretrafficPortRouteConfig[];
|
|
24
26
|
};
|
|
25
27
|
}
|
|
26
28
|
|