vector-framework 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/dist/auth/protected.d.ts +1 -0
- package/dist/auth/protected.d.ts.map +1 -1
- package/dist/auth/protected.js +3 -0
- package/dist/auth/protected.js.map +1 -1
- package/dist/cache/manager.d.ts +1 -0
- package/dist/cache/manager.d.ts.map +1 -1
- package/dist/cache/manager.js +3 -0
- package/dist/cache/manager.js.map +1 -1
- package/dist/cli/graceful-shutdown.d.ts +15 -0
- package/dist/cli/graceful-shutdown.d.ts.map +1 -0
- package/dist/cli/graceful-shutdown.js +42 -0
- package/dist/cli/graceful-shutdown.js.map +1 -0
- package/dist/cli/index.js +37 -43
- package/dist/cli/index.js.map +1 -1
- package/dist/cli.js +967 -222
- package/dist/core/config-loader.d.ts.map +1 -1
- package/dist/core/config-loader.js +5 -2
- package/dist/core/config-loader.js.map +1 -1
- package/dist/core/server.d.ts +4 -0
- package/dist/core/server.d.ts.map +1 -1
- package/dist/core/server.js +240 -9
- package/dist/core/server.js.map +1 -1
- package/dist/core/vector.d.ts +4 -2
- package/dist/core/vector.d.ts.map +1 -1
- package/dist/core/vector.js +32 -2
- package/dist/core/vector.js.map +1 -1
- package/dist/errors/index.cjs +2 -0
- package/dist/index.cjs +1434 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -1327
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +153 -46
- package/dist/openapi/docs-ui.d.ts +1 -1
- package/dist/openapi/docs-ui.d.ts.map +1 -1
- package/dist/openapi/docs-ui.js +147 -35
- package/dist/openapi/docs-ui.js.map +1 -1
- package/dist/openapi/generator.d.ts.map +1 -1
- package/dist/openapi/generator.js +318 -6
- package/dist/openapi/generator.js.map +1 -1
- package/dist/start-vector.d.ts +3 -0
- package/dist/start-vector.d.ts.map +1 -0
- package/dist/start-vector.js +38 -0
- package/dist/start-vector.js.map +1 -0
- package/dist/types/index.d.ts +25 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/logger.js +1 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +2 -0
- package/dist/utils/validation.js.map +1 -1
- package/package.json +10 -14
- package/src/auth/protected.ts +4 -0
- package/src/cache/manager.ts +4 -0
- package/src/cli/graceful-shutdown.ts +60 -0
- package/src/cli/index.ts +42 -49
- package/src/core/config-loader.ts +5 -2
- package/src/core/server.ts +304 -9
- package/src/core/vector.ts +38 -4
- package/src/index.ts +4 -3
- package/src/openapi/assets/favicon/android-chrome-192x192.png +0 -0
- package/src/openapi/assets/favicon/android-chrome-512x512.png +0 -0
- package/src/openapi/assets/favicon/apple-touch-icon.png +0 -0
- package/src/openapi/assets/favicon/favicon-16x16.png +0 -0
- package/src/openapi/assets/favicon/favicon-32x32.png +0 -0
- package/src/openapi/assets/favicon/favicon.ico +0 -0
- package/src/openapi/assets/favicon/site.webmanifest +11 -0
- package/src/openapi/assets/logo.svg +12 -0
- package/src/openapi/assets/logo_dark.svg +6 -0
- package/src/openapi/assets/logo_icon.png +0 -0
- package/src/openapi/assets/logo_white.svg +6 -0
- package/src/openapi/docs-ui.ts +153 -35
- package/src/openapi/generator.ts +341 -6
- package/src/start-vector.ts +50 -0
- package/src/types/index.ts +34 -0
- package/src/utils/logger.ts +1 -1
- package/src/utils/validation.ts +2 -0
package/src/core/server.ts
CHANGED
|
@@ -15,6 +15,7 @@ interface NormalizedOpenAPIConfig {
|
|
|
15
15
|
docs: {
|
|
16
16
|
enabled: boolean;
|
|
17
17
|
path: string;
|
|
18
|
+
exposePaths?: string[];
|
|
18
19
|
};
|
|
19
20
|
info?: {
|
|
20
21
|
title?: string;
|
|
@@ -24,6 +25,15 @@ interface NormalizedOpenAPIConfig {
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
const OPENAPI_TAILWIND_ASSET_PATH = '/_vector/openapi/tailwindcdn.js';
|
|
28
|
+
const OPENAPI_LOGO_DARK_ASSET_PATH = '/_vector/openapi/logo_dark.svg';
|
|
29
|
+
const OPENAPI_LOGO_WHITE_ASSET_PATH = '/_vector/openapi/logo_white.svg';
|
|
30
|
+
const OPENAPI_APPLE_TOUCH_ICON_ASSET_PATH = '/_vector/openapi/favicon/apple-touch-icon.png';
|
|
31
|
+
const OPENAPI_FAVICON_32_ASSET_PATH = '/_vector/openapi/favicon/favicon-32x32.png';
|
|
32
|
+
const OPENAPI_FAVICON_16_ASSET_PATH = '/_vector/openapi/favicon/favicon-16x16.png';
|
|
33
|
+
const OPENAPI_FAVICON_ICO_ASSET_PATH = '/_vector/openapi/favicon/favicon.ico';
|
|
34
|
+
const OPENAPI_WEBMANIFEST_ASSET_PATH = '/_vector/openapi/favicon/site.webmanifest';
|
|
35
|
+
const OPENAPI_ANDROID_192_ASSET_PATH = '/_vector/openapi/favicon/android-chrome-192x192.png';
|
|
36
|
+
const OPENAPI_ANDROID_512_ASSET_PATH = '/_vector/openapi/favicon/android-chrome-512x512.png';
|
|
27
37
|
const OPENAPI_TAILWIND_ASSET_RELATIVE_CANDIDATES = [
|
|
28
38
|
// Source execution (src/core/server.ts -> src/openapi/assets/tailwindcdn.js)
|
|
29
39
|
'../openapi/assets/tailwindcdn.js',
|
|
@@ -32,15 +42,58 @@ const OPENAPI_TAILWIND_ASSET_RELATIVE_CANDIDATES = [
|
|
|
32
42
|
// Unbundled dist/core/server.js execution (dist/core -> src/openapi/assets/tailwindcdn.js)
|
|
33
43
|
'../../src/openapi/assets/tailwindcdn.js',
|
|
34
44
|
] as const;
|
|
45
|
+
const OPENAPI_LOGO_DARK_ASSET_RELATIVE_CANDIDATES = [
|
|
46
|
+
// Source execution (src/core/server.ts -> src/openapi/assets/logo_dark.svg)
|
|
47
|
+
'../openapi/assets/logo_dark.svg',
|
|
48
|
+
// Bundled dist entrypoints (dist/index.mjs|dist/cli.js -> src/openapi/assets/logo_dark.svg)
|
|
49
|
+
'../src/openapi/assets/logo_dark.svg',
|
|
50
|
+
// Unbundled dist/core/server.js execution (dist/core -> src/openapi/assets/logo_dark.svg)
|
|
51
|
+
'../../src/openapi/assets/logo_dark.svg',
|
|
52
|
+
] as const;
|
|
53
|
+
const OPENAPI_LOGO_WHITE_ASSET_RELATIVE_CANDIDATES = [
|
|
54
|
+
// Source execution (src/core/server.ts -> src/openapi/assets/logo_white.svg)
|
|
55
|
+
'../openapi/assets/logo_white.svg',
|
|
56
|
+
// Bundled dist entrypoints (dist/index.mjs|dist/cli.js -> src/openapi/assets/logo_white.svg)
|
|
57
|
+
'../src/openapi/assets/logo_white.svg',
|
|
58
|
+
// Unbundled dist/core/server.js execution (dist/core -> src/openapi/assets/logo_white.svg)
|
|
59
|
+
'../../src/openapi/assets/logo_white.svg',
|
|
60
|
+
] as const;
|
|
35
61
|
const OPENAPI_TAILWIND_ASSET_CWD_CANDIDATES = [
|
|
36
62
|
'src/openapi/assets/tailwindcdn.js',
|
|
37
63
|
'openapi/assets/tailwindcdn.js',
|
|
38
64
|
'dist/openapi/assets/tailwindcdn.js',
|
|
39
65
|
] as const;
|
|
66
|
+
const OPENAPI_LOGO_DARK_ASSET_CWD_CANDIDATES = [
|
|
67
|
+
'src/openapi/assets/logo_dark.svg',
|
|
68
|
+
'openapi/assets/logo_dark.svg',
|
|
69
|
+
'dist/openapi/assets/logo_dark.svg',
|
|
70
|
+
] as const;
|
|
71
|
+
const OPENAPI_LOGO_WHITE_ASSET_CWD_CANDIDATES = [
|
|
72
|
+
'src/openapi/assets/logo_white.svg',
|
|
73
|
+
'openapi/assets/logo_white.svg',
|
|
74
|
+
'dist/openapi/assets/logo_white.svg',
|
|
75
|
+
] as const;
|
|
76
|
+
const OPENAPI_FAVICON_ASSET_RELATIVE_BASE_CANDIDATES = [
|
|
77
|
+
'../openapi/assets/favicon',
|
|
78
|
+
'../src/openapi/assets/favicon',
|
|
79
|
+
'../../src/openapi/assets/favicon',
|
|
80
|
+
] as const;
|
|
81
|
+
const OPENAPI_FAVICON_ASSET_CWD_BASE_CANDIDATES = [
|
|
82
|
+
'src/openapi/assets/favicon',
|
|
83
|
+
'openapi/assets/favicon',
|
|
84
|
+
'dist/openapi/assets/favicon',
|
|
85
|
+
] as const;
|
|
40
86
|
const OPENAPI_TAILWIND_ASSET_INLINE_FALLBACK = '/* OpenAPI docs runtime asset missing: tailwind disabled */';
|
|
41
87
|
|
|
42
|
-
function
|
|
43
|
-
|
|
88
|
+
function buildOpenAPIAssetCandidatePaths(bases: readonly string[], filename: string): string[] {
|
|
89
|
+
return bases.map((base) => `${base}/${filename}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function resolveOpenAPIAssetFile(
|
|
93
|
+
relativeCandidates: readonly string[],
|
|
94
|
+
cwdCandidates: readonly string[]
|
|
95
|
+
): ReturnType<typeof Bun.file> | null {
|
|
96
|
+
for (const relativePath of relativeCandidates) {
|
|
44
97
|
try {
|
|
45
98
|
const fileUrl = new URL(relativePath, import.meta.url);
|
|
46
99
|
if (existsSync(fileUrl)) {
|
|
@@ -52,7 +105,7 @@ function resolveOpenAPITailwindAssetFile(): ReturnType<typeof Bun.file> | null {
|
|
|
52
105
|
}
|
|
53
106
|
|
|
54
107
|
const cwd = process.cwd();
|
|
55
|
-
for (const relativePath of
|
|
108
|
+
for (const relativePath of cwdCandidates) {
|
|
56
109
|
const absolutePath = join(cwd, relativePath);
|
|
57
110
|
if (existsSync(absolutePath)) {
|
|
58
111
|
return Bun.file(absolutePath);
|
|
@@ -62,9 +115,93 @@ function resolveOpenAPITailwindAssetFile(): ReturnType<typeof Bun.file> | null {
|
|
|
62
115
|
return null;
|
|
63
116
|
}
|
|
64
117
|
|
|
65
|
-
const OPENAPI_TAILWIND_ASSET_FILE =
|
|
118
|
+
const OPENAPI_TAILWIND_ASSET_FILE = resolveOpenAPIAssetFile(
|
|
119
|
+
OPENAPI_TAILWIND_ASSET_RELATIVE_CANDIDATES,
|
|
120
|
+
OPENAPI_TAILWIND_ASSET_CWD_CANDIDATES
|
|
121
|
+
);
|
|
122
|
+
const OPENAPI_LOGO_DARK_ASSET_FILE = resolveOpenAPIAssetFile(
|
|
123
|
+
OPENAPI_LOGO_DARK_ASSET_RELATIVE_CANDIDATES,
|
|
124
|
+
OPENAPI_LOGO_DARK_ASSET_CWD_CANDIDATES
|
|
125
|
+
);
|
|
126
|
+
const OPENAPI_LOGO_WHITE_ASSET_FILE = resolveOpenAPIAssetFile(
|
|
127
|
+
OPENAPI_LOGO_WHITE_ASSET_RELATIVE_CANDIDATES,
|
|
128
|
+
OPENAPI_LOGO_WHITE_ASSET_CWD_CANDIDATES
|
|
129
|
+
);
|
|
130
|
+
const OPENAPI_APPLE_TOUCH_ICON_ASSET_FILE = resolveOpenAPIAssetFile(
|
|
131
|
+
buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_RELATIVE_BASE_CANDIDATES, 'apple-touch-icon.png'),
|
|
132
|
+
buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_CWD_BASE_CANDIDATES, 'apple-touch-icon.png')
|
|
133
|
+
);
|
|
134
|
+
const OPENAPI_FAVICON_32_ASSET_FILE = resolveOpenAPIAssetFile(
|
|
135
|
+
buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_RELATIVE_BASE_CANDIDATES, 'favicon-32x32.png'),
|
|
136
|
+
buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_CWD_BASE_CANDIDATES, 'favicon-32x32.png')
|
|
137
|
+
);
|
|
138
|
+
const OPENAPI_FAVICON_16_ASSET_FILE = resolveOpenAPIAssetFile(
|
|
139
|
+
buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_RELATIVE_BASE_CANDIDATES, 'favicon-16x16.png'),
|
|
140
|
+
buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_CWD_BASE_CANDIDATES, 'favicon-16x16.png')
|
|
141
|
+
);
|
|
142
|
+
const OPENAPI_FAVICON_ICO_ASSET_FILE = resolveOpenAPIAssetFile(
|
|
143
|
+
buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_RELATIVE_BASE_CANDIDATES, 'favicon.ico'),
|
|
144
|
+
buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_CWD_BASE_CANDIDATES, 'favicon.ico')
|
|
145
|
+
);
|
|
146
|
+
const OPENAPI_WEBMANIFEST_ASSET_FILE = resolveOpenAPIAssetFile(
|
|
147
|
+
buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_RELATIVE_BASE_CANDIDATES, 'site.webmanifest'),
|
|
148
|
+
buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_CWD_BASE_CANDIDATES, 'site.webmanifest')
|
|
149
|
+
);
|
|
150
|
+
const OPENAPI_ANDROID_192_ASSET_FILE = resolveOpenAPIAssetFile(
|
|
151
|
+
buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_RELATIVE_BASE_CANDIDATES, 'android-chrome-192x192.png'),
|
|
152
|
+
buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_CWD_BASE_CANDIDATES, 'android-chrome-192x192.png')
|
|
153
|
+
);
|
|
154
|
+
const OPENAPI_ANDROID_512_ASSET_FILE = resolveOpenAPIAssetFile(
|
|
155
|
+
buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_RELATIVE_BASE_CANDIDATES, 'android-chrome-512x512.png'),
|
|
156
|
+
buildOpenAPIAssetCandidatePaths(OPENAPI_FAVICON_ASSET_CWD_BASE_CANDIDATES, 'android-chrome-512x512.png')
|
|
157
|
+
);
|
|
158
|
+
const OPENAPI_FAVICON_ASSETS = [
|
|
159
|
+
{
|
|
160
|
+
path: OPENAPI_APPLE_TOUCH_ICON_ASSET_PATH,
|
|
161
|
+
file: OPENAPI_APPLE_TOUCH_ICON_ASSET_FILE,
|
|
162
|
+
contentType: 'image/png',
|
|
163
|
+
filename: 'apple-touch-icon.png',
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
path: OPENAPI_FAVICON_32_ASSET_PATH,
|
|
167
|
+
file: OPENAPI_FAVICON_32_ASSET_FILE,
|
|
168
|
+
contentType: 'image/png',
|
|
169
|
+
filename: 'favicon-32x32.png',
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
path: OPENAPI_FAVICON_16_ASSET_PATH,
|
|
173
|
+
file: OPENAPI_FAVICON_16_ASSET_FILE,
|
|
174
|
+
contentType: 'image/png',
|
|
175
|
+
filename: 'favicon-16x16.png',
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
path: OPENAPI_FAVICON_ICO_ASSET_PATH,
|
|
179
|
+
file: OPENAPI_FAVICON_ICO_ASSET_FILE,
|
|
180
|
+
contentType: 'image/x-icon',
|
|
181
|
+
filename: 'favicon.ico',
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
path: OPENAPI_WEBMANIFEST_ASSET_PATH,
|
|
185
|
+
file: OPENAPI_WEBMANIFEST_ASSET_FILE,
|
|
186
|
+
contentType: 'application/manifest+json; charset=utf-8',
|
|
187
|
+
filename: 'site.webmanifest',
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
path: OPENAPI_ANDROID_192_ASSET_PATH,
|
|
191
|
+
file: OPENAPI_ANDROID_192_ASSET_FILE,
|
|
192
|
+
contentType: 'image/png',
|
|
193
|
+
filename: 'android-chrome-192x192.png',
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
path: OPENAPI_ANDROID_512_ASSET_PATH,
|
|
197
|
+
file: OPENAPI_ANDROID_512_ASSET_FILE,
|
|
198
|
+
contentType: 'image/png',
|
|
199
|
+
filename: 'android-chrome-512x512.png',
|
|
200
|
+
},
|
|
201
|
+
] as const;
|
|
66
202
|
const DOCS_HTML_CACHE_CONTROL = 'public, max-age=0, must-revalidate';
|
|
67
203
|
const DOCS_ASSET_CACHE_CONTROL = 'public, max-age=31536000, immutable';
|
|
204
|
+
const DOCS_ASSET_ERROR_CACHE_CONTROL = 'no-store';
|
|
68
205
|
|
|
69
206
|
interface OpenAPIDocsHtmlCacheEntry {
|
|
70
207
|
html: string;
|
|
@@ -72,6 +209,30 @@ interface OpenAPIDocsHtmlCacheEntry {
|
|
|
72
209
|
etag: string;
|
|
73
210
|
}
|
|
74
211
|
|
|
212
|
+
function escapeRegex(value: string): string {
|
|
213
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function wildcardPatternToRegex(pattern: string): RegExp {
|
|
217
|
+
let regexSource = '^';
|
|
218
|
+
for (const char of pattern) {
|
|
219
|
+
if (char === '*') {
|
|
220
|
+
regexSource += '.*';
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
regexSource += escapeRegex(char);
|
|
224
|
+
}
|
|
225
|
+
regexSource += '$';
|
|
226
|
+
return new RegExp(regexSource);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function matchesExposePath(path: string, exposePathPattern: string): boolean {
|
|
230
|
+
if (!exposePathPattern.includes('*')) {
|
|
231
|
+
return path === exposePathPattern;
|
|
232
|
+
}
|
|
233
|
+
return wildcardPatternToRegex(exposePathPattern).test(path);
|
|
234
|
+
}
|
|
235
|
+
|
|
75
236
|
export class VectorServer<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
76
237
|
private server: Server | null = null;
|
|
77
238
|
private router: VectorRouter<TTypes>;
|
|
@@ -81,6 +242,8 @@ export class VectorServer<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
81
242
|
private openapiDocsHtmlCache: OpenAPIDocsHtmlCacheEntry | null = null;
|
|
82
243
|
private openapiWarningsLogged = false;
|
|
83
244
|
private openapiTailwindMissingLogged = false;
|
|
245
|
+
private openapiLogoDarkMissingLogged = false;
|
|
246
|
+
private openapiLogoWhiteMissingLogged = false;
|
|
84
247
|
private corsHandler: {
|
|
85
248
|
preflight: (request: Request) => Response;
|
|
86
249
|
corsify: (response: Response, request: Request) => Response;
|
|
@@ -149,10 +312,15 @@ export class VectorServer<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
149
312
|
const docsValue = openapiObject.docs;
|
|
150
313
|
const docs =
|
|
151
314
|
typeof docsValue === 'boolean'
|
|
152
|
-
? { enabled: docsValue, path: '/docs' }
|
|
315
|
+
? { enabled: docsValue, path: '/docs', exposePaths: undefined }
|
|
153
316
|
: {
|
|
154
317
|
enabled: docsValue?.enabled === true,
|
|
155
318
|
path: docsValue?.path || '/docs',
|
|
319
|
+
exposePaths: Array.isArray(docsValue?.exposePaths)
|
|
320
|
+
? docsValue.exposePaths
|
|
321
|
+
.map((path) => (typeof path === 'string' ? path.trim() : ''))
|
|
322
|
+
.filter((path) => path.length > 0)
|
|
323
|
+
: undefined,
|
|
156
324
|
};
|
|
157
325
|
|
|
158
326
|
return {
|
|
@@ -170,6 +338,17 @@ export class VectorServer<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
170
338
|
);
|
|
171
339
|
}
|
|
172
340
|
|
|
341
|
+
private shouldLogOpenAPIConversionWarnings(): boolean {
|
|
342
|
+
const nodeEnv = process.env.NODE_ENV;
|
|
343
|
+
const isDevelopment = this.config.development !== false && nodeEnv !== 'production';
|
|
344
|
+
if (!isDevelopment) {
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const logLevel = process.env.LOG_LEVEL;
|
|
349
|
+
return typeof logLevel === 'string' && logLevel.toLowerCase() === 'debug';
|
|
350
|
+
}
|
|
351
|
+
|
|
173
352
|
private getOpenAPIDocument(): Record<string, unknown> {
|
|
174
353
|
if (this.openapiDocCache) {
|
|
175
354
|
return this.openapiDocCache;
|
|
@@ -183,8 +362,10 @@ export class VectorServer<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
183
362
|
});
|
|
184
363
|
|
|
185
364
|
if (!this.openapiWarningsLogged && result.warnings.length > 0) {
|
|
186
|
-
|
|
187
|
-
|
|
365
|
+
if (this.shouldLogOpenAPIConversionWarnings()) {
|
|
366
|
+
for (const warning of result.warnings) {
|
|
367
|
+
console.warn(warning);
|
|
368
|
+
}
|
|
188
369
|
}
|
|
189
370
|
this.openapiWarningsLogged = true;
|
|
190
371
|
}
|
|
@@ -193,12 +374,48 @@ export class VectorServer<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
193
374
|
return this.openapiDocCache;
|
|
194
375
|
}
|
|
195
376
|
|
|
377
|
+
private getOpenAPIDocumentForDocs(): Record<string, unknown> {
|
|
378
|
+
const exposePaths = this.openapiConfig.docs.exposePaths;
|
|
379
|
+
const document = this.getOpenAPIDocument();
|
|
380
|
+
|
|
381
|
+
if (!Array.isArray(exposePaths) || exposePaths.length === 0) {
|
|
382
|
+
return document;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const existingPaths =
|
|
386
|
+
document.paths && typeof document.paths === 'object' && !Array.isArray(document.paths)
|
|
387
|
+
? (document.paths as Record<string, unknown>)
|
|
388
|
+
: {};
|
|
389
|
+
|
|
390
|
+
const filteredPaths: Record<string, unknown> = {};
|
|
391
|
+
for (const [path, value] of Object.entries(existingPaths)) {
|
|
392
|
+
if (exposePaths.some((pattern) => matchesExposePath(path, pattern))) {
|
|
393
|
+
filteredPaths[path] = value;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return {
|
|
398
|
+
...document,
|
|
399
|
+
paths: filteredPaths,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
196
403
|
private getOpenAPIDocsHtmlCacheEntry(): OpenAPIDocsHtmlCacheEntry {
|
|
197
404
|
if (this.openapiDocsHtmlCache) {
|
|
198
405
|
return this.openapiDocsHtmlCache;
|
|
199
406
|
}
|
|
200
407
|
|
|
201
|
-
const html = renderOpenAPIDocsHtml(
|
|
408
|
+
const html = renderOpenAPIDocsHtml(
|
|
409
|
+
this.getOpenAPIDocumentForDocs(),
|
|
410
|
+
this.openapiConfig.path,
|
|
411
|
+
OPENAPI_TAILWIND_ASSET_PATH,
|
|
412
|
+
OPENAPI_LOGO_DARK_ASSET_PATH,
|
|
413
|
+
OPENAPI_LOGO_WHITE_ASSET_PATH,
|
|
414
|
+
OPENAPI_APPLE_TOUCH_ICON_ASSET_PATH,
|
|
415
|
+
OPENAPI_FAVICON_32_ASSET_PATH,
|
|
416
|
+
OPENAPI_FAVICON_16_ASSET_PATH,
|
|
417
|
+
OPENAPI_WEBMANIFEST_ASSET_PATH
|
|
418
|
+
);
|
|
202
419
|
const gzip = Bun.gzipSync(html);
|
|
203
420
|
const etag = `"${Bun.hash(html).toString(16)}"`;
|
|
204
421
|
|
|
@@ -220,6 +437,11 @@ export class VectorServer<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
220
437
|
if (this.openapiConfig.docs.enabled) {
|
|
221
438
|
reserved.add(this.openapiConfig.docs.path);
|
|
222
439
|
reserved.add(OPENAPI_TAILWIND_ASSET_PATH);
|
|
440
|
+
reserved.add(OPENAPI_LOGO_DARK_ASSET_PATH);
|
|
441
|
+
reserved.add(OPENAPI_LOGO_WHITE_ASSET_PATH);
|
|
442
|
+
for (const asset of OPENAPI_FAVICON_ASSETS) {
|
|
443
|
+
reserved.add(asset.path);
|
|
444
|
+
}
|
|
223
445
|
}
|
|
224
446
|
|
|
225
447
|
const methodConflicts = this.router
|
|
@@ -316,6 +538,79 @@ export class VectorServer<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
316
538
|
});
|
|
317
539
|
}
|
|
318
540
|
|
|
541
|
+
if (this.openapiConfig.docs.enabled && pathname === OPENAPI_LOGO_DARK_ASSET_PATH) {
|
|
542
|
+
if (!OPENAPI_LOGO_DARK_ASSET_FILE) {
|
|
543
|
+
if (!this.openapiLogoDarkMissingLogged) {
|
|
544
|
+
this.openapiLogoDarkMissingLogged = true;
|
|
545
|
+
console.warn('[OpenAPI] Missing docs runtime asset "logo_dark.svg".');
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return new Response('OpenAPI docs runtime asset missing: logo_dark.svg', {
|
|
549
|
+
status: 404,
|
|
550
|
+
headers: {
|
|
551
|
+
'content-type': 'text/plain; charset=utf-8',
|
|
552
|
+
'cache-control': DOCS_ASSET_ERROR_CACHE_CONTROL,
|
|
553
|
+
},
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return new Response(OPENAPI_LOGO_DARK_ASSET_FILE, {
|
|
558
|
+
status: 200,
|
|
559
|
+
headers: {
|
|
560
|
+
'content-type': 'image/svg+xml; charset=utf-8',
|
|
561
|
+
'cache-control': DOCS_ASSET_CACHE_CONTROL,
|
|
562
|
+
},
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (this.openapiConfig.docs.enabled && pathname === OPENAPI_LOGO_WHITE_ASSET_PATH) {
|
|
567
|
+
if (!OPENAPI_LOGO_WHITE_ASSET_FILE) {
|
|
568
|
+
if (!this.openapiLogoWhiteMissingLogged) {
|
|
569
|
+
this.openapiLogoWhiteMissingLogged = true;
|
|
570
|
+
console.warn('[OpenAPI] Missing docs runtime asset "logo_white.svg".');
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return new Response('OpenAPI docs runtime asset missing: logo_white.svg', {
|
|
574
|
+
status: 404,
|
|
575
|
+
headers: {
|
|
576
|
+
'content-type': 'text/plain; charset=utf-8',
|
|
577
|
+
'cache-control': DOCS_ASSET_ERROR_CACHE_CONTROL,
|
|
578
|
+
},
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return new Response(OPENAPI_LOGO_WHITE_ASSET_FILE, {
|
|
583
|
+
status: 200,
|
|
584
|
+
headers: {
|
|
585
|
+
'content-type': 'image/svg+xml; charset=utf-8',
|
|
586
|
+
'cache-control': DOCS_ASSET_CACHE_CONTROL,
|
|
587
|
+
},
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (this.openapiConfig.docs.enabled) {
|
|
592
|
+
const faviconAsset = OPENAPI_FAVICON_ASSETS.find((asset) => asset.path === pathname);
|
|
593
|
+
if (faviconAsset) {
|
|
594
|
+
if (!faviconAsset.file) {
|
|
595
|
+
return new Response(`OpenAPI docs runtime asset missing: ${faviconAsset.filename}`, {
|
|
596
|
+
status: 404,
|
|
597
|
+
headers: {
|
|
598
|
+
'content-type': 'text/plain; charset=utf-8',
|
|
599
|
+
'cache-control': DOCS_ASSET_ERROR_CACHE_CONTROL,
|
|
600
|
+
},
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
return new Response(faviconAsset.file, {
|
|
605
|
+
status: 200,
|
|
606
|
+
headers: {
|
|
607
|
+
'content-type': faviconAsset.contentType,
|
|
608
|
+
'cache-control': DOCS_ASSET_CACHE_CONTROL,
|
|
609
|
+
},
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
319
614
|
return null;
|
|
320
615
|
}
|
|
321
616
|
|
|
@@ -385,7 +680,7 @@ export class VectorServer<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
385
680
|
reusePort: this.config.reusePort !== false,
|
|
386
681
|
routes: this.router.getRouteTable(),
|
|
387
682
|
fetch: fallbackFetch,
|
|
388
|
-
idleTimeout: this.config.idleTimeout
|
|
683
|
+
idleTimeout: this.config.idleTimeout ?? 60,
|
|
389
684
|
error: (error, request?: Request) => {
|
|
390
685
|
console.error('[ERROR] Server error:', error);
|
|
391
686
|
return this.applyCors(new Response('Internal Server Error', { status: 500 }), request);
|
package/src/core/vector.ts
CHANGED
|
@@ -39,6 +39,7 @@ export class Vector<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
39
39
|
private routeGenerator: RouteGenerator | null = null;
|
|
40
40
|
private _protectedHandler: ProtectedHandler<TTypes> | null = null;
|
|
41
41
|
private _cacheHandler: CacheHandler | null = null;
|
|
42
|
+
private shutdownPromise: Promise<void> | null = null;
|
|
42
43
|
|
|
43
44
|
private constructor() {
|
|
44
45
|
this.middlewareManager = new MiddlewareManager<TTypes>();
|
|
@@ -56,9 +57,13 @@ export class Vector<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
// Internal method to set protected handler
|
|
59
|
-
setProtectedHandler(handler: ProtectedHandler<TTypes>) {
|
|
60
|
+
setProtectedHandler(handler: ProtectedHandler<TTypes> | null) {
|
|
60
61
|
this._protectedHandler = handler;
|
|
61
|
-
|
|
62
|
+
if (handler) {
|
|
63
|
+
this.authManager.setProtectedHandler(handler);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
this.authManager.clearProtectedHandler();
|
|
62
67
|
}
|
|
63
68
|
|
|
64
69
|
getProtectedHandler(): ProtectedHandler<TTypes> | null {
|
|
@@ -66,9 +71,13 @@ export class Vector<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
// Internal method to set cache handler
|
|
69
|
-
setCacheHandler(handler: CacheHandler) {
|
|
74
|
+
setCacheHandler(handler: CacheHandler | null) {
|
|
70
75
|
this._cacheHandler = handler;
|
|
71
|
-
|
|
76
|
+
if (handler) {
|
|
77
|
+
this.cacheManager.setCacheHandler(handler);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this.cacheManager.clearCacheHandler();
|
|
72
81
|
}
|
|
73
82
|
|
|
74
83
|
getCacheHandler(): CacheHandler | null {
|
|
@@ -107,6 +116,10 @@ export class Vector<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
107
116
|
this.middlewareManager.addFinally(...config.finally);
|
|
108
117
|
}
|
|
109
118
|
|
|
119
|
+
if (typeof this.config.startup === 'function') {
|
|
120
|
+
await this.config.startup();
|
|
121
|
+
}
|
|
122
|
+
|
|
110
123
|
if (this.config.autoDiscover !== false) {
|
|
111
124
|
await this.discoverRoutes();
|
|
112
125
|
}
|
|
@@ -228,6 +241,27 @@ export class Vector<TTypes extends VectorTypes = DefaultVectorTypes> {
|
|
|
228
241
|
// Routes will be cleared on next startServer() call
|
|
229
242
|
}
|
|
230
243
|
|
|
244
|
+
async shutdown(): Promise<void> {
|
|
245
|
+
if (this.shutdownPromise) {
|
|
246
|
+
return this.shutdownPromise;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
this.shutdownPromise = (async () => {
|
|
250
|
+
// Stop accepting new traffic first.
|
|
251
|
+
this.stop();
|
|
252
|
+
|
|
253
|
+
if (typeof this.config.shutdown === 'function') {
|
|
254
|
+
await this.config.shutdown();
|
|
255
|
+
}
|
|
256
|
+
})();
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
await this.shutdownPromise;
|
|
260
|
+
} finally {
|
|
261
|
+
this.shutdownPromise = null;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
231
265
|
getServer(): VectorServer<TTypes> | null {
|
|
232
266
|
return this.server;
|
|
233
267
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
// Public exports for route definitions
|
|
1
|
+
// Public exports for route definitions and Vector startup API
|
|
2
2
|
import { route } from './http';
|
|
3
|
+
import { startVector } from './start-vector';
|
|
3
4
|
|
|
4
5
|
// Export route function for defining routes
|
|
5
6
|
export { route };
|
|
7
|
+
export { startVector };
|
|
6
8
|
|
|
7
9
|
// Export utilities for route handlers
|
|
8
10
|
export { APIError, createResponse } from './http';
|
|
@@ -10,5 +12,4 @@ export { APIError, createResponse } from './http';
|
|
|
10
12
|
// Export types for TypeScript users
|
|
11
13
|
export * from './types';
|
|
12
14
|
|
|
13
|
-
// Note: Vector
|
|
14
|
-
// Usage: Create vector.config.ts and run 'vector dev' or 'vector start'
|
|
15
|
+
// Note: Vector is config-driven and can run via CLI or programmatic startup API
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "",
|
|
3
|
+
"short_name": "",
|
|
4
|
+
"icons": [
|
|
5
|
+
{ "src": "/_vector/openapi/favicon/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
|
|
6
|
+
{ "src": "/_vector/openapi/favicon/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }
|
|
7
|
+
],
|
|
8
|
+
"theme_color": "#00a1ff",
|
|
9
|
+
"background_color": "#ffffff",
|
|
10
|
+
"display": "standalone"
|
|
11
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.000000000000002 2.8668130259787783" preserveAspectRatio="xMidYMid meet">
|
|
3
|
+
<defs>
|
|
4
|
+
<linearGradient id="SvgjsLinearGradient2002">
|
|
5
|
+
<stop stop-color="#00ff8f" offset="0"/>
|
|
6
|
+
<stop stop-color="#00a1ff" offset="1"/>
|
|
7
|
+
</linearGradient>
|
|
8
|
+
</defs>
|
|
9
|
+
<g transform="matrix(0.09366995975118918,0,0,0.09366995975118918,-0.3622393421406193,-0.9696304427369192)" fill="url(#SvgjsLinearGradient2002)">
|
|
10
|
+
<path d="M25.664063 26.71875 l-7.5 13.359375 l-9.433594 0 l-1.386719 -13.359375 l-1.796875 0 l-1.679687 -15.957031 l10.214844 0 l1.191406 14.082031 c2.636719 -4.628906 5.019531 -9.472656 7.636719 -14.082031 l9.960938 0 l-8.964844 15.957031 l1.757813 0 z M31.503906 27.34375 l-1.992187 0 l3.828125 -16.484375 l20.917969 0 l-1.679687 7.402344 l-11.152344 0 l-0.585937 2.5 l10.253906 0 l-1.503906 6.582031 l1.992188 0 l-0.175781 0.78125 l-10.273437 0 l-1.035156 4.570313 l11.113281 0 l-1.679687 7.382813 l-20.957031 0 z M62.363281 27.265625 l2.226563 0 c-0.351562 4.707031 2.851563 8.242188 11.875 2.441406 l2.929688 0 l-1.835937 7.96875 c-9.902344 5.703125 -24.589844 3.984375 -22.441406 -10.410156 l-2.207031 0 c0.117188 -0.527344 0.234375 -1.152344 0.390625 -1.835937 c1.992188 -8.730469 8.652344 -15.078125 17.929688 -15.078125 c5.117188 0 8.066406 1.464844 9.824219 2.675781 l-1.894531 8.164063 l-3.144531 0 c-7.070312 -7.617187 -13.183594 0.15625 -13.652344 6.074219 z M99.101563 26.71875 l-2.96875 13.359375 l-9.902344 0 l3.046875 -13.359375 l-1.777344 0 l1.972656 -8.554687 l-7.8125 0 l1.71875 -7.402344 l25.351563 0 l-1.660156 7.402344 l-7.792969 0 l-1.914062 8.554688 l1.738281 0 z M134.003907 25.683594 c-0.15625 0.625 -0.332031 1.25 -0.566406 1.894531 l2.128906 0 c-2.480469 7.753906 -8.789062 13.378906 -17.441406 13.378906 c-8.925781 0 -12.402344 -5.625 -11.269531 -13.378906 l-2.070312 0 c0.078125 -0.644531 0.175781 -1.269531 0.3125 -1.894531 c2.070313 -8.925781 8.535156 -15.3125 17.929688 -15.3125 c9.199219 0 12.988281 6.445313 10.976563 15.3125 z M125.429688 27.578125 l-2.109375 0 c0.195313 -0.644531 0.390625 -1.269531 0.527344 -1.933594 c2.480469 -10.722656 -6.171875 -10.410156 -8.59375 0.039063 c-0.136719 0.683594 -0.234375 1.308594 -0.332031 1.894531 l2.109375 0 c-1.015625 8.164063 5.644531 7.988281 8.398438 0 z M138.457031 26.71875 l-1.777344 0 l3.632813 -15.957031 c3.398438 0 9.53125 -0.332031 14.375 0.3125 c4.277344 0.625 8.417969 2.207031 7.695313 7.34375 c-0.410156 3.105469 -2.792969 6.289063 -5.371094 8.300781 l1.757813 0 c-0.46875 0.351563 -0.9375 0.722656 -1.445312 0.976563 l6.679688 12.382813 l-11.71875 0 l-4.882812 -9.550781 l-2.167969 9.550781 l-9.84375 0 z M148.535156 17.988281 l-1.191406 5.039063 c4.707031 0 7.34375 -5.039062 1.191406 -5.039062 z"/>
|
|
11
|
+
</g>
|
|
12
|
+
</svg>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.000000000000002 2.8668130259787783" preserveAspectRatio="xMidYMid meet">
|
|
3
|
+
<g transform="matrix(0.09366995975118918,0,0,0.09366995975118918,-0.3622393421406193,-0.9696304427369192)" fill="#111">
|
|
4
|
+
<path d="M25.664063 26.71875 l-7.5 13.359375 l-9.433594 0 l-1.386719 -13.359375 l-1.796875 0 l-1.679687 -15.957031 l10.214844 0 l1.191406 14.082031 c2.636719 -4.628906 5.019531 -9.472656 7.636719 -14.082031 l9.960938 0 l-8.964844 15.957031 l1.757813 0 z M31.503906 27.34375 l-1.992187 0 l3.828125 -16.484375 l20.917969 0 l-1.679687 7.402344 l-11.152344 0 l-0.585937 2.5 l10.253906 0 l-1.503906 6.582031 l1.992188 0 l-0.175781 0.78125 l-10.273437 0 l-1.035156 4.570313 l11.113281 0 l-1.679687 7.382813 l-20.957031 0 z M62.363281 27.265625 l2.226563 0 c-0.351562 4.707031 2.851563 8.242188 11.875 2.441406 l2.929688 0 l-1.835937 7.96875 c-9.902344 5.703125 -24.589844 3.984375 -22.441406 -10.410156 l-2.207031 0 c0.117188 -0.527344 0.234375 -1.152344 0.390625 -1.835937 c1.992188 -8.730469 8.652344 -15.078125 17.929688 -15.078125 c5.117188 0 8.066406 1.464844 9.824219 2.675781 l-1.894531 8.164063 l-3.144531 0 c-7.070312 -7.617187 -13.183594 0.15625 -13.652344 6.074219 z M99.101563 26.71875 l-2.96875 13.359375 l-9.902344 0 l3.046875 -13.359375 l-1.777344 0 l1.972656 -8.554687 l-7.8125 0 l1.71875 -7.402344 l25.351563 0 l-1.660156 7.402344 l-7.792969 0 l-1.914062 8.554688 l1.738281 0 z M134.003907 25.683594 c-0.15625 0.625 -0.332031 1.25 -0.566406 1.894531 l2.128906 0 c-2.480469 7.753906 -8.789062 13.378906 -17.441406 13.378906 c-8.925781 0 -12.402344 -5.625 -11.269531 -13.378906 l-2.070312 0 c0.078125 -0.644531 0.175781 -1.269531 0.3125 -1.894531 c2.070313 -8.925781 8.535156 -15.3125 17.929688 -15.3125 c9.199219 0 12.988281 6.445313 10.976563 15.3125 z M125.429688 27.578125 l-2.109375 0 c0.195313 -0.644531 0.390625 -1.269531 0.527344 -1.933594 c2.480469 -10.722656 -6.171875 -10.410156 -8.59375 0.039063 c-0.136719 0.683594 -0.234375 1.308594 -0.332031 1.894531 l2.109375 0 c-1.015625 8.164063 5.644531 7.988281 8.398438 0 z M138.457031 26.71875 l-1.777344 0 l3.632813 -15.957031 c3.398438 0 9.53125 -0.332031 14.375 0.3125 c4.277344 0.625 8.417969 2.207031 7.695313 7.34375 c-0.410156 3.105469 -2.792969 6.289063 -5.371094 8.300781 l1.757813 0 c-0.46875 0.351563 -0.9375 0.722656 -1.445312 0.976563 l6.679688 12.382813 l-11.71875 0 l-4.882812 -9.550781 l-2.167969 9.550781 l-9.84375 0 z M148.535156 17.988281 l-1.191406 5.039063 c4.707031 0 7.34375 -5.039062 1.191406 -5.039062 z"/>
|
|
5
|
+
</g>
|
|
6
|
+
</svg>
|
|
Binary file
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.000000000000002 2.8668130259787783" preserveAspectRatio="xMidYMid meet">
|
|
3
|
+
<g transform="matrix(0.09366995975118918,0,0,0.09366995975118918,-0.3622393421406193,-0.9696304427369192)" fill="#fff">
|
|
4
|
+
<path d="M25.664063 26.71875 l-7.5 13.359375 l-9.433594 0 l-1.386719 -13.359375 l-1.796875 0 l-1.679687 -15.957031 l10.214844 0 l1.191406 14.082031 c2.636719 -4.628906 5.019531 -9.472656 7.636719 -14.082031 l9.960938 0 l-8.964844 15.957031 l1.757813 0 z M31.503906 27.34375 l-1.992187 0 l3.828125 -16.484375 l20.917969 0 l-1.679687 7.402344 l-11.152344 0 l-0.585937 2.5 l10.253906 0 l-1.503906 6.582031 l1.992188 0 l-0.175781 0.78125 l-10.273437 0 l-1.035156 4.570313 l11.113281 0 l-1.679687 7.382813 l-20.957031 0 z M62.363281 27.265625 l2.226563 0 c-0.351562 4.707031 2.851563 8.242188 11.875 2.441406 l2.929688 0 l-1.835937 7.96875 c-9.902344 5.703125 -24.589844 3.984375 -22.441406 -10.410156 l-2.207031 0 c0.117188 -0.527344 0.234375 -1.152344 0.390625 -1.835937 c1.992188 -8.730469 8.652344 -15.078125 17.929688 -15.078125 c5.117188 0 8.066406 1.464844 9.824219 2.675781 l-1.894531 8.164063 l-3.144531 0 c-7.070312 -7.617187 -13.183594 0.15625 -13.652344 6.074219 z M99.101563 26.71875 l-2.96875 13.359375 l-9.902344 0 l3.046875 -13.359375 l-1.777344 0 l1.972656 -8.554687 l-7.8125 0 l1.71875 -7.402344 l25.351563 0 l-1.660156 7.402344 l-7.792969 0 l-1.914062 8.554688 l1.738281 0 z M134.003907 25.683594 c-0.15625 0.625 -0.332031 1.25 -0.566406 1.894531 l2.128906 0 c-2.480469 7.753906 -8.789062 13.378906 -17.441406 13.378906 c-8.925781 0 -12.402344 -5.625 -11.269531 -13.378906 l-2.070312 0 c0.078125 -0.644531 0.175781 -1.269531 0.3125 -1.894531 c2.070313 -8.925781 8.535156 -15.3125 17.929688 -15.3125 c9.199219 0 12.988281 6.445313 10.976563 15.3125 z M125.429688 27.578125 l-2.109375 0 c0.195313 -0.644531 0.390625 -1.269531 0.527344 -1.933594 c2.480469 -10.722656 -6.171875 -10.410156 -8.59375 0.039063 c-0.136719 0.683594 -0.234375 1.308594 -0.332031 1.894531 l2.109375 0 c-1.015625 8.164063 5.644531 7.988281 8.398438 0 z M138.457031 26.71875 l-1.777344 0 l3.632813 -15.957031 c3.398438 0 9.53125 -0.332031 14.375 0.3125 c4.277344 0.625 8.417969 2.207031 7.695313 7.34375 c-0.410156 3.105469 -2.792969 6.289063 -5.371094 8.300781 l1.757813 0 c-0.46875 0.351563 -0.9375 0.722656 -1.445312 0.976563 l6.679688 12.382813 l-11.71875 0 l-4.882812 -9.550781 l-2.167969 9.550781 l-9.84375 0 z M148.535156 17.988281 l-1.191406 5.039063 c4.707031 0 7.34375 -5.039062 1.191406 -5.039062 z"/>
|
|
5
|
+
</g>
|
|
6
|
+
</svg>
|