@tstdl/base 0.93.117 → 0.93.118

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.
Files changed (40) hide show
  1. package/api/server/gateway.js +2 -2
  2. package/index.d.ts +1 -0
  3. package/index.js +1 -0
  4. package/internal.d.ts +1 -0
  5. package/internal.js +1 -0
  6. package/notification/api/notification.api.d.ts +22 -10
  7. package/notification/api/notification.api.js +9 -3
  8. package/notification/client/notification-client.d.ts +2 -0
  9. package/notification/client/notification-client.js +7 -0
  10. package/notification/server/api/notification.api-controller.d.ts +3 -0
  11. package/notification/server/api/notification.api-controller.js +5 -0
  12. package/notification/server/services/notification-type.service.d.ts +1 -0
  13. package/notification/server/services/notification-type.service.js +5 -0
  14. package/notification/tests/notification-api.test.js +8 -0
  15. package/notification/tests/notification-sse.service.test.js +9 -0
  16. package/notification/tests/unit/notification-client.test.d.ts +1 -0
  17. package/notification/tests/unit/notification-client.test.js +112 -0
  18. package/object-storage/object-storage.d.ts +10 -0
  19. package/object-storage/s3/s3.object-storage-provider.d.ts +11 -4
  20. package/object-storage/s3/s3.object-storage-provider.js +29 -26
  21. package/object-storage/s3/s3.object-storage.d.ts +7 -4
  22. package/object-storage/s3/s3.object-storage.js +141 -60
  23. package/object-storage/s3/s3.object.d.ts +6 -0
  24. package/object-storage/s3/s3.object.js +1 -1
  25. package/object-storage/s3/tests/s3.object-storage.integration.test.d.ts +1 -0
  26. package/object-storage/s3/tests/s3.object-storage.integration.test.js +334 -0
  27. package/package.json +3 -2
  28. package/rpc/adapters/readable-stream.adapter.js +27 -22
  29. package/rpc/endpoints/message-port.rpc-endpoint.d.ts +4 -0
  30. package/rpc/endpoints/message-port.rpc-endpoint.js +4 -0
  31. package/rpc/model.d.ts +11 -1
  32. package/rpc/rpc.d.ts +17 -1
  33. package/rpc/rpc.endpoint.js +4 -3
  34. package/rpc/rpc.error.d.ts +5 -1
  35. package/rpc/rpc.error.js +16 -3
  36. package/rpc/rpc.js +89 -15
  37. package/rpc/tests/rpc.integration.test.d.ts +1 -0
  38. package/rpc/tests/rpc.integration.test.js +619 -0
  39. package/unit-test/integration-setup.d.ts +1 -0
  40. package/unit-test/integration-setup.js +12 -0
@@ -190,7 +190,7 @@ let ApiGateway = ApiGateway_1 = class ApiGateway {
190
190
  const decodedUrlParameters = mapObjectValues(context.resourcePatternResult.pathname.groups, (value) => isDefined(value) ? decodeURIComponent(value) : undefined);
191
191
  const parameters = { ...context.request.query.asObject(), ...bodyAsParameters, ...decodedUrlParameters };
192
192
  const validatedParameters = isDefined(context.endpoint.definition.parameters)
193
- ? Schema.parse(context.endpoint.definition.parameters, parameters)
193
+ ? Schema.parse(context.endpoint.definition.parameters, parameters, { coerce: true })
194
194
  : parameters;
195
195
  const requestTokenProvider = this.#requestTokenProvider;
196
196
  const auditor = this.#auditor;
@@ -279,5 +279,5 @@ async function getBody(request, options, schema) {
279
279
  body = await request.body.readAsBuffer(options);
280
280
  }
281
281
  }
282
- return Schema.parse(schema, body);
282
+ return Schema.parse(schema, body, { coerce: true });
283
283
  }
package/index.d.ts CHANGED
@@ -5,4 +5,5 @@
5
5
  */
6
6
  export * from './core.js';
7
7
  export * from './import.js';
8
+ export * from './internal.js';
8
9
  export * from './require.js';
package/index.js CHANGED
@@ -5,4 +5,5 @@
5
5
  */
6
6
  export * from './core.js';
7
7
  export * from './import.js';
8
+ export * from './internal.js';
8
9
  export * from './require.js';
package/internal.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare const internal: unique symbol;
package/internal.js ADDED
@@ -0,0 +1 @@
1
+ export const internal = Symbol('internal');
@@ -14,6 +14,12 @@ export declare const notificationApiDefinition: {
14
14
  };
15
15
  credentials: true;
16
16
  };
17
+ types: {
18
+ resource: string;
19
+ method: "GET";
20
+ result: import("../../schema/index.js").ObjectSchema<Partial<import("../../schema/index.js").Record<string, string>>>;
21
+ credentials: true;
22
+ };
17
23
  listInApp: {
18
24
  resource: string;
19
25
  method: "GET";
@@ -77,9 +83,9 @@ export declare const notificationApiDefinition: {
77
83
  resource: string;
78
84
  method: "POST";
79
85
  parameters: import("../../schema/index.js").ObjectSchema<{
80
- readonly type: string;
81
- readonly channel: "email" | "in-app" | "web-push";
82
- readonly enabled: boolean;
86
+ type: string;
87
+ enabled: boolean;
88
+ channel: "email" | "in-app" | "web-push";
83
89
  }>;
84
90
  result: import("../../schema/index.js").LiteralSchema<"ok">;
85
91
  credentials: true;
@@ -88,11 +94,11 @@ export declare const notificationApiDefinition: {
88
94
  resource: string;
89
95
  method: "POST";
90
96
  parameters: import("../../schema/index.js").ObjectSchema<{
91
- readonly endpoint: string;
92
- readonly keys: {
97
+ keys: {
93
98
  p256dhBase64: string;
94
99
  authBase64: string;
95
100
  };
101
+ endpoint: string;
96
102
  }>;
97
103
  result: import("../../schema/index.js").LiteralSchema<"ok">;
98
104
  credentials: true;
@@ -111,6 +117,12 @@ declare const _NotificationApiClient: import("../../api/client/index.js").ApiCli
111
117
  };
112
118
  credentials: true;
113
119
  };
120
+ types: {
121
+ resource: string;
122
+ method: "GET";
123
+ result: import("../../schema/index.js").ObjectSchema<Partial<import("../../schema/index.js").Record<string, string>>>;
124
+ credentials: true;
125
+ };
114
126
  listInApp: {
115
127
  resource: string;
116
128
  method: "GET";
@@ -174,9 +186,9 @@ declare const _NotificationApiClient: import("../../api/client/index.js").ApiCli
174
186
  resource: string;
175
187
  method: "POST";
176
188
  parameters: import("../../schema/index.js").ObjectSchema<{
177
- readonly type: string;
178
- readonly channel: "email" | "in-app" | "web-push";
179
- readonly enabled: boolean;
189
+ type: string;
190
+ enabled: boolean;
191
+ channel: "email" | "in-app" | "web-push";
180
192
  }>;
181
193
  result: import("../../schema/index.js").LiteralSchema<"ok">;
182
194
  credentials: true;
@@ -185,11 +197,11 @@ declare const _NotificationApiClient: import("../../api/client/index.js").ApiCli
185
197
  resource: string;
186
198
  method: "POST";
187
199
  parameters: import("../../schema/index.js").ObjectSchema<{
188
- readonly endpoint: string;
189
- readonly keys: {
200
+ keys: {
190
201
  p256dhBase64: string;
191
202
  authBase64: string;
192
203
  };
204
+ endpoint: string;
193
205
  }>;
194
206
  result: import("../../schema/index.js").LiteralSchema<"ok">;
195
207
  credentials: true;
@@ -7,7 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { compileClient } from '../../api/client/index.js';
8
8
  import { defineApi } from '../../api/types.js';
9
9
  import { ReplaceClass } from '../../injector/decorators.js';
10
- import { array, boolean, enumeration, explicitObject, literal, number, object, optional, string } from '../../schema/index.js';
10
+ import { array, boolean, enumeration, literal, number, object, optional, record, string } from '../../schema/index.js';
11
11
  import { DataStream } from '../../sse/data-stream.js';
12
12
  import { InAppNotificationView } from '../models/in-app-notification.model.js';
13
13
  import { NotificationChannel } from '../models/index.js';
@@ -20,6 +20,12 @@ export const notificationApiDefinition = defineApi({
20
20
  result: (DataStream),
21
21
  credentials: true,
22
22
  },
23
+ types: {
24
+ resource: 'types',
25
+ method: 'GET',
26
+ result: record(string(), string()),
27
+ credentials: true,
28
+ },
23
29
  listInApp: {
24
30
  resource: 'in-app',
25
31
  method: 'GET',
@@ -78,7 +84,7 @@ export const notificationApiDefinition = defineApi({
78
84
  updatePreference: {
79
85
  resource: 'preferences',
80
86
  method: 'POST',
81
- parameters: explicitObject({
87
+ parameters: object({
82
88
  type: string(),
83
89
  channel: enumeration(NotificationChannel),
84
90
  enabled: boolean(),
@@ -89,7 +95,7 @@ export const notificationApiDefinition = defineApi({
89
95
  registerWebPush: {
90
96
  resource: 'web-push/register',
91
97
  method: 'POST',
92
- parameters: explicitObject({
98
+ parameters: object({
93
99
  endpoint: string(),
94
100
  keys: object({
95
101
  p256dhBase64: string(),
@@ -10,9 +10,11 @@ export declare class NotificationClient<Definitions extends NotificationDefiniti
10
10
  readonly state$: import("rxjs").Observable<NotificationState<Definitions>>;
11
11
  readonly notifications$: import("rxjs").Observable<InAppNotificationView<Definitions>[]>;
12
12
  readonly unreadCount$: import("rxjs").Observable<number>;
13
+ readonly types$: import("rxjs").Observable<Record<keyof Definitions, string>>;
13
14
  readonly state: import("../../signals/api.js").Signal<NotificationState<Definitions>>;
14
15
  readonly notifications: import("../../signals/api.js").Signal<InAppNotificationView<Definitions>[]>;
15
16
  readonly unreadCount: import("../../signals/api.js").Signal<number>;
17
+ readonly types: import("../../signals/api.js").Signal<Record<keyof Definitions, string>>;
16
18
  loadNext(count?: number): void;
17
19
  }
18
20
  export {};
@@ -47,9 +47,16 @@ let NotificationClient = class NotificationClient {
47
47
  }), shareReplay({ bufferSize: 1, refCount: true }));
48
48
  notifications$ = this.state$.pipe(map((state) => state.notifications));
49
49
  unreadCount$ = this.state$.pipe(map((state) => state.unreadCount));
50
+ types$ = this.#authenticationService.sessionId$.pipe(switchMap((sessionId) => {
51
+ if (isUndefined(sessionId)) {
52
+ return of({});
53
+ }
54
+ return this.api.types();
55
+ }), shareReplay({ bufferSize: 1, refCount: true }));
50
56
  state = toSignal(this.state$, { initialValue: { notifications: [], unreadCount: 0 } });
51
57
  notifications = computed(() => this.state().notifications);
52
58
  unreadCount = computed(() => this.state().unreadCount);
59
+ types = toSignal(this.types$, { initialValue: {} });
53
60
  loadNext(count = 20) {
54
61
  const current = this.notifications();
55
62
  const after = current[current.length - 1]?.id;
@@ -1,11 +1,14 @@
1
1
  import type { ApiController, ApiRequestContext, ApiServerResult } from '../../../api/types.js';
2
2
  import type { NotificationApiDefinition } from '../../api/notification.api.js';
3
3
  import { NotificationSseService } from '../services/notification-sse.service.js';
4
+ import { NotificationTypeService } from '../services/notification-type.service.js';
4
5
  import { NotificationService } from '../services/notification.service.js';
5
6
  export declare class NotificationApiController implements ApiController<NotificationApiDefinition> {
6
7
  protected readonly notificationService: NotificationService<Record<string, import("../../index.js").NotificationDefinition<import("../../../types/types.js").ObjectLiteral, import("../../../types/types.js").ObjectLiteral>>>;
8
+ protected readonly notificationTypeService: NotificationTypeService;
7
9
  protected readonly sseService: NotificationSseService;
8
10
  stream({ abortSignal, getToken }: ApiRequestContext<NotificationApiDefinition, 'stream'>): ApiServerResult<NotificationApiDefinition, 'stream'>;
11
+ types(): Promise<Record<string, string>>;
9
12
  listInApp({ parameters, getToken }: ApiRequestContext<NotificationApiDefinition, 'listInApp'>): Promise<any>;
10
13
  markRead({ parameters, getToken }: ApiRequestContext<NotificationApiDefinition, 'markRead'>): Promise<'ok'>;
11
14
  markAllRead({ getToken }: ApiRequestContext<NotificationApiDefinition, 'markAllRead'>): Promise<'ok'>;
@@ -10,9 +10,11 @@ import { toAsyncIterable } from '../../../rxjs-utils/index.js';
10
10
  import { decodeBase64 } from '../../../utils/base64.js';
11
11
  import { notificationApiDefinition } from '../../api/notification.api.js';
12
12
  import { NotificationSseService } from '../services/notification-sse.service.js';
13
+ import { NotificationTypeService } from '../services/notification-type.service.js';
13
14
  import { NotificationService } from '../services/notification.service.js';
14
15
  let NotificationApiController = class NotificationApiController {
15
16
  notificationService = inject(NotificationService);
17
+ notificationTypeService = inject(NotificationTypeService);
16
18
  sseService = inject(NotificationSseService);
17
19
  async *stream({ abortSignal, getToken }) {
18
20
  const token = await getToken();
@@ -26,6 +28,9 @@ let NotificationApiController = class NotificationApiController {
26
28
  this.sseService.unregister(token.payload.tenant, token.payload.subject, source);
27
29
  }
28
30
  }
31
+ async types() {
32
+ return await this.notificationTypeService.getTypes();
33
+ }
29
34
  async listInApp({ parameters, getToken }) {
30
35
  const token = await getToken();
31
36
  return await this.notificationService.listInApp(token.payload.tenant, token.payload.subject, parameters);
@@ -8,4 +8,5 @@ export type TypeInitializationData = {
8
8
  export declare class NotificationTypeService extends Transactional {
9
9
  readonly repository: import("../../../orm/server/index.js").EntityRepository<NotificationType>;
10
10
  initializeTypes<T extends string>(typeData: Record<T, TypeInitializationData>): Promise<Record<T, NotificationType>>;
11
+ getTypes(): Promise<Record<string, string>>;
11
12
  }
@@ -34,6 +34,11 @@ let NotificationTypeService = class NotificationTypeService extends Transactiona
34
34
  const mappedTypes = typeEntries.map(([key]) => [key, assertDefinedPass(typeMap.get(key), 'Could not map notification type.')]);
35
35
  return fromEntries(mappedTypes);
36
36
  }
37
+ async getTypes() {
38
+ const types = await this.repository.loadAll();
39
+ const entries = types.map((type) => [type.key, type.label]);
40
+ return fromEntries(entries);
41
+ }
37
42
  };
38
43
  NotificationTypeService = __decorate([
39
44
  Singleton()
@@ -5,6 +5,7 @@ import { setupIntegrationTest, truncateTables } from '../../unit-test/index.js';
5
5
  import { NotificationChannel } from '../models/index.js';
6
6
  import { NotificationApiController } from '../server/api/notification.api-controller.js';
7
7
  import { NotificationSseService } from '../server/services/notification-sse.service.js';
8
+ import { NotificationTypeService } from '../server/services/notification-type.service.js';
8
9
  import { NotificationService } from '../server/services/notification.service.js';
9
10
  describe('Notification API (Integration)', () => {
10
11
  let injector;
@@ -64,6 +65,13 @@ describe('Notification API (Integration)', () => {
64
65
  await nextPromise;
65
66
  expect(registerSpy).toHaveBeenCalledWith(tenantId, userId);
66
67
  });
68
+ test('types should call service', async () => {
69
+ const notificationTypeService = injector.resolve(NotificationTypeService);
70
+ const getTypesSpy = vi.spyOn(notificationTypeService, 'getTypes').mockResolvedValue({ test: 'Test' });
71
+ const result = await controller.types();
72
+ expect(result).toEqual({ test: 'Test' });
73
+ expect(getTypesSpy).toHaveBeenCalled();
74
+ });
67
75
  test('listInApp should call service', async () => {
68
76
  const listInAppSpy = vi.spyOn(notificationService, 'listInApp').mockResolvedValue([]);
69
77
  const params = { limit: 10, offset: 0, includeArchived: false };
@@ -17,4 +17,13 @@ describe('NotificationSseService', () => {
17
17
  await expect(service.send(msg, 1)).resolves.not.toThrow();
18
18
  });
19
19
  });
20
+ test('should dispatch unread count update', async () => {
21
+ const { injector } = await setupIntegrationTest({ modules: { messageBus: true, signals: true } });
22
+ const service = injector.resolve(NotificationSseService);
23
+ const tenantId = 't1';
24
+ const userId = 'u1';
25
+ await runInInjectionContext(injector, async () => {
26
+ await expect(service.dispatchUnreadCountUpdate(tenantId, userId, 5)).resolves.not.toThrow();
27
+ });
28
+ });
20
29
  });
@@ -0,0 +1,112 @@
1
+ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
2
+ import { AuthenticationClientService } from '../../../authentication/client/authentication.service.js';
3
+ import { Injector, runInInjectionContext } from '../../../injector/index.js';
4
+ import { NotificationApiClient } from '../../../notification/api/index.js';
5
+ import { NotificationClient } from '../../../notification/client/notification-client.js';
6
+ import { configureDefaultSignalsImplementation } from '../../../signals/implementation/configure.js';
7
+ import { BehaviorSubject, of, Subject } from 'rxjs';
8
+ describe('NotificationClient', () => {
9
+ let injector;
10
+ let authenticationServiceMock;
11
+ let notificationApiClientMock;
12
+ let notificationClient;
13
+ const sessionId$ = new BehaviorSubject(undefined);
14
+ const stream$ = new Subject();
15
+ beforeEach(() => {
16
+ configureDefaultSignalsImplementation();
17
+ injector = new Injector('TestInjector');
18
+ authenticationServiceMock = {
19
+ sessionId$,
20
+ };
21
+ notificationApiClientMock = {
22
+ listInApp: vi.fn().mockResolvedValue([]),
23
+ unreadCount: vi.fn().mockResolvedValue(0),
24
+ types: vi.fn().mockResolvedValue({}),
25
+ stream: vi.fn().mockReturnValue(of(stream$)),
26
+ };
27
+ injector.register(AuthenticationClientService, { useValue: authenticationServiceMock });
28
+ injector.register(NotificationApiClient, { useValue: notificationApiClientMock });
29
+ notificationClient = injector.resolve(NotificationClient);
30
+ });
31
+ afterEach(() => {
32
+ vi.clearAllMocks();
33
+ sessionId$.next(undefined);
34
+ });
35
+ test('should initialize with empty state', () => {
36
+ runInInjectionContext(injector, () => {
37
+ expect(notificationClient.notifications()).toEqual([]);
38
+ expect(notificationClient.unreadCount()).toBe(0);
39
+ expect(notificationClient.types()).toEqual({});
40
+ });
41
+ });
42
+ test('should load notifications on session start', async () => {
43
+ const notifications = [{ id: '1', type: 'test' }];
44
+ const unreadCount = 5;
45
+ const types = { test: 'Test' };
46
+ notificationApiClientMock.listInApp.mockResolvedValue(notifications);
47
+ notificationApiClientMock.unreadCount.mockResolvedValue(unreadCount);
48
+ notificationApiClientMock.types.mockResolvedValue(types);
49
+ await runInInjectionContext(injector, async () => {
50
+ sessionId$.next('session-1');
51
+ // Wait for async operations (microtasks)
52
+ await new Promise(resolve => setTimeout(resolve, 0));
53
+ expect(notificationClient.notifications()).toEqual(notifications);
54
+ expect(notificationClient.unreadCount()).toBe(unreadCount);
55
+ expect(notificationClient.types()).toEqual(types);
56
+ expect(notificationApiClientMock.listInApp).toHaveBeenCalledWith({ limit: 20 });
57
+ expect(notificationApiClientMock.unreadCount).toHaveBeenCalled();
58
+ expect(notificationApiClientMock.types).toHaveBeenCalled();
59
+ expect(notificationApiClientMock.stream).toHaveBeenCalled();
60
+ });
61
+ });
62
+ test('should clear notifications on session end', async () => {
63
+ const notifications = [{ id: '1', type: 'test' }];
64
+ notificationApiClientMock.listInApp.mockResolvedValue(notifications);
65
+ await runInInjectionContext(injector, async () => {
66
+ sessionId$.next('session-1');
67
+ await new Promise(resolve => setTimeout(resolve, 0));
68
+ expect(notificationClient.notifications()).toHaveLength(1);
69
+ sessionId$.next(undefined);
70
+ await new Promise(resolve => setTimeout(resolve, 0));
71
+ expect(notificationClient.notifications()).toEqual([]);
72
+ expect(notificationClient.unreadCount()).toBe(0);
73
+ });
74
+ });
75
+ test('should handle new notification from stream', async () => {
76
+ const initialNotifications = [{ id: '1', type: 'test' }];
77
+ notificationApiClientMock.listInApp.mockResolvedValue(initialNotifications);
78
+ await runInInjectionContext(injector, async () => {
79
+ sessionId$.next('session-1');
80
+ await new Promise(resolve => setTimeout(resolve, 0));
81
+ const newNotification = { id: '2', type: 'test' };
82
+ stream$.next({ notification: newNotification, unreadCount: 1 });
83
+ expect(notificationClient.notifications()).toEqual([newNotification, ...initialNotifications]);
84
+ expect(notificationClient.unreadCount()).toBe(1);
85
+ });
86
+ });
87
+ test('should handle unread count update from stream', async () => {
88
+ notificationApiClientMock.listInApp.mockResolvedValue([]);
89
+ await runInInjectionContext(injector, async () => {
90
+ sessionId$.next('session-1');
91
+ await new Promise(resolve => setTimeout(resolve, 0));
92
+ stream$.next({ unreadCount: 10 });
93
+ expect(notificationClient.unreadCount()).toBe(10);
94
+ });
95
+ });
96
+ test('should load next page of notifications', async () => {
97
+ const page1 = [{ id: '2', type: 'test' }];
98
+ const page2 = [{ id: '1', type: 'test' }];
99
+ notificationApiClientMock.listInApp
100
+ .mockResolvedValueOnce(page1) // Initial load
101
+ .mockResolvedValueOnce(page2); // Pagination
102
+ await runInInjectionContext(injector, async () => {
103
+ sessionId$.next('session-1');
104
+ await new Promise(resolve => setTimeout(resolve, 0));
105
+ expect(notificationClient.notifications()).toEqual(page1);
106
+ notificationClient.loadNext(10);
107
+ await new Promise(resolve => setTimeout(resolve, 0));
108
+ expect(notificationClient.notifications()).toEqual([...page1, ...page2]);
109
+ expect(notificationApiClientMock.listInApp).toHaveBeenCalledWith({ limit: 10, after: '2' });
110
+ });
111
+ });
112
+ });
@@ -3,17 +3,27 @@ import type { ObjectMetadata, ObjectStorageObject } from './object.js';
3
3
  export type UploadObjectOptions = {
4
4
  contentLength?: number;
5
5
  contentType?: string;
6
+ contentDisposition?: string;
7
+ cacheControl?: string;
6
8
  metadata?: ObjectMetadata;
7
9
  };
8
10
  export type UploadUrlOptions = {
9
11
  contentLength?: number;
10
12
  contentType?: string;
13
+ contentDisposition?: string;
14
+ cacheControl?: string;
11
15
  metadata?: ObjectMetadata;
12
16
  };
13
17
  export type CopyObjectOptions = {
18
+ contentType?: string;
19
+ contentDisposition?: string;
20
+ cacheControl?: string;
14
21
  metadata?: ObjectMetadata;
15
22
  };
16
23
  export type MoveObjectOptions = {
24
+ contentType?: string;
25
+ contentDisposition?: string;
26
+ cacheControl?: string;
17
27
  metadata?: ObjectMetadata;
18
28
  };
19
29
  export type ObjectStorageConfiguration = {
@@ -1,3 +1,4 @@
1
+ import { Injector } from '../../injector/injector.js';
1
2
  import { ObjectStorageProvider } from '../../object-storage/index.js';
2
3
  import { S3ObjectStorage } from './s3.object-storage.js';
3
4
  export declare class S3ObjectStorageProviderConfig {
@@ -29,11 +30,15 @@ export declare class S3ObjectStorageProviderConfig {
29
30
  * S3 secret key
30
31
  */
31
32
  secretKey: string;
33
+ /**
34
+ * Whether to use path-style addressing (e.g., http://s3.endpoint.tld/BUCKET/KEY) instead of virtual-host addressing (e.g., http://BUCKET.s3.endpoint.tld/KEY).
35
+ * Useful for local development with alternative s3 implementations.
36
+ */
37
+ forcePathStyle?: boolean;
32
38
  }
33
39
  export declare class S3ObjectStorageProvider extends ObjectStorageProvider<S3ObjectStorage> {
34
- private readonly client;
35
- private readonly bucket;
36
- constructor(config: S3ObjectStorageProviderConfig);
40
+ #private;
41
+ constructor();
37
42
  get(module: string): S3ObjectStorage;
38
43
  }
39
44
  /**
@@ -41,4 +46,6 @@ export declare class S3ObjectStorageProvider extends ObjectStorageProvider<S3Obj
41
46
  * @param config s3 config
42
47
  * @param register whether to register for {@link ObjectStorage} and {@link ObjectStorageProvider}
43
48
  */
44
- export declare function configureS3ObjectStorage(config: S3ObjectStorageProviderConfig, register?: boolean): void;
49
+ export declare function configureS3ObjectStorage(config: S3ObjectStorageProviderConfig & {
50
+ injector?: Injector;
51
+ }): void;
@@ -7,8 +7,8 @@ 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 { Client } from 'minio';
11
- import { Singleton } from '../../injector/decorators.js';
10
+ import { S3Client } from '@aws-sdk/client-s3';
11
+ import { inject, Singleton } from '../../injector/index.js';
12
12
  import { Injector } from '../../injector/injector.js';
13
13
  import { ObjectStorage, ObjectStorageProvider } from '../../object-storage/index.js';
14
14
  import { assertDefinedPass, assertStringPass, isDefined } from '../../utils/type-guards.js';
@@ -42,35 +42,39 @@ export class S3ObjectStorageProviderConfig {
42
42
  * S3 secret key
43
43
  */
44
44
  secretKey;
45
+ /**
46
+ * Whether to use path-style addressing (e.g., http://s3.endpoint.tld/BUCKET/KEY) instead of virtual-host addressing (e.g., http://BUCKET.s3.endpoint.tld/KEY).
47
+ * Useful for local development with alternative s3 implementations.
48
+ */
49
+ forcePathStyle;
45
50
  }
46
51
  let S3ObjectStorageProvider = class S3ObjectStorageProvider extends ObjectStorageProvider {
47
- client;
48
- bucket;
49
- constructor(config) {
52
+ #config = inject(S3ObjectStorageProviderConfig);
53
+ #client = new S3Client({
54
+ endpoint: this.#config.endpoint,
55
+ region: this.#config.region ?? 'garage',
56
+ credentials: {
57
+ accessKeyId: this.#config.accessKey,
58
+ secretAccessKey: this.#config.secretKey,
59
+ },
60
+ forcePathStyle: this.#config.forcePathStyle,
61
+ });
62
+ #bucket = assertDefinedPass((this.#config.bucketPerModule == true) ? true : this.#config.bucket, 'either bucket or bucketPerModule must be specified');
63
+ constructor() {
50
64
  super();
51
- const { hostname, port, protocol } = new URL(config.endpoint);
52
- if (isDefined(config.bucket) && (config.bucketPerModule == true)) {
65
+ if (isDefined(this.#config.bucket) && (this.#config.bucketPerModule == true)) {
53
66
  throw new Error('bucket and bucketPerModule is mutually exclusive');
54
67
  }
55
- this.client = new Client({
56
- endPoint: hostname,
57
- region: config.region,
58
- port: (port.length > 0) ? parseInt(port, 10) : undefined,
59
- useSSL: protocol == 'https:',
60
- accessKey: config.accessKey,
61
- secretKey: config.secretKey,
62
- });
63
- this.bucket = assertDefinedPass((config.bucketPerModule == true) ? true : config.bucket, 'either bucket or bucketPerModule must be specified');
64
68
  }
65
69
  get(module) {
66
- const bucket = (this.bucket == true) ? module : assertStringPass(this.bucket);
67
- const prefix = (this.bucket == true) ? '' : ((module == '') ? '' : `${module}/`);
68
- return new S3ObjectStorage(this.client, bucket, module, prefix);
70
+ const bucket = (this.#bucket == true) ? module : assertStringPass(this.#bucket);
71
+ const prefix = (this.#bucket == true) ? '' : ((module == '') ? '' : `${module}/`);
72
+ return new S3ObjectStorage(this.#client, bucket, module, prefix);
69
73
  }
70
74
  };
71
75
  S3ObjectStorageProvider = __decorate([
72
76
  Singleton(),
73
- __metadata("design:paramtypes", [S3ObjectStorageProviderConfig])
77
+ __metadata("design:paramtypes", [])
74
78
  ], S3ObjectStorageProvider);
75
79
  export { S3ObjectStorageProvider };
76
80
  /**
@@ -78,10 +82,9 @@ export { S3ObjectStorageProvider };
78
82
  * @param config s3 config
79
83
  * @param register whether to register for {@link ObjectStorage} and {@link ObjectStorageProvider}
80
84
  */
81
- export function configureS3ObjectStorage(config, register = true) {
82
- Injector.register(S3ObjectStorageProviderConfig, { useValue: config });
83
- if (register) {
84
- Injector.registerSingleton(ObjectStorageProvider, { useToken: S3ObjectStorageProvider });
85
- Injector.registerSingleton(ObjectStorage, { useToken: S3ObjectStorage });
86
- }
85
+ export function configureS3ObjectStorage(config) {
86
+ const targetInjector = config.injector ?? Injector;
87
+ targetInjector.register(S3ObjectStorageProviderConfig, { useValue: config });
88
+ targetInjector.registerSingleton(ObjectStorageProvider, { useToken: S3ObjectStorageProvider });
89
+ targetInjector.registerSingleton(ObjectStorage, { useToken: S3ObjectStorage });
87
90
  }
@@ -1,17 +1,17 @@
1
- import { type BucketItemStat, type Client } from 'minio';
1
+ import { S3Client } from '@aws-sdk/client-s3';
2
2
  import { ObjectStorage, type CopyObjectOptions, type MoveObjectOptions, type ObjectStorageConfiguration, type UploadObjectOptions, type UploadUrlOptions } from '../../object-storage/index.js';
3
- import { S3Object } from './s3.object.js';
3
+ import { S3Object, type S3BucketItemStat } from './s3.object.js';
4
4
  export declare class S3ObjectStorage extends ObjectStorage {
5
5
  private readonly client;
6
6
  private readonly bucket;
7
7
  private readonly prefix;
8
- constructor(client: Client, bucket: string, module: string, keyPrefix: string);
8
+ constructor(client: S3Client, bucket: string, module: string, keyPrefix: string);
9
9
  ensureBucketExists(region?: string, options?: {
10
10
  objectLocking?: boolean;
11
11
  }): Promise<void>;
12
12
  configureBucket(configuration: ObjectStorageConfiguration): Promise<void>;
13
13
  exists(key: string): Promise<boolean>;
14
- statObject(key: string): Promise<BucketItemStat>;
14
+ statObject(key: string): Promise<S3BucketItemStat>;
15
15
  uploadObject(key: string, content: Uint8Array | ReadableStream<Uint8Array>, options?: UploadObjectOptions): Promise<S3Object>;
16
16
  copyObject(source: S3Object | string, destination: S3Object | string | [ObjectStorage, string], options?: CopyObjectOptions): Promise<S3Object>;
17
17
  moveObject(source: S3Object | string, destination: S3Object | string | [ObjectStorage, string], options?: MoveObjectOptions): Promise<S3Object>;
@@ -28,4 +28,7 @@ export declare class S3ObjectStorage extends ObjectStorage {
28
28
  private getResourceUriSync;
29
29
  private getBucketKey;
30
30
  private getKey;
31
+ isNotFoundError(error: unknown): boolean;
32
+ isForbiddenError(error: unknown): boolean;
33
+ isError(error: unknown, ...names: string[]): boolean;
31
34
  }