@tstdl/base 0.93.172 → 0.93.174
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/ai/genkit/tests/multi-region.test.js +1 -1
- package/ai/genkit/tests/token-limit-fallback.test.js +1 -1
- package/api/server/middlewares/cors.middleware.js +78 -40
- package/api/types.d.ts +4 -4
- package/authentication/client/authentication.service.d.ts +1 -1
- package/authentication/client/authentication.service.js +5 -4
- package/document-management/tests/ai-config-integration.test.js +4 -4
- package/document-management/tests/document-validation-ai-overrides.test.js +3 -0
- package/examples/api/streaming.js +1 -1
- package/examples/document-management/main.js +2 -2
- package/http/server/http-server-response.d.ts +1 -1
- package/http/server/http-server-response.js +13 -5
- package/logger/tests/pretty-print.test.js +2 -2
- package/package.json +1 -2
|
@@ -6,7 +6,7 @@ import { CircuitBreakerProvider } from '../../../circuit-breaker/provider.js';
|
|
|
6
6
|
import { Logger } from '../../../logger/logger.js';
|
|
7
7
|
import { setupIntegrationTest } from '../../../testing/index.js';
|
|
8
8
|
import { vertexAiMultiLocation } from '../multi-region.plugin.js';
|
|
9
|
-
vi.mock('
|
|
9
|
+
vi.mock('../../../utils/array/index.js', async (importOriginal) => {
|
|
10
10
|
const actual = await importOriginal();
|
|
11
11
|
return {
|
|
12
12
|
...actual,
|
|
@@ -7,7 +7,7 @@ import { Logger } from '../../../logger/logger.js';
|
|
|
7
7
|
import { setupIntegrationTest } from '../../../testing/index.js';
|
|
8
8
|
import { timeout } from '../../../utils/timing.js';
|
|
9
9
|
import { vertexAiMultiLocation } from '../multi-region.plugin.js';
|
|
10
|
-
vi.mock('
|
|
10
|
+
vi.mock('../../../utils/array/index.js', async (importOriginal) => {
|
|
11
11
|
const actual = await importOriginal();
|
|
12
12
|
return {
|
|
13
13
|
...actual,
|
|
@@ -1,53 +1,91 @@
|
|
|
1
1
|
import { resolveApiEndpointDataProvider } from '../../../api/types.js';
|
|
2
|
-
import { toArray } from '../../../utils/array/array.js';
|
|
3
|
-
import { isDefined } from '../../../utils/type-guards.js';
|
|
2
|
+
import { distinct, toArray } from '../../../utils/array/array.js';
|
|
3
|
+
import { isDefined, isString } from '../../../utils/type-guards.js';
|
|
4
|
+
const defaultAllowedHeaders = ['Content-Type', 'Authorization', 'Cache-Control', 'If-None-Match', 'Range'];
|
|
5
|
+
const defaultExposedHeaders = ['ETag', 'Content-Disposition', 'Content-Range'];
|
|
6
|
+
const headerSplitPattern = /,\s*/u;
|
|
4
7
|
export function corsMiddleware(options = {}) {
|
|
5
8
|
return async function corsMiddleware(context, next) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (isOptions) {
|
|
16
|
-
const allowMethods = (await resolveApiEndpointDataProvider(request, context, cors.accessControlAllowMethods)) ?? [...context.api.endpoints.keys()].join(', ');
|
|
17
|
-
response.headers.set('Access-Control-Allow-Methods', allowMethods);
|
|
18
|
-
if (isDefined(cors.accessControlAllowHeaders) && !request.headers.has('Access-Control-Allow-Headers')) {
|
|
19
|
-
const value = await resolveApiEndpointDataProvider(request, context, cors.accessControlAllowHeaders);
|
|
20
|
-
response.headers.set('Access-Control-Allow-Headers', value);
|
|
21
|
-
}
|
|
22
|
-
if (isDefined(cors.accessControlExposeHeaders) && !request.headers.has('Access-Control-Expose-Headers')) {
|
|
23
|
-
const value = await resolveApiEndpointDataProvider(request, context, cors.accessControlExposeHeaders);
|
|
24
|
-
response.headers.set('Access-Control-Expose-Headers', value);
|
|
25
|
-
}
|
|
26
|
-
if (isDefined(cors.accessControlMaxAge) && !request.headers.has('Access-Control-Max-Age')) {
|
|
27
|
-
const value = await resolveApiEndpointDataProvider(request, context, cors.accessControlMaxAge);
|
|
28
|
-
response.headers.set('Access-Control-Max-Age', value);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
if (!request.headers.has('Access-Control-Allow-Credentials')) {
|
|
32
|
-
const allowCredentials = isDefined(cors.accessControlAllowCredentials)
|
|
33
|
-
? await resolveApiEndpointDataProvider(request, context, cors.accessControlAllowCredentials)
|
|
34
|
-
: endpointDefinition?.credentials;
|
|
35
|
-
if (allowCredentials == true) {
|
|
36
|
-
response.headers.set('Access-Control-Allow-Credentials', 'true');
|
|
37
|
-
}
|
|
38
|
-
}
|
|
9
|
+
const { request, response } = context;
|
|
10
|
+
const requestMethod = request.headers.tryGetSingle('Access-Control-Request-Method') ?? request.method;
|
|
11
|
+
const isOptions = (request.method == 'OPTIONS');
|
|
12
|
+
const endpointDefinition = context.api.endpoints.get(requestMethod)?.definition;
|
|
13
|
+
const cors = { ...options.default, ...endpointDefinition?.cors };
|
|
14
|
+
const origin = request.headers.tryGetSingle('Origin');
|
|
15
|
+
const allowCredentials = (await resolveApiEndpointDataProvider(request, context, cors.accessControlAllowCredentials)) ?? endpointDefinition?.credentials;
|
|
16
|
+
if (isDefined(origin)) {
|
|
17
|
+
let allowOriginValidationFailed = false;
|
|
39
18
|
if (isDefined(cors.accessControlAllowOrigin) && !response.headers.has('Access-Control-Allow-Origin')) {
|
|
40
|
-
const
|
|
41
|
-
|
|
19
|
+
const allowedOrigin = await resolveApiEndpointDataProvider(request, context, cors.accessControlAllowOrigin);
|
|
20
|
+
if (isDefined(allowedOrigin)) {
|
|
21
|
+
const isWildcard = (allowedOrigin == '*');
|
|
22
|
+
const isAllowed = isWildcard || (origin == allowedOrigin);
|
|
23
|
+
if (isAllowed) {
|
|
24
|
+
const value = (isWildcard && (allowCredentials == true)) ? origin : allowedOrigin;
|
|
25
|
+
response.headers.set('Access-Control-Allow-Origin', value);
|
|
26
|
+
if (value == origin) {
|
|
27
|
+
response.headers.append('Vary', 'Origin');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
allowOriginValidationFailed = true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
42
34
|
}
|
|
43
35
|
if (isDefined(cors.autoAccessControlAllowOrigin) && !response.headers.has('Access-Control-Allow-Origin')) {
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
const allowed = isDefined(value) && toArray(value).includes(origin);
|
|
36
|
+
const allowedOrigins = await resolveApiEndpointDataProvider(request, context, cors.autoAccessControlAllowOrigin);
|
|
37
|
+
const allowed = allowedOrigins?.includes(origin) ?? false;
|
|
47
38
|
if (allowed) {
|
|
48
39
|
response.headers.set('Access-Control-Allow-Origin', origin);
|
|
40
|
+
response.headers.append('Vary', 'Origin');
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
allowOriginValidationFailed = true;
|
|
49
44
|
}
|
|
50
45
|
}
|
|
46
|
+
if (isOptions && allowOriginValidationFailed && !response.headers.has('Access-Control-Allow-Origin')) {
|
|
47
|
+
response.statusCode = 403;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (isOptions) {
|
|
52
|
+
const customAllowMethods = await resolveApiEndpointDataProvider(request, context, cors.accessControlAllowMethods);
|
|
53
|
+
const allowMethods = customAllowMethods?.join(', ') ?? [...context.api.endpoints.keys()].join(', ');
|
|
54
|
+
response.headers.set('Access-Control-Allow-Methods', allowMethods);
|
|
55
|
+
const customAllowHeaders = await resolveApiEndpointDataProvider(request, context, cors.accessControlAllowHeaders);
|
|
56
|
+
const allowHeaders = distinct([
|
|
57
|
+
...defaultAllowedHeaders,
|
|
58
|
+
...(customAllowHeaders ?? []),
|
|
59
|
+
...toArray(response.headers.tryGet('Access-Control-Allow-Headers') ?? []).filter(isString).flatMap((h) => h.split(headerSplitPattern)),
|
|
60
|
+
]).filter((header) => (header.length > 0)).join(', ');
|
|
61
|
+
response.headers.set('Access-Control-Allow-Headers', allowHeaders);
|
|
62
|
+
if (isDefined(cors.accessControlMaxAge) && !response.headers.has('Access-Control-Max-Age')) {
|
|
63
|
+
const value = await resolveApiEndpointDataProvider(request, context, cors.accessControlMaxAge);
|
|
64
|
+
response.headers.set('Access-Control-Max-Age', value);
|
|
65
|
+
}
|
|
66
|
+
else if (!response.headers.has('Access-Control-Max-Age')) {
|
|
67
|
+
response.headers.set('Access-Control-Max-Age', '86400');
|
|
68
|
+
}
|
|
69
|
+
if (allowCredentials == true) {
|
|
70
|
+
response.headers.set('Access-Control-Allow-Credentials', 'true');
|
|
71
|
+
}
|
|
72
|
+
response.statusCode = 204;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
await next();
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
const customExposeHeaders = await resolveApiEndpointDataProvider(request, context, cors.accessControlExposeHeaders);
|
|
80
|
+
const exposeHeaders = [...new Set([
|
|
81
|
+
...defaultExposedHeaders,
|
|
82
|
+
...(customExposeHeaders ?? []),
|
|
83
|
+
...toArray(response.headers.tryGet('Access-Control-Expose-Headers') ?? []).filter(isString).flatMap((h) => h.split(headerSplitPattern)),
|
|
84
|
+
])].filter((header) => (header.length > 0)).join(', ');
|
|
85
|
+
response.headers.set('Access-Control-Expose-Headers', exposeHeaders);
|
|
86
|
+
if (allowCredentials == true) {
|
|
87
|
+
response.headers.setIfMissing('Access-Control-Allow-Credentials', 'true');
|
|
88
|
+
}
|
|
51
89
|
}
|
|
52
90
|
};
|
|
53
91
|
}
|
package/api/types.d.ts
CHANGED
|
@@ -33,11 +33,11 @@ export type ApiEndpointDefinitionResult = SchemaTestable | typeof String | typeo
|
|
|
33
33
|
export type ApiEndpointDataProvider<T> = T | ((request: HttpServerRequest, context: ApiGatewayMiddlewareContext) => T | Promise<T>);
|
|
34
34
|
export type ApiEndpointDefinitionCors = {
|
|
35
35
|
accessControlAllowCredentials?: ApiEndpointDataProvider<boolean | undefined>;
|
|
36
|
-
accessControlAllowHeaders?: ApiEndpointDataProvider<
|
|
37
|
-
accessControlAllowMethods?: ApiEndpointDataProvider<
|
|
36
|
+
accessControlAllowHeaders?: ApiEndpointDataProvider<string[] | undefined>;
|
|
37
|
+
accessControlAllowMethods?: ApiEndpointDataProvider<HttpMethod[] | undefined>;
|
|
38
38
|
accessControlAllowOrigin?: ApiEndpointDataProvider<string | undefined>;
|
|
39
|
-
autoAccessControlAllowOrigin?: ApiEndpointDataProvider<
|
|
40
|
-
accessControlExposeHeaders?: ApiEndpointDataProvider<
|
|
39
|
+
autoAccessControlAllowOrigin?: ApiEndpointDataProvider<string[] | undefined>;
|
|
40
|
+
accessControlExposeHeaders?: ApiEndpointDataProvider<string[] | undefined>;
|
|
41
41
|
accessControlMaxAge?: ApiEndpointDataProvider<number | undefined>;
|
|
42
42
|
};
|
|
43
43
|
export type ApiEndpointDefinition = {
|
|
@@ -26,7 +26,7 @@ export declare class AuthenticationClientService<AdditionalTokenPayload extends
|
|
|
26
26
|
private readonly logger;
|
|
27
27
|
private readonly disposeSignal;
|
|
28
28
|
private readonly forceRefreshRequested;
|
|
29
|
-
private readonly
|
|
29
|
+
private readonly forceRefreshSubject;
|
|
30
30
|
private clockOffset;
|
|
31
31
|
private initialized;
|
|
32
32
|
private refreshLoopPromise;
|
|
@@ -7,7 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
9
|
};
|
|
10
|
-
import { Subject, filter, firstValueFrom, from, race,
|
|
10
|
+
import { Subject, filter, firstValueFrom, from, race, takeUntil, timer } from 'rxjs';
|
|
11
11
|
import { CancellationSignal } from '../../cancellation/token.js';
|
|
12
12
|
import { BadRequestError } from '../../errors/bad-request.error.js';
|
|
13
13
|
import { ForbiddenError } from '../../errors/forbidden.error.js';
|
|
@@ -72,7 +72,7 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
72
72
|
logger = inject(Logger, 'AuthenticationService');
|
|
73
73
|
disposeSignal = inject(CancellationSignal).fork();
|
|
74
74
|
forceRefreshRequested = signal(false);
|
|
75
|
-
|
|
75
|
+
forceRefreshSubject = new Subject();
|
|
76
76
|
clockOffset = 0;
|
|
77
77
|
initialized = false;
|
|
78
78
|
refreshLoopPromise;
|
|
@@ -267,6 +267,7 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
267
267
|
this.setAdditionalData(data);
|
|
268
268
|
}
|
|
269
269
|
this.forceRefreshRequested.set(true);
|
|
270
|
+
this.forceRefreshSubject.next();
|
|
270
271
|
}
|
|
271
272
|
/**
|
|
272
273
|
* Refresh the token.
|
|
@@ -404,7 +405,7 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
404
405
|
timer(Math.max(10, delayMs)),
|
|
405
406
|
from(this.disposeSignal),
|
|
406
407
|
this.token$.pipe(filter((t) => t?.exp !== referenceExp)),
|
|
407
|
-
this.
|
|
408
|
+
this.forceRefreshSubject,
|
|
408
409
|
]), { defaultValue: undefined });
|
|
409
410
|
while (this.disposeSignal.isUnset) {
|
|
410
411
|
const token = this.token();
|
|
@@ -428,9 +429,9 @@ let AuthenticationClientService = class AuthenticationClientService {
|
|
|
428
429
|
const currentNow = this.estimatedServerTimestampSeconds();
|
|
429
430
|
const currentBuffer = calculateRefreshBufferSeconds(currentToken);
|
|
430
431
|
const stillNeedsRefresh = this.forceRefreshRequested() || (currentNow >= currentToken.exp - currentBuffer);
|
|
431
|
-
this.forceRefreshRequested.set(false);
|
|
432
432
|
if (stillNeedsRefresh) {
|
|
433
433
|
await this.refresh();
|
|
434
|
+
this.forceRefreshRequested.set(false);
|
|
434
435
|
}
|
|
435
436
|
});
|
|
436
437
|
// If lock is held by another instance/tab, wait briefly for it to finish (passive sync)
|
|
@@ -90,10 +90,10 @@ describe('DocumentManagementAiService Integration', () => {
|
|
|
90
90
|
await runInInjectionContext(injector, async () => {
|
|
91
91
|
await aiService.extractData(tenantId, document.id);
|
|
92
92
|
const lastCall = mockGenerate.mock.calls[0][0];
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
expect(
|
|
96
|
-
expect(
|
|
93
|
+
const userPrompt = lastCall.prompt;
|
|
94
|
+
const userPromptText = userPrompt.map((p) => p.text).join('\n');
|
|
95
|
+
expect(userPromptText).toContain('Format the title of the document exactly as follows: INV-{{Date}}');
|
|
96
|
+
expect(userPromptText).toContain('Focus on items.');
|
|
97
97
|
});
|
|
98
98
|
});
|
|
99
99
|
test('classifyDocumentType should include granular instructions in prompt', async () => {
|
|
@@ -6,6 +6,7 @@ import { object, string } from '../../schema/index.js';
|
|
|
6
6
|
import { setupIntegrationTest } from '../../testing/index.js';
|
|
7
7
|
import { isString } from '../../utils/type-guards.js';
|
|
8
8
|
import { DocumentManagementConfiguration } from '../server/module.js';
|
|
9
|
+
import { DocumentFileService } from '../server/services/document-file.service.js';
|
|
9
10
|
import { DocumentManagementAiProviderService, DocumentManagementAncillaryService } from '../server/services/index.js';
|
|
10
11
|
import { AiValidationExecutor } from '../server/validators/ai-validation-executor.js';
|
|
11
12
|
import { TestDocumentManagementAncillaryService } from './helper.js';
|
|
@@ -41,6 +42,7 @@ describe('AiValidationExecutor Overrides', () => {
|
|
|
41
42
|
injector.register(DocumentManagementAiProviderService, { useValue: mockAiProvider });
|
|
42
43
|
injector.register(DocumentManagementAncillaryService, { useToken: TestDocumentManagementAncillaryService });
|
|
43
44
|
injector.register(DocumentManagementConfiguration, { useValue: { fileObjectStorageModule: 'docs', fileUploadObjectStorageModule: 'uploads', filePreviewObjectStorageModule: 'previews' } });
|
|
45
|
+
injector.register(DocumentFileService, { useValue: { getContent: vi.fn().mockResolvedValue(new Uint8Array([1, 2, 3])) } });
|
|
44
46
|
});
|
|
45
47
|
afterAll(async () => {
|
|
46
48
|
await injector?.dispose();
|
|
@@ -66,6 +68,7 @@ describe('AiValidationExecutor Overrides', () => {
|
|
|
66
68
|
const context = {
|
|
67
69
|
execution: { tenantId },
|
|
68
70
|
definition: { identifier: 'test-validation' },
|
|
71
|
+
document: { id: 'test-doc', mimeType: 'application/pdf' },
|
|
69
72
|
};
|
|
70
73
|
await executor.publicExecute(context);
|
|
71
74
|
const call = generateSpy.mock.calls[0][0];
|
|
@@ -132,8 +132,8 @@ function bootstrap() {
|
|
|
132
132
|
prefix: null,
|
|
133
133
|
cors: {
|
|
134
134
|
default: {
|
|
135
|
-
autoAccessControlAllowOrigin: 'http://localhost:4200',
|
|
136
|
-
accessControlAllowHeaders: 'Content-Type, Authorization',
|
|
135
|
+
autoAccessControlAllowOrigin: ['http://localhost:4200'],
|
|
136
|
+
accessControlAllowHeaders: ['Content-Type', 'Authorization'],
|
|
137
137
|
},
|
|
138
138
|
},
|
|
139
139
|
},
|
|
@@ -23,9 +23,9 @@ export type HttpServerResponseOptions = {
|
|
|
23
23
|
};
|
|
24
24
|
export declare class HttpServerResponse {
|
|
25
25
|
#private;
|
|
26
|
+
readonly headers: HttpHeaders;
|
|
26
27
|
statusCode: number | undefined;
|
|
27
28
|
statusMessage: string | undefined;
|
|
28
|
-
headers: HttpHeaders;
|
|
29
29
|
get body(): HttpServerResponseBody | undefined;
|
|
30
30
|
set body(value: HttpServerResponseBody | undefined);
|
|
31
31
|
get bodyType(): HttpServerResponseBodyType;
|
|
@@ -5,9 +5,9 @@ import { HttpHeaders } from '../http-headers.js';
|
|
|
5
5
|
export class HttpServerResponse {
|
|
6
6
|
#body;
|
|
7
7
|
#bodyType;
|
|
8
|
+
headers = new HttpHeaders();
|
|
8
9
|
statusCode;
|
|
9
10
|
statusMessage;
|
|
10
|
-
headers;
|
|
11
11
|
get body() {
|
|
12
12
|
return this.#body;
|
|
13
13
|
}
|
|
@@ -35,10 +35,18 @@ export class HttpServerResponse {
|
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
37
|
update(options) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
if (isDefined(options.statusCode)) {
|
|
39
|
+
this.statusCode = options.statusCode;
|
|
40
|
+
}
|
|
41
|
+
if (isDefined(options.statusMessage)) {
|
|
42
|
+
this.statusMessage = options.statusMessage;
|
|
43
|
+
}
|
|
44
|
+
if (isDefined(options.headers)) {
|
|
45
|
+
this.headers.setMany(options.headers);
|
|
46
|
+
}
|
|
47
|
+
if (isDefined(options.body)) {
|
|
48
|
+
this.body = options.body;
|
|
49
|
+
}
|
|
42
50
|
if (isDefined(options.cookies)) {
|
|
43
51
|
for (const [name, cookie] of objectEntries(options.cookies)) {
|
|
44
52
|
this.headers.append('Set-Cookie', formatSetCookie(name, cookie.value, cookie));
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
2
|
// Mock supports before importing PrettyPrintLogFormatter
|
|
3
|
-
vi.mock('
|
|
3
|
+
vi.mock('../../supports.js', () => ({
|
|
4
4
|
supportsColoredStdout: true,
|
|
5
5
|
supportsColoredStderr: true,
|
|
6
6
|
}));
|
|
7
|
-
import { LogLevel } from '../level.js';
|
|
8
7
|
import { PrettyPrintLogFormatter } from '../formatters/pretty-print.js';
|
|
8
|
+
import { LogLevel } from '../level.js';
|
|
9
9
|
describe('PrettyPrintLogFormatter', () => {
|
|
10
10
|
const formatter = new PrettyPrintLogFormatter();
|
|
11
11
|
it('should format a log entry with an error and no context', () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tstdl/base",
|
|
3
|
-
"version": "0.93.
|
|
3
|
+
"version": "0.93.174",
|
|
4
4
|
"author": "Patrick Hein",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -203,7 +203,6 @@
|
|
|
203
203
|
"typedoc-plugin-missing-exports": "4.1",
|
|
204
204
|
"typescript": "5.9",
|
|
205
205
|
"typescript-eslint": "8.57",
|
|
206
|
-
"vite-tsconfig-paths": "6.1",
|
|
207
206
|
"vitest": "4.1"
|
|
208
207
|
}
|
|
209
208
|
}
|