vovk 3.0.0-draft.154 → 3.0.0-draft.156
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/cjs/client/createRPC.d.ts +1 -1
- package/cjs/client/createRPC.js +5 -5
- package/cjs/index.d.ts +2 -1
- package/cjs/index.js +3 -1
- package/cjs/utils/multitenant.d.ts +24 -0
- package/cjs/utils/multitenant.js +170 -0
- package/mjs/client/createRPC.d.ts +1 -1
- package/mjs/client/createRPC.js +5 -5
- package/mjs/index.d.ts +2 -1
- package/mjs/index.js +3 -1
- package/mjs/utils/multitenant.d.ts +24 -0
- package/mjs/utils/multitenant.js +170 -0
- package/package.json +1 -1
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { KnownAny, VovkSchema } from '../types.js';
|
|
2
2
|
import type { VovkClientOptions, VovkClient, VovkDefaultFetcherOptions } from './types.js';
|
|
3
|
-
export declare const createRPC: <T, OPTS extends Record<string, KnownAny> = VovkDefaultFetcherOptions<Record<string, never>>>(schema: VovkSchema,
|
|
3
|
+
export declare const createRPC: <T, OPTS extends Record<string, KnownAny> = VovkDefaultFetcherOptions<Record<string, never>>>(schema: VovkSchema, segmentName: string, rpcModuleName: string, options?: VovkClientOptions<OPTS>) => VovkClient<T, OPTS>;
|
package/cjs/client/createRPC.js
CHANGED
|
@@ -17,8 +17,8 @@ const getHandlerPath = (endpoint, params, query) => {
|
|
|
17
17
|
}
|
|
18
18
|
return `${result}${queryStr ? '?' : ''}${queryStr}`;
|
|
19
19
|
};
|
|
20
|
-
const createRPC = (schema,
|
|
21
|
-
const
|
|
20
|
+
const createRPC = (schema, segmentName, rpcModuleName, options) => {
|
|
21
|
+
const segmentNamePath = options?.segmentNameOverride ?? segmentName;
|
|
22
22
|
const segmentSchema = schema.segments[segmentName];
|
|
23
23
|
if (!segmentSchema)
|
|
24
24
|
throw new Error(`Unable to create RPC object. Segment schema is missing. Check client template.`);
|
|
@@ -34,10 +34,10 @@ const createRPC = (schema, givenSegmentName, rpcModuleName, options) => {
|
|
|
34
34
|
const mainPrefix = [
|
|
35
35
|
apiRoot.startsWith('http://') || apiRoot.startsWith('https://') || apiRoot.startsWith('/') ? '' : '/',
|
|
36
36
|
apiRoot,
|
|
37
|
-
|
|
37
|
+
segmentNamePath,
|
|
38
38
|
]
|
|
39
39
|
.filter(Boolean)
|
|
40
|
-
.join('/');
|
|
40
|
+
.join('/') + '/';
|
|
41
41
|
return mainPrefix + getHandlerPath([controllerPrefix, path].filter(Boolean).join('/'), params, query);
|
|
42
42
|
};
|
|
43
43
|
const handler = (input = {}) => {
|
|
@@ -83,7 +83,7 @@ const createRPC = (schema, givenSegmentName, rpcModuleName, options) => {
|
|
|
83
83
|
handler.segmentSchema = segmentSchema;
|
|
84
84
|
handler.fullSchema = schema;
|
|
85
85
|
handler.isRPC = true;
|
|
86
|
-
handler.path = [
|
|
86
|
+
handler.path = [segmentNamePath, controllerPrefix, path].filter(Boolean).join('/');
|
|
87
87
|
// @ts-expect-error TODO
|
|
88
88
|
client[staticMethodName] = handler;
|
|
89
89
|
}
|
package/cjs/index.d.ts
CHANGED
|
@@ -7,7 +7,8 @@ import { createDecorator } from './createDecorator.js';
|
|
|
7
7
|
import { JSONLinesResponse } from './JSONLinesResponse.js';
|
|
8
8
|
import { generateStaticAPI } from './utils/generateStaticAPI.js';
|
|
9
9
|
import { withValidation } from './utils/withValidation.js';
|
|
10
|
-
|
|
10
|
+
import { multitenant } from './utils/multitenant.js';
|
|
11
|
+
export { type KnownAny, type VovkClient, type VovkClientFetcher, type VovkDefaultFetcherOptions, type VovkStreamAsyncIterable, type VovkValidateOnClient, type VovkSegmentSchema, type VovkErrorResponse, type VovkRequest, type VovkOutput, type VovkIteration, type VovkBody, type VovkQuery, type VovkParams, type VovkYieldType, type VovkReturnType, type VovkClientOptions, type VovkControllerSchema, type VovkHandlerSchema, type VovkSchema, type VovkConfig, type VovkStrictConfig, type VovkValidationType, VovkSchemaIdEnum, JSONLinesResponse, HttpException, HttpStatus, HttpMethod, createVovkApp, createDecorator, createRPC, fetcher, createFetcher, generateStaticAPI, withValidation, multitenant, };
|
|
11
12
|
export declare const get: {
|
|
12
13
|
(givenPath?: string | undefined, options?: import("./types.js").DecoratorOptions | undefined): ReturnType<(givenPath?: string, options?: import("./types.js").DecoratorOptions) => (givenTarget: KnownAny, propertyKey: string) => void>;
|
|
13
14
|
auto: (options?: import("./types.js").DecoratorOptions) => (givenTarget: KnownAny, propertyKey: string) => void;
|
package/cjs/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var _a;
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.initVovk = exports.prefix = exports.options = exports.head = exports.del = exports.patch = exports.put = exports.post = exports.get = exports.withValidation = exports.generateStaticAPI = exports.createFetcher = exports.fetcher = exports.createRPC = exports.createDecorator = exports.createVovkApp = exports.HttpMethod = exports.HttpStatus = exports.HttpException = exports.JSONLinesResponse = exports.VovkSchemaIdEnum = void 0;
|
|
4
|
+
exports.initVovk = exports.prefix = exports.options = exports.head = exports.del = exports.patch = exports.put = exports.post = exports.get = exports.multitenant = exports.withValidation = exports.generateStaticAPI = exports.createFetcher = exports.fetcher = exports.createRPC = exports.createDecorator = exports.createVovkApp = exports.HttpMethod = exports.HttpStatus = exports.HttpException = exports.JSONLinesResponse = exports.VovkSchemaIdEnum = void 0;
|
|
5
5
|
const createVovkApp_js_1 = require("./createVovkApp.js");
|
|
6
6
|
Object.defineProperty(exports, "createVovkApp", { enumerable: true, get: function () { return createVovkApp_js_1.createVovkApp; } });
|
|
7
7
|
const types_js_1 = require("./types.js");
|
|
@@ -22,4 +22,6 @@ const generateStaticAPI_js_1 = require("./utils/generateStaticAPI.js");
|
|
|
22
22
|
Object.defineProperty(exports, "generateStaticAPI", { enumerable: true, get: function () { return generateStaticAPI_js_1.generateStaticAPI; } });
|
|
23
23
|
const withValidation_js_1 = require("./utils/withValidation.js");
|
|
24
24
|
Object.defineProperty(exports, "withValidation", { enumerable: true, get: function () { return withValidation_js_1.withValidation; } });
|
|
25
|
+
const multitenant_js_1 = require("./utils/multitenant.js");
|
|
26
|
+
Object.defineProperty(exports, "multitenant", { enumerable: true, get: function () { return multitenant_js_1.multitenant; } });
|
|
25
27
|
_a = (0, createVovkApp_js_1.createVovkApp)(), exports.get = _a.get, exports.post = _a.post, exports.put = _a.put, exports.patch = _a.patch, exports.del = _a.del, exports.head = _a.head, exports.options = _a.options, exports.prefix = _a.prefix, exports.initVovk = _a.initVovk;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
type Override = {
|
|
2
|
+
from: string;
|
|
3
|
+
to: string;
|
|
4
|
+
};
|
|
5
|
+
type Config = {
|
|
6
|
+
requestUrl: string;
|
|
7
|
+
requestHost: string;
|
|
8
|
+
targetHost: string;
|
|
9
|
+
overrides: {
|
|
10
|
+
[key: string]: Override[];
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
export declare function multitenant(config: Config): {
|
|
14
|
+
action: null;
|
|
15
|
+
destination: null;
|
|
16
|
+
message: string;
|
|
17
|
+
subdomains: null;
|
|
18
|
+
} | {
|
|
19
|
+
action: string;
|
|
20
|
+
destination: string;
|
|
21
|
+
message: string;
|
|
22
|
+
subdomains: Record<string, string> | null;
|
|
23
|
+
};
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Function "multitenant" accepts an object with "requestUrl", "requestHost", "targetHost" and "overrides" properties.
|
|
4
|
+
The requestHost parameter comes from request headers and should be used instead of the hostname in the requestUrl.
|
|
5
|
+
The keys in overrides are subdomains relative to the "targetHost". They can include a dot to indicate lower-level
|
|
6
|
+
subdomains, and can use square brackets to indicate wildcard values. The values are arrays of objects with "from"
|
|
7
|
+
and "to" properties, which are strings indicating the override paths for Next.js.
|
|
8
|
+
|
|
9
|
+
If a path matches a specific subdomain name (e.g., "/admin" matches the "admin" subdomain),
|
|
10
|
+
the request will be redirected to that specific subdomain instead of being processed on the current subdomain.
|
|
11
|
+
|
|
12
|
+
If a path ends with "_schema_", it will not be processed by any overrides and will be passed through as-is.
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
|
|
16
|
+
const { action, destination, message, subdomains } = multitenant({
|
|
17
|
+
requestUrl: request.url,
|
|
18
|
+
requestHost: request.headers.get('host'),
|
|
19
|
+
targetHost: "localhost:3000",
|
|
20
|
+
overrides: {
|
|
21
|
+
"[customer_name].customer": [
|
|
22
|
+
{ from: "api", to: "api/customer" }, // API
|
|
23
|
+
{ from: "", to: "[customer_name]" } // UI
|
|
24
|
+
],
|
|
25
|
+
"pro.[customer_name].customer": [
|
|
26
|
+
{ from: "api", to: "api/customer/pro" }, // API
|
|
27
|
+
{ from: "", to: "pro/[customer_name]" } // UI
|
|
28
|
+
],
|
|
29
|
+
"admin": [
|
|
30
|
+
{ from: "api", to: "api/admin" }, // API
|
|
31
|
+
{ from: "", to: "admin" } // UI
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
subdomains is a Record<string, string> | null where keys are subdomain names and values are their corresponding values. If non-wildcard subdomains are used, should be equal to null.
|
|
37
|
+
action is one of "rewrite", "redirect", "notfound", or null.
|
|
38
|
+
message is a string describing the action taken
|
|
39
|
+
destination is a string URL to redirect or rewrite to, or null if no action is taken or "notfound"
|
|
40
|
+
*/
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.multitenant = multitenant;
|
|
43
|
+
// Get the reserved paths from the overrides configuration
|
|
44
|
+
const getReservedPaths = (overrides) => {
|
|
45
|
+
return Object.keys(overrides).filter((key) => !key.includes('[') && !key.includes(']')); // Filter out dynamic paths
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Convert a pattern with [placeholders] to a regex pattern and extract placeholder names
|
|
49
|
+
*/
|
|
50
|
+
const patternToRegex = (pattern) => {
|
|
51
|
+
const paramNames = [];
|
|
52
|
+
const regexPattern = pattern
|
|
53
|
+
.replace(/\[([^\]]+)\]/g, (_, name) => {
|
|
54
|
+
paramNames.push(name);
|
|
55
|
+
return '([^.]+)';
|
|
56
|
+
})
|
|
57
|
+
.replace(/\./g, '\\.'); // Escape dots in the pattern
|
|
58
|
+
return {
|
|
59
|
+
regex: new RegExp(`^${regexPattern}$`),
|
|
60
|
+
paramNames,
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
function multitenant(config) {
|
|
64
|
+
const { requestUrl, requestHost, targetHost, overrides } = config;
|
|
65
|
+
// Parse the URL
|
|
66
|
+
const urlObj = new URL(requestUrl);
|
|
67
|
+
const pathname = urlObj.pathname.slice(1); // Remove leading slash
|
|
68
|
+
// Skip processing for paths ending with "_schema_"
|
|
69
|
+
if (pathname.endsWith('_schema_')) {
|
|
70
|
+
return {
|
|
71
|
+
action: null,
|
|
72
|
+
destination: null,
|
|
73
|
+
message: 'Schema endpoint, bypassing overrides',
|
|
74
|
+
subdomains: null,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const pathSegments = pathname.split('/').filter(Boolean);
|
|
78
|
+
// Get reserved paths
|
|
79
|
+
const reservedPaths = getReservedPaths(overrides);
|
|
80
|
+
// Check if any path segment matches a reserved path (e.g., "admin")
|
|
81
|
+
for (let i = 0; i < pathSegments.length; i++) {
|
|
82
|
+
const segment = pathSegments[i];
|
|
83
|
+
if (reservedPaths.includes(segment)) {
|
|
84
|
+
// Create the destination URL with the reserved path as subdomain
|
|
85
|
+
const destinationHost = `${segment}.${targetHost}`;
|
|
86
|
+
// Keep path segments before the reserved path
|
|
87
|
+
const beforeSegments = pathSegments.slice(0, i);
|
|
88
|
+
// Keep path segments after the reserved path
|
|
89
|
+
const afterSegments = pathSegments.slice(i + 1);
|
|
90
|
+
// Construct the new path
|
|
91
|
+
const newPath = [...beforeSegments, ...afterSegments].join('/');
|
|
92
|
+
const destinationUrl = new URL(`${urlObj.protocol}//${destinationHost}`);
|
|
93
|
+
if (newPath) {
|
|
94
|
+
destinationUrl.pathname = `/${newPath}`;
|
|
95
|
+
}
|
|
96
|
+
// Keep any query parameters
|
|
97
|
+
destinationUrl.search = urlObj.search;
|
|
98
|
+
return {
|
|
99
|
+
action: 'redirect',
|
|
100
|
+
destination: destinationUrl.toString(),
|
|
101
|
+
message: `Redirecting to ${segment} subdomain`,
|
|
102
|
+
subdomains: null, // No wildcards used
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Process based on host and subdomains
|
|
107
|
+
for (const pattern in overrides) {
|
|
108
|
+
const fullPattern = `${pattern}.${targetHost}`;
|
|
109
|
+
const { regex, paramNames } = patternToRegex(fullPattern);
|
|
110
|
+
const match = requestHost.match(regex);
|
|
111
|
+
if (match) {
|
|
112
|
+
const overrideRules = overrides[pattern];
|
|
113
|
+
// Extract parameters from the match
|
|
114
|
+
const params = {};
|
|
115
|
+
if (match.length > 1) {
|
|
116
|
+
for (let i = 0; i < paramNames.length; i++) {
|
|
117
|
+
params[paramNames[i]] = match[i + 1];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Find the appropriate rule based on the path
|
|
121
|
+
for (const rule of overrideRules) {
|
|
122
|
+
if (pathname === rule.from || pathname.startsWith(`${rule.from}/`)) {
|
|
123
|
+
// Replace path with the destination
|
|
124
|
+
let destination = pathname.replace(rule.from, rule.to);
|
|
125
|
+
// Replace any dynamic parameters in destination
|
|
126
|
+
if (Object.keys(params).length > 0) {
|
|
127
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
128
|
+
destination = destination.replace(`[${key}]`, value);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
// Only return non-null subdomains if we have wildcard parameters
|
|
132
|
+
const wildcardSubdomains = paramNames.length > 0 ? params : null;
|
|
133
|
+
return {
|
|
134
|
+
action: 'rewrite',
|
|
135
|
+
destination: `${urlObj.protocol}//${urlObj.host}/${destination}${urlObj.search}`,
|
|
136
|
+
message: `Rewriting to ${destination}`,
|
|
137
|
+
subdomains: wildcardSubdomains,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Handle cases where a customer subdomain tries to access reserved paths
|
|
144
|
+
if (pathSegments.length > 0 && reservedPaths.includes(pathSegments[0])) {
|
|
145
|
+
const reservedPath = pathSegments[0];
|
|
146
|
+
const restPath = pathSegments.slice(1).join('/');
|
|
147
|
+
// Create the destination URL with the reserved path as subdomain
|
|
148
|
+
const destinationHost = `${reservedPath}.${targetHost}`;
|
|
149
|
+
const destinationUrl = new URL(`${urlObj.protocol}//${destinationHost}`);
|
|
150
|
+
// Only add remaining path segments if they exist
|
|
151
|
+
if (restPath) {
|
|
152
|
+
destinationUrl.pathname = `/${restPath}`;
|
|
153
|
+
}
|
|
154
|
+
// Keep any query parameters
|
|
155
|
+
destinationUrl.search = urlObj.search;
|
|
156
|
+
return {
|
|
157
|
+
action: 'redirect',
|
|
158
|
+
destination: destinationUrl.toString(),
|
|
159
|
+
message: `Redirecting to ${reservedPath} subdomain`,
|
|
160
|
+
subdomains: null, // No wildcards used for reserved paths
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
// Default case - pass through
|
|
164
|
+
return {
|
|
165
|
+
action: null,
|
|
166
|
+
destination: null,
|
|
167
|
+
message: 'No action',
|
|
168
|
+
subdomains: null, // No wildcards matched
|
|
169
|
+
};
|
|
170
|
+
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { KnownAny, VovkSchema } from '../types.js';
|
|
2
2
|
import type { VovkClientOptions, VovkClient, VovkDefaultFetcherOptions } from './types.js';
|
|
3
|
-
export declare const createRPC: <T, OPTS extends Record<string, KnownAny> = VovkDefaultFetcherOptions<Record<string, never>>>(schema: VovkSchema,
|
|
3
|
+
export declare const createRPC: <T, OPTS extends Record<string, KnownAny> = VovkDefaultFetcherOptions<Record<string, never>>>(schema: VovkSchema, segmentName: string, rpcModuleName: string, options?: VovkClientOptions<OPTS>) => VovkClient<T, OPTS>;
|
package/mjs/client/createRPC.js
CHANGED
|
@@ -17,8 +17,8 @@ const getHandlerPath = (endpoint, params, query) => {
|
|
|
17
17
|
}
|
|
18
18
|
return `${result}${queryStr ? '?' : ''}${queryStr}`;
|
|
19
19
|
};
|
|
20
|
-
const createRPC = (schema,
|
|
21
|
-
const
|
|
20
|
+
const createRPC = (schema, segmentName, rpcModuleName, options) => {
|
|
21
|
+
const segmentNamePath = options?.segmentNameOverride ?? segmentName;
|
|
22
22
|
const segmentSchema = schema.segments[segmentName];
|
|
23
23
|
if (!segmentSchema)
|
|
24
24
|
throw new Error(`Unable to create RPC object. Segment schema is missing. Check client template.`);
|
|
@@ -34,10 +34,10 @@ const createRPC = (schema, givenSegmentName, rpcModuleName, options) => {
|
|
|
34
34
|
const mainPrefix = [
|
|
35
35
|
apiRoot.startsWith('http://') || apiRoot.startsWith('https://') || apiRoot.startsWith('/') ? '' : '/',
|
|
36
36
|
apiRoot,
|
|
37
|
-
|
|
37
|
+
segmentNamePath,
|
|
38
38
|
]
|
|
39
39
|
.filter(Boolean)
|
|
40
|
-
.join('/');
|
|
40
|
+
.join('/') + '/';
|
|
41
41
|
return mainPrefix + getHandlerPath([controllerPrefix, path].filter(Boolean).join('/'), params, query);
|
|
42
42
|
};
|
|
43
43
|
const handler = (input = {}) => {
|
|
@@ -83,7 +83,7 @@ const createRPC = (schema, givenSegmentName, rpcModuleName, options) => {
|
|
|
83
83
|
handler.segmentSchema = segmentSchema;
|
|
84
84
|
handler.fullSchema = schema;
|
|
85
85
|
handler.isRPC = true;
|
|
86
|
-
handler.path = [
|
|
86
|
+
handler.path = [segmentNamePath, controllerPrefix, path].filter(Boolean).join('/');
|
|
87
87
|
// @ts-expect-error TODO
|
|
88
88
|
client[staticMethodName] = handler;
|
|
89
89
|
}
|
package/mjs/index.d.ts
CHANGED
|
@@ -7,7 +7,8 @@ import { createDecorator } from './createDecorator.js';
|
|
|
7
7
|
import { JSONLinesResponse } from './JSONLinesResponse.js';
|
|
8
8
|
import { generateStaticAPI } from './utils/generateStaticAPI.js';
|
|
9
9
|
import { withValidation } from './utils/withValidation.js';
|
|
10
|
-
|
|
10
|
+
import { multitenant } from './utils/multitenant.js';
|
|
11
|
+
export { type KnownAny, type VovkClient, type VovkClientFetcher, type VovkDefaultFetcherOptions, type VovkStreamAsyncIterable, type VovkValidateOnClient, type VovkSegmentSchema, type VovkErrorResponse, type VovkRequest, type VovkOutput, type VovkIteration, type VovkBody, type VovkQuery, type VovkParams, type VovkYieldType, type VovkReturnType, type VovkClientOptions, type VovkControllerSchema, type VovkHandlerSchema, type VovkSchema, type VovkConfig, type VovkStrictConfig, type VovkValidationType, VovkSchemaIdEnum, JSONLinesResponse, HttpException, HttpStatus, HttpMethod, createVovkApp, createDecorator, createRPC, fetcher, createFetcher, generateStaticAPI, withValidation, multitenant, };
|
|
11
12
|
export declare const get: {
|
|
12
13
|
(givenPath?: string | undefined, options?: import("./types.js").DecoratorOptions | undefined): ReturnType<(givenPath?: string, options?: import("./types.js").DecoratorOptions) => (givenTarget: KnownAny, propertyKey: string) => void>;
|
|
13
14
|
auto: (options?: import("./types.js").DecoratorOptions) => (givenTarget: KnownAny, propertyKey: string) => void;
|
package/mjs/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var _a;
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.initVovk = exports.prefix = exports.options = exports.head = exports.del = exports.patch = exports.put = exports.post = exports.get = exports.withValidation = exports.generateStaticAPI = exports.createFetcher = exports.fetcher = exports.createRPC = exports.createDecorator = exports.createVovkApp = exports.HttpMethod = exports.HttpStatus = exports.HttpException = exports.JSONLinesResponse = exports.VovkSchemaIdEnum = void 0;
|
|
4
|
+
exports.initVovk = exports.prefix = exports.options = exports.head = exports.del = exports.patch = exports.put = exports.post = exports.get = exports.multitenant = exports.withValidation = exports.generateStaticAPI = exports.createFetcher = exports.fetcher = exports.createRPC = exports.createDecorator = exports.createVovkApp = exports.HttpMethod = exports.HttpStatus = exports.HttpException = exports.JSONLinesResponse = exports.VovkSchemaIdEnum = void 0;
|
|
5
5
|
const createVovkApp_js_1 = require("./createVovkApp.js");
|
|
6
6
|
Object.defineProperty(exports, "createVovkApp", { enumerable: true, get: function () { return createVovkApp_js_1.createVovkApp; } });
|
|
7
7
|
const types_js_1 = require("./types.js");
|
|
@@ -22,4 +22,6 @@ const generateStaticAPI_js_1 = require("./utils/generateStaticAPI.js");
|
|
|
22
22
|
Object.defineProperty(exports, "generateStaticAPI", { enumerable: true, get: function () { return generateStaticAPI_js_1.generateStaticAPI; } });
|
|
23
23
|
const withValidation_js_1 = require("./utils/withValidation.js");
|
|
24
24
|
Object.defineProperty(exports, "withValidation", { enumerable: true, get: function () { return withValidation_js_1.withValidation; } });
|
|
25
|
+
const multitenant_js_1 = require("./utils/multitenant.js");
|
|
26
|
+
Object.defineProperty(exports, "multitenant", { enumerable: true, get: function () { return multitenant_js_1.multitenant; } });
|
|
25
27
|
_a = (0, createVovkApp_js_1.createVovkApp)(), exports.get = _a.get, exports.post = _a.post, exports.put = _a.put, exports.patch = _a.patch, exports.del = _a.del, exports.head = _a.head, exports.options = _a.options, exports.prefix = _a.prefix, exports.initVovk = _a.initVovk;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
type Override = {
|
|
2
|
+
from: string;
|
|
3
|
+
to: string;
|
|
4
|
+
};
|
|
5
|
+
type Config = {
|
|
6
|
+
requestUrl: string;
|
|
7
|
+
requestHost: string;
|
|
8
|
+
targetHost: string;
|
|
9
|
+
overrides: {
|
|
10
|
+
[key: string]: Override[];
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
export declare function multitenant(config: Config): {
|
|
14
|
+
action: null;
|
|
15
|
+
destination: null;
|
|
16
|
+
message: string;
|
|
17
|
+
subdomains: null;
|
|
18
|
+
} | {
|
|
19
|
+
action: string;
|
|
20
|
+
destination: string;
|
|
21
|
+
message: string;
|
|
22
|
+
subdomains: Record<string, string> | null;
|
|
23
|
+
};
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Function "multitenant" accepts an object with "requestUrl", "requestHost", "targetHost" and "overrides" properties.
|
|
4
|
+
The requestHost parameter comes from request headers and should be used instead of the hostname in the requestUrl.
|
|
5
|
+
The keys in overrides are subdomains relative to the "targetHost". They can include a dot to indicate lower-level
|
|
6
|
+
subdomains, and can use square brackets to indicate wildcard values. The values are arrays of objects with "from"
|
|
7
|
+
and "to" properties, which are strings indicating the override paths for Next.js.
|
|
8
|
+
|
|
9
|
+
If a path matches a specific subdomain name (e.g., "/admin" matches the "admin" subdomain),
|
|
10
|
+
the request will be redirected to that specific subdomain instead of being processed on the current subdomain.
|
|
11
|
+
|
|
12
|
+
If a path ends with "_schema_", it will not be processed by any overrides and will be passed through as-is.
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
|
|
16
|
+
const { action, destination, message, subdomains } = multitenant({
|
|
17
|
+
requestUrl: request.url,
|
|
18
|
+
requestHost: request.headers.get('host'),
|
|
19
|
+
targetHost: "localhost:3000",
|
|
20
|
+
overrides: {
|
|
21
|
+
"[customer_name].customer": [
|
|
22
|
+
{ from: "api", to: "api/customer" }, // API
|
|
23
|
+
{ from: "", to: "[customer_name]" } // UI
|
|
24
|
+
],
|
|
25
|
+
"pro.[customer_name].customer": [
|
|
26
|
+
{ from: "api", to: "api/customer/pro" }, // API
|
|
27
|
+
{ from: "", to: "pro/[customer_name]" } // UI
|
|
28
|
+
],
|
|
29
|
+
"admin": [
|
|
30
|
+
{ from: "api", to: "api/admin" }, // API
|
|
31
|
+
{ from: "", to: "admin" } // UI
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
subdomains is a Record<string, string> | null where keys are subdomain names and values are their corresponding values. If non-wildcard subdomains are used, should be equal to null.
|
|
37
|
+
action is one of "rewrite", "redirect", "notfound", or null.
|
|
38
|
+
message is a string describing the action taken
|
|
39
|
+
destination is a string URL to redirect or rewrite to, or null if no action is taken or "notfound"
|
|
40
|
+
*/
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.multitenant = multitenant;
|
|
43
|
+
// Get the reserved paths from the overrides configuration
|
|
44
|
+
const getReservedPaths = (overrides) => {
|
|
45
|
+
return Object.keys(overrides).filter((key) => !key.includes('[') && !key.includes(']')); // Filter out dynamic paths
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Convert a pattern with [placeholders] to a regex pattern and extract placeholder names
|
|
49
|
+
*/
|
|
50
|
+
const patternToRegex = (pattern) => {
|
|
51
|
+
const paramNames = [];
|
|
52
|
+
const regexPattern = pattern
|
|
53
|
+
.replace(/\[([^\]]+)\]/g, (_, name) => {
|
|
54
|
+
paramNames.push(name);
|
|
55
|
+
return '([^.]+)';
|
|
56
|
+
})
|
|
57
|
+
.replace(/\./g, '\\.'); // Escape dots in the pattern
|
|
58
|
+
return {
|
|
59
|
+
regex: new RegExp(`^${regexPattern}$`),
|
|
60
|
+
paramNames,
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
function multitenant(config) {
|
|
64
|
+
const { requestUrl, requestHost, targetHost, overrides } = config;
|
|
65
|
+
// Parse the URL
|
|
66
|
+
const urlObj = new URL(requestUrl);
|
|
67
|
+
const pathname = urlObj.pathname.slice(1); // Remove leading slash
|
|
68
|
+
// Skip processing for paths ending with "_schema_"
|
|
69
|
+
if (pathname.endsWith('_schema_')) {
|
|
70
|
+
return {
|
|
71
|
+
action: null,
|
|
72
|
+
destination: null,
|
|
73
|
+
message: 'Schema endpoint, bypassing overrides',
|
|
74
|
+
subdomains: null,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const pathSegments = pathname.split('/').filter(Boolean);
|
|
78
|
+
// Get reserved paths
|
|
79
|
+
const reservedPaths = getReservedPaths(overrides);
|
|
80
|
+
// Check if any path segment matches a reserved path (e.g., "admin")
|
|
81
|
+
for (let i = 0; i < pathSegments.length; i++) {
|
|
82
|
+
const segment = pathSegments[i];
|
|
83
|
+
if (reservedPaths.includes(segment)) {
|
|
84
|
+
// Create the destination URL with the reserved path as subdomain
|
|
85
|
+
const destinationHost = `${segment}.${targetHost}`;
|
|
86
|
+
// Keep path segments before the reserved path
|
|
87
|
+
const beforeSegments = pathSegments.slice(0, i);
|
|
88
|
+
// Keep path segments after the reserved path
|
|
89
|
+
const afterSegments = pathSegments.slice(i + 1);
|
|
90
|
+
// Construct the new path
|
|
91
|
+
const newPath = [...beforeSegments, ...afterSegments].join('/');
|
|
92
|
+
const destinationUrl = new URL(`${urlObj.protocol}//${destinationHost}`);
|
|
93
|
+
if (newPath) {
|
|
94
|
+
destinationUrl.pathname = `/${newPath}`;
|
|
95
|
+
}
|
|
96
|
+
// Keep any query parameters
|
|
97
|
+
destinationUrl.search = urlObj.search;
|
|
98
|
+
return {
|
|
99
|
+
action: 'redirect',
|
|
100
|
+
destination: destinationUrl.toString(),
|
|
101
|
+
message: `Redirecting to ${segment} subdomain`,
|
|
102
|
+
subdomains: null, // No wildcards used
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Process based on host and subdomains
|
|
107
|
+
for (const pattern in overrides) {
|
|
108
|
+
const fullPattern = `${pattern}.${targetHost}`;
|
|
109
|
+
const { regex, paramNames } = patternToRegex(fullPattern);
|
|
110
|
+
const match = requestHost.match(regex);
|
|
111
|
+
if (match) {
|
|
112
|
+
const overrideRules = overrides[pattern];
|
|
113
|
+
// Extract parameters from the match
|
|
114
|
+
const params = {};
|
|
115
|
+
if (match.length > 1) {
|
|
116
|
+
for (let i = 0; i < paramNames.length; i++) {
|
|
117
|
+
params[paramNames[i]] = match[i + 1];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Find the appropriate rule based on the path
|
|
121
|
+
for (const rule of overrideRules) {
|
|
122
|
+
if (pathname === rule.from || pathname.startsWith(`${rule.from}/`)) {
|
|
123
|
+
// Replace path with the destination
|
|
124
|
+
let destination = pathname.replace(rule.from, rule.to);
|
|
125
|
+
// Replace any dynamic parameters in destination
|
|
126
|
+
if (Object.keys(params).length > 0) {
|
|
127
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
128
|
+
destination = destination.replace(`[${key}]`, value);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
// Only return non-null subdomains if we have wildcard parameters
|
|
132
|
+
const wildcardSubdomains = paramNames.length > 0 ? params : null;
|
|
133
|
+
return {
|
|
134
|
+
action: 'rewrite',
|
|
135
|
+
destination: `${urlObj.protocol}//${urlObj.host}/${destination}${urlObj.search}`,
|
|
136
|
+
message: `Rewriting to ${destination}`,
|
|
137
|
+
subdomains: wildcardSubdomains,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Handle cases where a customer subdomain tries to access reserved paths
|
|
144
|
+
if (pathSegments.length > 0 && reservedPaths.includes(pathSegments[0])) {
|
|
145
|
+
const reservedPath = pathSegments[0];
|
|
146
|
+
const restPath = pathSegments.slice(1).join('/');
|
|
147
|
+
// Create the destination URL with the reserved path as subdomain
|
|
148
|
+
const destinationHost = `${reservedPath}.${targetHost}`;
|
|
149
|
+
const destinationUrl = new URL(`${urlObj.protocol}//${destinationHost}`);
|
|
150
|
+
// Only add remaining path segments if they exist
|
|
151
|
+
if (restPath) {
|
|
152
|
+
destinationUrl.pathname = `/${restPath}`;
|
|
153
|
+
}
|
|
154
|
+
// Keep any query parameters
|
|
155
|
+
destinationUrl.search = urlObj.search;
|
|
156
|
+
return {
|
|
157
|
+
action: 'redirect',
|
|
158
|
+
destination: destinationUrl.toString(),
|
|
159
|
+
message: `Redirecting to ${reservedPath} subdomain`,
|
|
160
|
+
subdomains: null, // No wildcards used for reserved paths
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
// Default case - pass through
|
|
164
|
+
return {
|
|
165
|
+
action: null,
|
|
166
|
+
destination: null,
|
|
167
|
+
message: 'No action',
|
|
168
|
+
subdomains: null, // No wildcards matched
|
|
169
|
+
};
|
|
170
|
+
}
|