vovk 3.0.0-draft.33 → 3.0.0-draft.331
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/LICENSE +1 -1
- package/README.md +8 -96
- package/bin/index.mjs +8 -0
- package/{HttpException.d.ts → cjs/HttpException.d.ts} +2 -2
- package/{HttpException.js → cjs/HttpException.js} +3 -3
- package/cjs/JSONLinesResponse.d.ts +19 -0
- package/cjs/JSONLinesResponse.js +93 -0
- package/{VovkApp.d.ts → cjs/VovkApp.d.ts} +10 -9
- package/cjs/VovkApp.js +202 -0
- package/cjs/client/createRPC.d.ts +3 -0
- package/cjs/client/createRPC.js +90 -0
- package/cjs/client/defaultHandler.d.ts +6 -0
- package/cjs/client/defaultHandler.js +29 -0
- package/cjs/client/defaultStreamHandler.d.ts +9 -0
- package/{client → cjs/client}/defaultStreamHandler.js +25 -13
- package/cjs/client/fetcher.d.ts +14 -0
- package/cjs/client/fetcher.js +93 -0
- package/cjs/client/index.d.ts +4 -0
- package/cjs/client/index.js +10 -0
- package/cjs/client/progressive.d.ts +9 -0
- package/cjs/client/progressive.js +45 -0
- package/cjs/client/types.d.ts +123 -0
- package/{createVovkApp.d.ts → cjs/createVovkApp.d.ts} +12 -11
- package/cjs/createVovkApp.js +133 -0
- package/cjs/index.d.ts +65 -0
- package/cjs/index.js +38 -0
- package/cjs/openapi/error.d.ts +2 -0
- package/cjs/openapi/error.js +100 -0
- package/cjs/openapi/generateFnName.d.ts +23 -0
- package/cjs/openapi/generateFnName.js +81 -0
- package/cjs/openapi/index.d.ts +12 -0
- package/cjs/openapi/index.js +21 -0
- package/cjs/openapi/openAPIToVovkSchema/applyComponentsSchemas.d.ts +3 -0
- package/cjs/openapi/openAPIToVovkSchema/applyComponentsSchemas.js +67 -0
- package/cjs/openapi/openAPIToVovkSchema/index.d.ts +4 -0
- package/cjs/openapi/openAPIToVovkSchema/index.js +192 -0
- package/cjs/openapi/openAPIToVovkSchema/inlineRefs.d.ts +10 -0
- package/cjs/openapi/openAPIToVovkSchema/inlineRefs.js +102 -0
- package/cjs/openapi/vovkSchemaToOpenAPI.d.ts +9 -0
- package/cjs/openapi/vovkSchemaToOpenAPI.js +233 -0
- package/cjs/types.d.ts +400 -0
- package/cjs/types.js +74 -0
- package/cjs/utils/camelCase.d.ts +6 -0
- package/cjs/utils/camelCase.js +37 -0
- package/cjs/utils/createCodeExamples.d.ts +19 -0
- package/cjs/utils/createCodeExamples.js +110 -0
- package/cjs/utils/createDecorator.d.ts +6 -0
- package/{createDecorator.js → cjs/utils/createDecorator.js} +26 -16
- package/cjs/utils/createLLMTools.d.ts +44 -0
- package/cjs/utils/createLLMTools.js +118 -0
- package/cjs/utils/createStandardValidation.d.ts +81 -0
- package/cjs/utils/createStandardValidation.js +33 -0
- package/cjs/utils/generateStaticAPI.d.ts +4 -0
- package/cjs/utils/generateStaticAPI.js +30 -0
- package/cjs/utils/getJSONSchemaExample.d.ts +8 -0
- package/cjs/utils/getJSONSchemaExample.js +234 -0
- package/cjs/utils/getJSONSchemaSample.d.ts +2 -0
- package/cjs/utils/getJSONSchemaSample.js +167 -0
- package/cjs/utils/getSchema.d.ts +21 -0
- package/cjs/utils/getSchema.js +38 -0
- package/cjs/utils/multitenant.d.ts +24 -0
- package/cjs/utils/multitenant.js +170 -0
- package/cjs/utils/parseQuery.d.ts +25 -0
- package/cjs/utils/parseQuery.js +156 -0
- package/{utils → cjs/utils}/reqForm.d.ts +1 -2
- package/cjs/utils/reqForm.js +33 -0
- package/{utils → cjs/utils}/reqMeta.d.ts +1 -2
- package/cjs/utils/reqQuery.d.ts +2 -0
- package/cjs/utils/reqQuery.js +10 -0
- package/cjs/utils/serializeQuery.d.ts +13 -0
- package/cjs/utils/serializeQuery.js +65 -0
- package/cjs/utils/setHandlerSchema.d.ts +4 -0
- package/cjs/utils/setHandlerSchema.js +15 -0
- package/cjs/utils/upperFirst.d.ts +1 -0
- package/cjs/utils/upperFirst.js +6 -0
- package/cjs/utils/withValidationLibrary.d.ts +76 -0
- package/cjs/utils/withValidationLibrary.js +123 -0
- package/mjs/HttpException.d.ts +7 -0
- package/mjs/HttpException.js +15 -0
- package/mjs/JSONLinesResponse.d.ts +19 -0
- package/mjs/JSONLinesResponse.js +93 -0
- package/mjs/VovkApp.d.ts +29 -0
- package/mjs/VovkApp.js +202 -0
- package/mjs/client/createRPC.d.ts +3 -0
- package/mjs/client/createRPC.js +90 -0
- package/mjs/client/defaultHandler.d.ts +6 -0
- package/mjs/client/defaultHandler.js +29 -0
- package/mjs/client/defaultStreamHandler.d.ts +9 -0
- package/mjs/client/defaultStreamHandler.js +94 -0
- package/mjs/client/fetcher.d.ts +14 -0
- package/mjs/client/fetcher.js +93 -0
- package/mjs/client/index.d.ts +4 -0
- package/mjs/client/index.js +10 -0
- package/mjs/client/progressive.d.ts +9 -0
- package/mjs/client/progressive.js +45 -0
- package/mjs/client/types.d.ts +123 -0
- package/mjs/createVovkApp.d.ts +63 -0
- package/mjs/createVovkApp.js +133 -0
- package/mjs/index.d.ts +65 -0
- package/mjs/index.js +38 -0
- package/mjs/openapi/error.d.ts +2 -0
- package/mjs/openapi/error.js +100 -0
- package/mjs/openapi/generateFnName.d.ts +23 -0
- package/mjs/openapi/generateFnName.js +81 -0
- package/mjs/openapi/index.d.ts +12 -0
- package/mjs/openapi/index.js +21 -0
- package/mjs/openapi/openAPIToVovkSchema/applyComponentsSchemas.d.ts +3 -0
- package/mjs/openapi/openAPIToVovkSchema/applyComponentsSchemas.js +67 -0
- package/mjs/openapi/openAPIToVovkSchema/index.d.ts +4 -0
- package/mjs/openapi/openAPIToVovkSchema/index.js +192 -0
- package/mjs/openapi/openAPIToVovkSchema/inlineRefs.d.ts +10 -0
- package/mjs/openapi/openAPIToVovkSchema/inlineRefs.js +102 -0
- package/mjs/openapi/vovkSchemaToOpenAPI.d.ts +9 -0
- package/mjs/openapi/vovkSchemaToOpenAPI.js +233 -0
- package/mjs/types.d.ts +400 -0
- package/mjs/types.js +74 -0
- package/mjs/utils/camelCase.d.ts +6 -0
- package/mjs/utils/camelCase.js +37 -0
- package/mjs/utils/createCodeExamples.d.ts +19 -0
- package/mjs/utils/createCodeExamples.js +110 -0
- package/mjs/utils/createDecorator.d.ts +6 -0
- package/mjs/utils/createDecorator.js +48 -0
- package/mjs/utils/createLLMTools.d.ts +44 -0
- package/mjs/utils/createLLMTools.js +118 -0
- package/mjs/utils/createStandardValidation.d.ts +81 -0
- package/mjs/utils/createStandardValidation.js +33 -0
- package/mjs/utils/generateStaticAPI.d.ts +4 -0
- package/mjs/utils/generateStaticAPI.js +30 -0
- package/mjs/utils/getJSONSchemaExample.d.ts +8 -0
- package/mjs/utils/getJSONSchemaExample.js +234 -0
- package/mjs/utils/getJSONSchemaSample.d.ts +2 -0
- package/mjs/utils/getJSONSchemaSample.js +167 -0
- package/mjs/utils/getSchema.d.ts +21 -0
- package/mjs/utils/getSchema.js +38 -0
- package/mjs/utils/multitenant.d.ts +24 -0
- package/mjs/utils/multitenant.js +170 -0
- package/mjs/utils/parseQuery.d.ts +25 -0
- package/mjs/utils/parseQuery.js +156 -0
- package/mjs/utils/reqForm.d.ts +2 -0
- package/mjs/utils/reqForm.js +33 -0
- package/mjs/utils/reqMeta.d.ts +2 -0
- package/mjs/utils/reqMeta.js +13 -0
- package/mjs/utils/reqQuery.d.ts +2 -0
- package/mjs/utils/reqQuery.js +10 -0
- package/mjs/utils/serializeQuery.d.ts +13 -0
- package/mjs/utils/serializeQuery.js +65 -0
- package/mjs/utils/setHandlerSchema.d.ts +4 -0
- package/mjs/utils/setHandlerSchema.js +15 -0
- package/mjs/utils/shim.d.ts +1 -0
- package/mjs/utils/shim.js +18 -0
- package/mjs/utils/upperFirst.d.ts +1 -0
- package/mjs/utils/upperFirst.js +6 -0
- package/mjs/utils/withValidationLibrary.d.ts +76 -0
- package/mjs/utils/withValidationLibrary.js +123 -0
- package/package.json +29 -6
- package/.npmignore +0 -2
- package/StreamJSONResponse.d.ts +0 -17
- package/StreamJSONResponse.js +0 -54
- package/VovkApp.js +0 -185
- package/client/clientizeController.d.ts +0 -4
- package/client/clientizeController.js +0 -92
- package/client/defaultFetcher.d.ts +0 -4
- package/client/defaultFetcher.js +0 -49
- package/client/defaultHandler.d.ts +0 -2
- package/client/defaultHandler.js +0 -22
- package/client/defaultStreamHandler.d.ts +0 -4
- package/client/index.d.ts +0 -4
- package/client/index.js +0 -5
- package/client/types.d.ts +0 -100
- package/createDecorator.d.ts +0 -4
- package/createVovkApp.js +0 -118
- package/index.d.ts +0 -60
- package/index.js +0 -20
- package/types.d.ts +0 -157
- package/types.js +0 -65
- package/utils/generateStaticAPI.d.ts +0 -4
- package/utils/generateStaticAPI.js +0 -18
- package/utils/getSchema.d.ts +0 -8
- package/utils/getSchema.js +0 -38
- package/utils/reqForm.js +0 -13
- package/utils/reqQuery.d.ts +0 -3
- package/utils/reqQuery.js +0 -25
- package/utils/setClientValidatorsForHandler.d.ts +0 -5
- package/utils/setClientValidatorsForHandler.js +0 -25
- package/worker/index.d.ts +0 -3
- package/worker/index.js +0 -7
- package/worker/promisifyWorker.d.ts +0 -2
- package/worker/promisifyWorker.js +0 -141
- package/worker/types.d.ts +0 -31
- package/worker/worker.d.ts +0 -1
- package/worker/worker.js +0 -43
- /package/{client → cjs/client}/types.js +0 -0
- /package/{utils → cjs/utils}/reqMeta.js +0 -0
- /package/{utils → cjs/utils}/shim.d.ts +0 -0
- /package/{utils → cjs/utils}/shim.js +0 -0
- /package/{worker → mjs/client}/types.js +0 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getJSONSchemaSample = getJSONSchemaSample;
|
|
4
|
+
function getJSONSchemaSample(schema, rootSchema) {
|
|
5
|
+
if (!schema || typeof schema !== 'object')
|
|
6
|
+
return null;
|
|
7
|
+
// Use the input schema as the root if not provided
|
|
8
|
+
rootSchema = rootSchema || schema;
|
|
9
|
+
// If there's an example, use it
|
|
10
|
+
if (schema.example !== undefined) {
|
|
11
|
+
return schema.example;
|
|
12
|
+
}
|
|
13
|
+
// If there are examples, use one of them
|
|
14
|
+
if (schema.examples && schema.examples.length > 0) {
|
|
15
|
+
return schema.examples[0];
|
|
16
|
+
}
|
|
17
|
+
// Handle const if present
|
|
18
|
+
if (schema.const !== undefined) {
|
|
19
|
+
return schema.const;
|
|
20
|
+
}
|
|
21
|
+
// Handle $ref if present
|
|
22
|
+
if (schema.$ref) {
|
|
23
|
+
return handleRef(schema.$ref, rootSchema);
|
|
24
|
+
}
|
|
25
|
+
// Handle enum if present
|
|
26
|
+
if (schema.enum && schema.enum.length > 0) {
|
|
27
|
+
return schema.enum[0];
|
|
28
|
+
}
|
|
29
|
+
// Handle oneOf, anyOf, allOf
|
|
30
|
+
if (schema.oneOf && schema.oneOf.length > 0) {
|
|
31
|
+
return getJSONSchemaSample(schema.oneOf[0], rootSchema);
|
|
32
|
+
}
|
|
33
|
+
if (schema.anyOf && schema.anyOf.length > 0) {
|
|
34
|
+
return getJSONSchemaSample(schema.anyOf[0], rootSchema);
|
|
35
|
+
}
|
|
36
|
+
if (schema.allOf && schema.allOf.length > 0) {
|
|
37
|
+
// Merge all schemas in allOf
|
|
38
|
+
const mergedSchema = schema.allOf.reduce((acc, s) => ({ ...acc, ...s }), {});
|
|
39
|
+
return getJSONSchemaSample(mergedSchema, rootSchema);
|
|
40
|
+
}
|
|
41
|
+
// Handle different types
|
|
42
|
+
if (schema.type) {
|
|
43
|
+
switch (schema.type) {
|
|
44
|
+
case 'string':
|
|
45
|
+
return handleString(schema);
|
|
46
|
+
case 'number':
|
|
47
|
+
case 'integer':
|
|
48
|
+
return handleNumber(schema);
|
|
49
|
+
case 'boolean':
|
|
50
|
+
return handleBoolean();
|
|
51
|
+
case 'object':
|
|
52
|
+
return handleObject(schema, rootSchema);
|
|
53
|
+
case 'array':
|
|
54
|
+
return handleArray(schema, rootSchema);
|
|
55
|
+
case 'null':
|
|
56
|
+
return null;
|
|
57
|
+
default:
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// If type is not specified but properties are, treat it as an object
|
|
62
|
+
if (schema.properties) {
|
|
63
|
+
return handleObject(schema, rootSchema);
|
|
64
|
+
}
|
|
65
|
+
// Default fallback
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
function handleRef(ref, rootSchema) {
|
|
69
|
+
// Parse the reference path
|
|
70
|
+
const path = ref.split('/').slice(1); // Remove the initial '#'
|
|
71
|
+
// Navigate through the schema to find the referenced definition
|
|
72
|
+
let current = rootSchema;
|
|
73
|
+
for (const segment of path) {
|
|
74
|
+
current = current[segment];
|
|
75
|
+
if (current === undefined) {
|
|
76
|
+
return null; // Reference not found
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Process the referenced schema
|
|
80
|
+
return getJSONSchemaSample(current, rootSchema);
|
|
81
|
+
}
|
|
82
|
+
function handleString(schema) {
|
|
83
|
+
if (schema.format) {
|
|
84
|
+
switch (schema.format) {
|
|
85
|
+
case 'email':
|
|
86
|
+
case 'idn-email':
|
|
87
|
+
return 'user@example.com';
|
|
88
|
+
case 'uri':
|
|
89
|
+
case 'url':
|
|
90
|
+
case 'iri':
|
|
91
|
+
return 'https://example.com';
|
|
92
|
+
case 'date':
|
|
93
|
+
return '2023-01-01';
|
|
94
|
+
case 'date-time':
|
|
95
|
+
return '2023-01-01T00:00:00Z';
|
|
96
|
+
case 'time':
|
|
97
|
+
return '12:00:00Z';
|
|
98
|
+
case 'duration':
|
|
99
|
+
return 'PT1H';
|
|
100
|
+
case 'uuid':
|
|
101
|
+
return '00000000-0000-0000-0000-000000000000';
|
|
102
|
+
case 'regex':
|
|
103
|
+
return '^[a-zA-Z0-9]+$';
|
|
104
|
+
case 'relative-json-pointer':
|
|
105
|
+
return '/some/relative/path';
|
|
106
|
+
case 'color':
|
|
107
|
+
return '#000000';
|
|
108
|
+
case 'hostname':
|
|
109
|
+
return 'example.com';
|
|
110
|
+
case 'zipcode':
|
|
111
|
+
return '12345';
|
|
112
|
+
case 'phone':
|
|
113
|
+
return '+123-456-7890';
|
|
114
|
+
case 'password':
|
|
115
|
+
return '******';
|
|
116
|
+
default:
|
|
117
|
+
return 'string';
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (schema.pattern) {
|
|
121
|
+
// For simplicity, return a basic string for patterns
|
|
122
|
+
return 'pattern-string';
|
|
123
|
+
}
|
|
124
|
+
return 'string';
|
|
125
|
+
}
|
|
126
|
+
function handleNumber(schema) {
|
|
127
|
+
if (schema.minimum !== undefined && schema.maximum !== undefined) {
|
|
128
|
+
return schema.minimum;
|
|
129
|
+
}
|
|
130
|
+
else if (schema.minimum !== undefined) {
|
|
131
|
+
return schema.minimum;
|
|
132
|
+
}
|
|
133
|
+
else if (schema.maximum !== undefined) {
|
|
134
|
+
return schema.maximum;
|
|
135
|
+
}
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
function handleBoolean() {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
function handleObject(schema, rootSchema) {
|
|
142
|
+
const result = {};
|
|
143
|
+
if (schema.properties) {
|
|
144
|
+
const required = schema.required || [];
|
|
145
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
146
|
+
// Only include required properties or as a basic example
|
|
147
|
+
if (required.includes(key) || required.length === 0) {
|
|
148
|
+
result[key] = getJSONSchemaSample(propSchema, rootSchema);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Handle additionalProperties
|
|
153
|
+
if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {
|
|
154
|
+
result['additionalProp'] = getJSONSchemaSample(schema.additionalProperties, rootSchema);
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
function handleArray(schema, rootSchema) {
|
|
159
|
+
if (schema.items) {
|
|
160
|
+
const itemSchema = schema.items;
|
|
161
|
+
const minItems = schema.minItems || 1;
|
|
162
|
+
// Create minimum number of items (capped at a reasonable max for examples)
|
|
163
|
+
const numItems = Math.min(minItems, 3);
|
|
164
|
+
return Array.from({ length: numItems }, () => getJSONSchemaSample(itemSchema, rootSchema));
|
|
165
|
+
}
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type VovkSegmentSchema, type VovkController, type StaticClass } from '../types';
|
|
2
|
+
export declare function getControllerSchema(controller: VovkController, rpcModuleName: string, exposeValidation: boolean): Promise<{
|
|
3
|
+
rpcModuleName: string;
|
|
4
|
+
originalControllerName: string;
|
|
5
|
+
prefix: string;
|
|
6
|
+
handlers: {
|
|
7
|
+
[k: string]: {
|
|
8
|
+
path: string;
|
|
9
|
+
httpMethod: string;
|
|
10
|
+
openapi?: import("openapi3-ts/oas31").OperationObject;
|
|
11
|
+
misc?: Record<string, import("../types").KnownAny>;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
}>;
|
|
15
|
+
export default function getSchema(options: {
|
|
16
|
+
emitSchema?: boolean;
|
|
17
|
+
segmentName?: string;
|
|
18
|
+
controllers: Record<string, StaticClass>;
|
|
19
|
+
exposeValidation?: boolean;
|
|
20
|
+
forceApiRoot?: string;
|
|
21
|
+
}): Promise<VovkSegmentSchema>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getControllerSchema = getControllerSchema;
|
|
4
|
+
exports.default = getSchema;
|
|
5
|
+
const types_1 = require("../types");
|
|
6
|
+
async function getControllerSchema(controller, rpcModuleName, exposeValidation) {
|
|
7
|
+
const handlers = exposeValidation
|
|
8
|
+
? controller._handlers
|
|
9
|
+
: Object.fromEntries(
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
11
|
+
Object.entries(controller._handlers ?? {}).map(([key, { validation: _v, ...value }]) => [key, value]));
|
|
12
|
+
return {
|
|
13
|
+
rpcModuleName,
|
|
14
|
+
originalControllerName: controller.name,
|
|
15
|
+
prefix: controller._prefix ?? '',
|
|
16
|
+
handlers,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
async function getSchema(options) {
|
|
20
|
+
const exposeValidation = options?.exposeValidation ?? true;
|
|
21
|
+
const emitSchema = options.emitSchema ?? true;
|
|
22
|
+
const schema = {
|
|
23
|
+
$schema: types_1.VovkSchemaIdEnum.SEGMENT,
|
|
24
|
+
emitSchema,
|
|
25
|
+
segmentName: options.segmentName ?? '',
|
|
26
|
+
segmentType: 'segment',
|
|
27
|
+
controllers: {},
|
|
28
|
+
};
|
|
29
|
+
if (options.forceApiRoot) {
|
|
30
|
+
schema.forceApiRoot = options.forceApiRoot;
|
|
31
|
+
}
|
|
32
|
+
if (!emitSchema)
|
|
33
|
+
return schema;
|
|
34
|
+
for (const [rpcModuleName, controller] of Object.entries(options.controllers ?? {})) {
|
|
35
|
+
schema.controllers[rpcModuleName] = await getControllerSchema(controller, rpcModuleName, exposeValidation);
|
|
36
|
+
}
|
|
37
|
+
return schema;
|
|
38
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { KnownAny } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Deserialize a bracket-based query string into an object.
|
|
4
|
+
*
|
|
5
|
+
* Supports:
|
|
6
|
+
* - Key/value pairs with nested brackets (e.g. "a[b][0]=value")
|
|
7
|
+
* - Arrays with empty bracket (e.g. "arr[]=1&arr[]=2")
|
|
8
|
+
* - Mixed arrays of objects, etc.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* parseQuery("x=xx&y[0]=yy&y[1]=uu&z[f]=x&z[u][0]=uu&z[u][1]=xx&z[d][x]=ee")
|
|
12
|
+
* => {
|
|
13
|
+
* x: "xx",
|
|
14
|
+
* y: ["yy", "uu"],
|
|
15
|
+
* z: {
|
|
16
|
+
* f: "x",
|
|
17
|
+
* u: ["uu", "xx"],
|
|
18
|
+
* d: { x: "ee" }
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* @param queryString - The raw query string (e.g. location.search.slice(1))
|
|
23
|
+
* @returns - A nested object representing the query params
|
|
24
|
+
*/
|
|
25
|
+
export default function parseQuery(queryString: string): Record<string, KnownAny>;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = parseQuery;
|
|
4
|
+
/**
|
|
5
|
+
* Parse a bracket-based key (e.g. "z[d][0][x]" or "arr[]")
|
|
6
|
+
* into an array of path segments (strings or special push-markers).
|
|
7
|
+
*
|
|
8
|
+
* Example: "z[d][0][x]" => ["z", "d", "0", "x"]
|
|
9
|
+
* Example: "arr[]" => ["arr", "" ] // "" indicates "push" onto array
|
|
10
|
+
*/
|
|
11
|
+
function parseKey(key) {
|
|
12
|
+
// The first segment is everything up to the first '[' (or the entire key if no '[')
|
|
13
|
+
const segments = [];
|
|
14
|
+
const topKeyMatch = key.match(/^([^[\]]+)/);
|
|
15
|
+
if (topKeyMatch) {
|
|
16
|
+
segments.push(topKeyMatch[1]);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
// If it starts with brackets, treat it as empty? (edge case)
|
|
20
|
+
segments.push('');
|
|
21
|
+
}
|
|
22
|
+
// Now capture all bracket parts: [something], [0], []
|
|
23
|
+
const bracketRegex = /\[([^[\]]*)\]/g;
|
|
24
|
+
let match;
|
|
25
|
+
while ((match = bracketRegex.exec(key)) !== null) {
|
|
26
|
+
// match[1] is the content inside the brackets
|
|
27
|
+
segments.push(match[1]);
|
|
28
|
+
}
|
|
29
|
+
return segments;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Recursively set a value in a nested object/array, given a path of segments.
|
|
33
|
+
* - If segment is numeric => treat as array index
|
|
34
|
+
* - If segment is empty "" => push to array
|
|
35
|
+
* - Else => object property
|
|
36
|
+
*/
|
|
37
|
+
function setValue(obj, path, value) {
|
|
38
|
+
let current = obj;
|
|
39
|
+
for (let i = 0; i < path.length; i++) {
|
|
40
|
+
const segment = path[i];
|
|
41
|
+
// If we're at the last segment, set the value
|
|
42
|
+
if (i === path.length - 1) {
|
|
43
|
+
if (segment === '') {
|
|
44
|
+
// Empty bracket => push
|
|
45
|
+
if (!Array.isArray(current)) {
|
|
46
|
+
current = [];
|
|
47
|
+
}
|
|
48
|
+
current.push(value);
|
|
49
|
+
}
|
|
50
|
+
else if (!isNaN(Number(segment))) {
|
|
51
|
+
// Numeric segment => array index
|
|
52
|
+
const idx = Number(segment);
|
|
53
|
+
if (!Array.isArray(current)) {
|
|
54
|
+
current = [];
|
|
55
|
+
}
|
|
56
|
+
current[idx] = value;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
// Object property
|
|
60
|
+
current[segment] = value;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
// Not the last segment: descend into existing structure or create it
|
|
65
|
+
const nextSegment = path[i + 1];
|
|
66
|
+
if (segment === '') {
|
|
67
|
+
// Empty bracket => push
|
|
68
|
+
if (!Array.isArray(current)) {
|
|
69
|
+
// Convert the current node into an array, if not one
|
|
70
|
+
current = [];
|
|
71
|
+
}
|
|
72
|
+
// If we are not at the last path, we need a placeholder object or array
|
|
73
|
+
// for the next segment. We'll push something and move current to that.
|
|
74
|
+
if (current.length === 0) {
|
|
75
|
+
// nothing in array yet
|
|
76
|
+
current.push(typeof nextSegment === 'string' && !isNaN(Number(nextSegment)) ? [] : {});
|
|
77
|
+
}
|
|
78
|
+
else if (typeof nextSegment === 'string' && !isNaN(Number(nextSegment))) {
|
|
79
|
+
// next is numeric => we want an array
|
|
80
|
+
if (!Array.isArray(current[current.length - 1])) {
|
|
81
|
+
current[current.length - 1] = [];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// next is not numeric => we want an object
|
|
86
|
+
if (typeof current[current.length - 1] !== 'object') {
|
|
87
|
+
current[current.length - 1] = {};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
current = current[current.length - 1];
|
|
91
|
+
}
|
|
92
|
+
else if (!isNaN(Number(segment))) {
|
|
93
|
+
// segment is numeric => array index
|
|
94
|
+
const idx = Number(segment);
|
|
95
|
+
if (!Array.isArray(current)) {
|
|
96
|
+
current = [];
|
|
97
|
+
}
|
|
98
|
+
if (current[idx] === undefined) {
|
|
99
|
+
// Create placeholder for next segment
|
|
100
|
+
current[idx] = typeof nextSegment === 'string' && !isNaN(Number(nextSegment)) ? [] : {};
|
|
101
|
+
}
|
|
102
|
+
current = current[idx];
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// segment is an object key
|
|
106
|
+
if (current[segment] === undefined) {
|
|
107
|
+
// Create placeholder
|
|
108
|
+
current[segment] = typeof nextSegment === 'string' && !isNaN(Number(nextSegment)) ? [] : {};
|
|
109
|
+
}
|
|
110
|
+
current = current[segment];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Deserialize a bracket-based query string into an object.
|
|
117
|
+
*
|
|
118
|
+
* Supports:
|
|
119
|
+
* - Key/value pairs with nested brackets (e.g. "a[b][0]=value")
|
|
120
|
+
* - Arrays with empty bracket (e.g. "arr[]=1&arr[]=2")
|
|
121
|
+
* - Mixed arrays of objects, etc.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* parseQuery("x=xx&y[0]=yy&y[1]=uu&z[f]=x&z[u][0]=uu&z[u][1]=xx&z[d][x]=ee")
|
|
125
|
+
* => {
|
|
126
|
+
* x: "xx",
|
|
127
|
+
* y: ["yy", "uu"],
|
|
128
|
+
* z: {
|
|
129
|
+
* f: "x",
|
|
130
|
+
* u: ["uu", "xx"],
|
|
131
|
+
* d: { x: "ee" }
|
|
132
|
+
* }
|
|
133
|
+
* }
|
|
134
|
+
*
|
|
135
|
+
* @param queryString - The raw query string (e.g. location.search.slice(1))
|
|
136
|
+
* @returns - A nested object representing the query params
|
|
137
|
+
*/
|
|
138
|
+
function parseQuery(queryString) {
|
|
139
|
+
const result = {};
|
|
140
|
+
if (!queryString)
|
|
141
|
+
return result;
|
|
142
|
+
// Split into key=value pairs
|
|
143
|
+
const pairs = queryString
|
|
144
|
+
.replace(/^\?/, '') // Remove leading "?" if present
|
|
145
|
+
.split('&');
|
|
146
|
+
for (const pair of pairs) {
|
|
147
|
+
const [rawKey, rawVal = ''] = pair.split('=');
|
|
148
|
+
const decodedKey = decodeURIComponent(rawKey);
|
|
149
|
+
const decodedVal = decodeURIComponent(rawVal);
|
|
150
|
+
// Parse bracket notation
|
|
151
|
+
const pathSegments = parseKey(decodedKey);
|
|
152
|
+
// Insert into the result object
|
|
153
|
+
setValue(result, pathSegments, decodedVal);
|
|
154
|
+
}
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = reqForm;
|
|
4
|
+
const formMap = new WeakMap();
|
|
5
|
+
async function reqForm(req) {
|
|
6
|
+
if (formMap.has(req)) {
|
|
7
|
+
return formMap.get(req);
|
|
8
|
+
}
|
|
9
|
+
const body = await req.formData();
|
|
10
|
+
req.formData = () => Promise.resolve(body);
|
|
11
|
+
const formData = {};
|
|
12
|
+
for (const [key, value] of body.entries()) {
|
|
13
|
+
if (value instanceof File) {
|
|
14
|
+
// If this key already exists, convert to array or append to existing array
|
|
15
|
+
if (formData[key]) {
|
|
16
|
+
if (Array.isArray(formData[key])) {
|
|
17
|
+
formData[key].push(value);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
formData[key] = [formData[key], value];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
formData[key] = value;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
formData[key] = value.toString();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
formMap.set(req, formData);
|
|
32
|
+
return formData;
|
|
33
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = reqQuery;
|
|
7
|
+
const parseQuery_1 = __importDefault(require("./parseQuery"));
|
|
8
|
+
function reqQuery(req) {
|
|
9
|
+
return (0, parseQuery_1.default)(req.nextUrl.search);
|
|
10
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { KnownAny } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Serialize a nested object (including arrays, arrays of objects, etc.)
|
|
4
|
+
* into a bracket-based query string.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* serializeQuery({ x: 'xx', y: [1, 2], z: { f: 'x' } })
|
|
8
|
+
* => "x=xx&y[0]=1&y[1]=2&z[f]=x"
|
|
9
|
+
*
|
|
10
|
+
* @param obj - The input object to be serialized
|
|
11
|
+
* @returns - A bracket-based query string (without leading "?")
|
|
12
|
+
*/
|
|
13
|
+
export default function serializeQuery(obj: Record<string, KnownAny>): string;
|