@velajs/vela 1.9.0 → 1.12.0
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/CHANGELOG.md +122 -0
- package/dist/__tests__/workers-als/entry-als.d.ts +9 -0
- package/dist/application.d.ts +18 -0
- package/dist/application.js +45 -0
- package/dist/cache/cache.module.d.ts +5 -7
- package/dist/cache/cache.module.js +44 -66
- package/dist/cache/cache.types.d.ts +26 -0
- package/dist/cache/cache.types.js +1 -1
- package/dist/cache/index.d.ts +2 -1
- package/dist/cache/index.js +1 -0
- package/dist/cache/tiered-cache.store.d.ts +19 -0
- package/dist/cache/tiered-cache.store.js +40 -0
- package/dist/config/config.module.d.ts +6 -5
- package/dist/config/config.module.js +46 -49
- package/dist/container/container.d.ts +58 -0
- package/dist/container/container.js +223 -18
- package/dist/container/disposable.d.ts +5 -0
- package/dist/container/disposable.js +33 -0
- package/dist/container/types.d.ts +8 -0
- package/dist/cors/cors.module.d.ts +5 -5
- package/dist/cors/cors.module.js +26 -25
- package/dist/discovery/discoverable.decorator.d.ts +38 -0
- package/dist/discovery/discoverable.decorator.js +42 -0
- package/dist/discovery/discovery.service.d.ts +94 -0
- package/dist/discovery/discovery.service.js +166 -0
- package/dist/discovery/index.d.ts +2 -0
- package/dist/discovery/index.js +2 -0
- package/dist/entrypoint/entrypoint.registry.d.ts +40 -0
- package/dist/entrypoint/entrypoint.registry.js +106 -0
- package/dist/entrypoint/entrypoint.types.d.ts +41 -0
- package/dist/entrypoint/entrypoint.types.js +3 -0
- package/dist/entrypoint/execution-context.d.ts +15 -0
- package/dist/entrypoint/execution-context.js +21 -0
- package/dist/entrypoint/execution-scope.d.ts +21 -0
- package/dist/entrypoint/execution-scope.js +28 -0
- package/dist/entrypoint/index.d.ts +4 -0
- package/dist/entrypoint/index.js +4 -0
- package/dist/event-emitter/event-emitter.subscriber.d.ts +3 -3
- package/dist/event-emitter/event-emitter.subscriber.js +12 -29
- package/dist/factory/adapter.d.ts +36 -0
- package/dist/factory/adapter.js +18 -0
- package/dist/factory/bootstrap.js +20 -2
- package/dist/factory.d.ts +11 -1
- package/dist/factory.js +23 -2
- package/dist/fetch/fetch.module.d.ts +5 -6
- package/dist/fetch/fetch.module.js +9 -42
- package/dist/http/ambient.d.ts +17 -0
- package/dist/http/ambient.js +44 -0
- package/dist/http/execution-context.js +4 -1
- package/dist/http/handler-executor.js +22 -19
- package/dist/http/index.d.ts +1 -0
- package/dist/http/index.js +1 -0
- package/dist/http/route-contributor.d.ts +68 -0
- package/dist/http/route-contributor.js +22 -0
- package/dist/http/route.manager.d.ts +19 -0
- package/dist/http/route.manager.js +112 -19
- package/dist/i18n/deep-merge.d.ts +2 -0
- package/dist/i18n/deep-merge.js +15 -0
- package/dist/i18n/i18n.middleware.d.ts +15 -0
- package/dist/i18n/i18n.middleware.js +94 -0
- package/dist/i18n/i18n.module.d.ts +13 -0
- package/dist/i18n/i18n.module.js +64 -0
- package/dist/i18n/i18n.options.d.ts +30 -0
- package/dist/i18n/i18n.options.js +16 -0
- package/dist/i18n/i18n.service.d.ts +20 -0
- package/dist/i18n/i18n.service.js +49 -0
- package/dist/i18n/i18n.tokens.d.ts +6 -0
- package/dist/i18n/i18n.tokens.js +3 -0
- package/dist/i18n/i18n.types.d.ts +6 -0
- package/dist/i18n/i18n.types.js +1 -0
- package/dist/i18n/index.d.ts +10 -0
- package/dist/i18n/index.js +8 -0
- package/dist/i18n/message-loader.service.d.ts +21 -0
- package/dist/i18n/message-loader.service.js +90 -0
- package/dist/i18n/message-registry.d.ts +16 -0
- package/dist/i18n/message-registry.js +49 -0
- package/dist/index.d.ts +17 -5
- package/dist/index.js +14 -3
- package/dist/internal.d.ts +0 -2
- package/dist/internal.js +0 -7
- package/dist/module/configurable-module.builder.d.ts +56 -0
- package/dist/module/configurable-module.builder.js +84 -0
- package/dist/module/configurable-module.types.d.ts +93 -0
- package/dist/module/configurable-module.types.js +5 -0
- package/dist/module/define-module.d.ts +105 -0
- package/dist/module/define-module.js +227 -0
- package/dist/module/index.d.ts +4 -0
- package/dist/module/index.js +3 -0
- package/dist/module/lazy-provider.d.ts +65 -0
- package/dist/module/lazy-provider.js +111 -0
- package/dist/openapi/document.js +16 -15
- package/dist/pipeline/component.manager.d.ts +15 -9
- package/dist/pipeline/component.manager.js +32 -46
- package/dist/pipeline/index.d.ts +2 -0
- package/dist/pipeline/index.js +1 -0
- package/dist/pipeline/pipeline-runner.d.ts +31 -0
- package/dist/pipeline/pipeline-runner.js +40 -0
- package/dist/pipeline/types.d.ts +21 -4
- package/dist/registry/metadata.registry.d.ts +24 -18
- package/dist/registry/metadata.registry.js +137 -49
- package/dist/schedule/schedule.module.d.ts +5 -0
- package/dist/schedule/schedule.module.js +18 -8
- package/dist/schedule/schedule.registry.d.ts +3 -3
- package/dist/schedule/schedule.registry.js +23 -44
- package/dist/schedule-node/schedule-node.module.d.ts +5 -0
- package/dist/schedule-node/schedule-node.module.js +20 -10
- package/dist/seeder/index.d.ts +5 -0
- package/dist/seeder/index.js +4 -0
- package/dist/seeder/seeder.decorator.d.ts +7 -0
- package/dist/seeder/seeder.decorator.js +15 -0
- package/dist/seeder/seeder.module.d.ts +16 -0
- package/dist/seeder/seeder.module.js +41 -0
- package/dist/seeder/seeder.registry.d.ts +23 -0
- package/dist/seeder/seeder.registry.js +83 -0
- package/dist/seeder/seeder.tokens.d.ts +2 -0
- package/dist/seeder/seeder.tokens.js +1 -0
- package/dist/seeder/seeder.types.d.ts +20 -0
- package/dist/seeder/seeder.types.js +1 -0
- package/dist/storage/index.d.ts +4 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage/path-template.d.ts +9 -0
- package/dist/storage/path-template.js +20 -0
- package/dist/storage/signed-url.d.ts +12 -0
- package/dist/storage/signed-url.js +55 -0
- package/dist/storage/storage.types.d.ts +41 -0
- package/dist/storage/storage.types.js +5 -0
- package/dist/throttler/throttler.module.d.ts +5 -8
- package/dist/throttler/throttler.module.js +27 -55
- package/dist/websocket/index.d.ts +12 -0
- package/dist/websocket/index.js +16 -0
- package/dist/websocket/websocket.decorators.d.ts +33 -0
- package/dist/websocket/websocket.decorators.js +58 -0
- package/dist/websocket/websocket.module.d.ts +35 -0
- package/dist/websocket/websocket.module.js +63 -0
- package/dist/websocket/websocket.tokens.d.ts +23 -0
- package/dist/websocket/websocket.tokens.js +21 -0
- package/dist/websocket/websocket.types.d.ts +86 -0
- package/dist/websocket/websocket.types.js +4 -0
- package/dist/websocket/ws-argument-resolver.d.ts +13 -0
- package/dist/websocket/ws-argument-resolver.js +40 -0
- package/dist/websocket/ws-dispatcher.d.ts +45 -0
- package/dist/websocket/ws-dispatcher.js +259 -0
- package/dist/websocket/ws-exception.d.ts +16 -0
- package/dist/websocket/ws-exception.js +32 -0
- package/dist/websocket/ws-execution-context.d.ts +9 -0
- package/dist/websocket/ws-execution-context.js +24 -0
- package/dist/websocket/ws-server.d.ts +31 -0
- package/dist/websocket/ws-server.js +74 -0
- package/dist/websocket/ws-sync.d.ts +47 -0
- package/dist/websocket/ws-sync.js +74 -0
- package/dist/websocket-node/index.d.ts +4 -0
- package/dist/websocket-node/index.js +5 -0
- package/dist/websocket-node/node-ws-client.d.ts +24 -0
- package/dist/websocket-node/node-ws-client.js +49 -0
- package/dist/websocket-node/redis-sync.d.ts +33 -0
- package/dist/websocket-node/redis-sync.js +56 -0
- package/dist/websocket-node/register-gateways.d.ts +20 -0
- package/dist/websocket-node/register-gateways.js +61 -0
- package/package.json +31 -1
- package/dist/http/crud-bridge.d.ts +0 -90
- package/dist/http/crud-bridge.js +0 -25
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
}
|
|
7
|
+
import { Module } from "../module/decorators.js";
|
|
8
|
+
import { defineModule } from "../module/define-module.js";
|
|
9
|
+
import { stableHash } from "../module/stable-hash.js";
|
|
10
|
+
import { SeederRegistry } from "./seeder.registry.js";
|
|
11
|
+
const { ConfigurableModuleClass } = defineModule({
|
|
12
|
+
name: 'Seeder',
|
|
13
|
+
// Class references aren't value-hashable — key on the seeder names, the same
|
|
14
|
+
// identity the hand-rolled forRoot used.
|
|
15
|
+
key: (options)=>stableHash({
|
|
16
|
+
seeders: (options.seeders ?? []).map((s)=>s.name)
|
|
17
|
+
}),
|
|
18
|
+
setup: ({ options })=>({
|
|
19
|
+
// Register seeder classes as providers so they're discovered at bootstrap.
|
|
20
|
+
// (Seeders declared in other modules' providers are also discovered — this
|
|
21
|
+
// is just a convenience to co-locate them.)
|
|
22
|
+
providers: [
|
|
23
|
+
...options.seeders ?? []
|
|
24
|
+
]
|
|
25
|
+
})
|
|
26
|
+
});
|
|
27
|
+
export class SeederModule extends ConfigurableModuleClass {
|
|
28
|
+
}
|
|
29
|
+
SeederModule = _ts_decorate([
|
|
30
|
+
Module({
|
|
31
|
+
providers: [
|
|
32
|
+
SeederRegistry
|
|
33
|
+
],
|
|
34
|
+
exports: [
|
|
35
|
+
SeederRegistry
|
|
36
|
+
]
|
|
37
|
+
})
|
|
38
|
+
], SeederModule);
|
|
39
|
+
/** Convenience runner: resolve the registry from a built app and run all seeders. */ export function runSeeders(app, options) {
|
|
40
|
+
return app.get(SeederRegistry).runAll(options);
|
|
41
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Container } from '../container/container';
|
|
2
|
+
import type { OnApplicationBootstrap } from '../lifecycle/index';
|
|
3
|
+
import type { RegisteredSeeder, SeederResult } from './seeder.types';
|
|
4
|
+
/**
|
|
5
|
+
* Discovers `@Seeder()` providers at bootstrap and runs them on demand.
|
|
6
|
+
* Mirrors `ScheduleRegistry`'s discovery (scan container tokens → read metadata).
|
|
7
|
+
*/
|
|
8
|
+
export declare class SeederRegistry implements OnApplicationBootstrap {
|
|
9
|
+
private readonly container;
|
|
10
|
+
private seeders;
|
|
11
|
+
constructor(container: Container);
|
|
12
|
+
onApplicationBootstrap(): void;
|
|
13
|
+
/** Registered seeders in run order. */
|
|
14
|
+
list(): RegisteredSeeder[];
|
|
15
|
+
/**
|
|
16
|
+
* Run every seeder in order, each in its own request-scoped child container so
|
|
17
|
+
* request-scoped dependencies resolve. Returns a per-seeder result; when
|
|
18
|
+
* `stopOnError` is true (default) the run aborts on the first failure.
|
|
19
|
+
*/
|
|
20
|
+
runAll(options?: {
|
|
21
|
+
stopOnError?: boolean;
|
|
22
|
+
}): Promise<SeederResult[]>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
}
|
|
7
|
+
function _ts_metadata(k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
}
|
|
10
|
+
function _ts_param(paramIndex, decorator) {
|
|
11
|
+
return function(target, key) {
|
|
12
|
+
decorator(target, key, paramIndex);
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
import { Container } from "../container/container.js";
|
|
16
|
+
import { Inject, Injectable } from "../container/index.js";
|
|
17
|
+
import { MetadataRegistry } from "../registry/metadata.registry.js";
|
|
18
|
+
import { SEEDER_METADATA } from "./seeder.tokens.js";
|
|
19
|
+
export class SeederRegistry {
|
|
20
|
+
container;
|
|
21
|
+
seeders = [];
|
|
22
|
+
constructor(container){
|
|
23
|
+
this.container = container;
|
|
24
|
+
}
|
|
25
|
+
onApplicationBootstrap() {
|
|
26
|
+
for (const token of this.container.getTokens()){
|
|
27
|
+
if (typeof token !== 'function') continue;
|
|
28
|
+
const meta = MetadataRegistry.getCustomClassMeta(token, SEEDER_METADATA);
|
|
29
|
+
if (!meta) continue;
|
|
30
|
+
this.seeders.push({
|
|
31
|
+
target: token,
|
|
32
|
+
name: meta.name ?? token.name,
|
|
33
|
+
order: meta.order ?? 0
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
this.seeders.sort((a, b)=>a.order - b.order);
|
|
37
|
+
}
|
|
38
|
+
/** Registered seeders in run order. */ list() {
|
|
39
|
+
return [
|
|
40
|
+
...this.seeders
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Run every seeder in order, each in its own request-scoped child container so
|
|
45
|
+
* request-scoped dependencies resolve. Returns a per-seeder result; when
|
|
46
|
+
* `stopOnError` is true (default) the run aborts on the first failure.
|
|
47
|
+
*/ async runAll(options = {}) {
|
|
48
|
+
const stopOnError = options.stopOnError ?? true;
|
|
49
|
+
const results = [];
|
|
50
|
+
for (const seeder of this.seeders){
|
|
51
|
+
const child = this.container.createChild();
|
|
52
|
+
let failed = false;
|
|
53
|
+
try {
|
|
54
|
+
const instance = child.resolve(seeder.target);
|
|
55
|
+
await instance.run();
|
|
56
|
+
results.push({
|
|
57
|
+
name: seeder.name,
|
|
58
|
+
ok: true
|
|
59
|
+
});
|
|
60
|
+
} catch (error) {
|
|
61
|
+
results.push({
|
|
62
|
+
name: seeder.name,
|
|
63
|
+
ok: false,
|
|
64
|
+
error
|
|
65
|
+
});
|
|
66
|
+
failed = true;
|
|
67
|
+
} finally{
|
|
68
|
+
// Dispose the per-seeder child so its request-scoped disposables clean up.
|
|
69
|
+
await child.dispose();
|
|
70
|
+
}
|
|
71
|
+
if (failed && stopOnError) break;
|
|
72
|
+
}
|
|
73
|
+
return results;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
SeederRegistry = _ts_decorate([
|
|
77
|
+
Injectable(),
|
|
78
|
+
_ts_param(0, Inject(Container)),
|
|
79
|
+
_ts_metadata("design:type", Function),
|
|
80
|
+
_ts_metadata("design:paramtypes", [
|
|
81
|
+
typeof Container === "undefined" ? Object : Container
|
|
82
|
+
])
|
|
83
|
+
], SeederRegistry);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/** Custom class-metadata key for `@Seeder()` (read during bootstrap discovery). */ export const SEEDER_METADATA = 'vela:seeder';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** A database seeder. Implement `run()` and tag the class with `@Seeder()`. */
|
|
2
|
+
export interface Seeder {
|
|
3
|
+
run(): void | Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
export interface SeederMetadata {
|
|
6
|
+
/** Display name (defaults to the class name). */
|
|
7
|
+
name?: string;
|
|
8
|
+
/** Ascending run order (default 0). */
|
|
9
|
+
order?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface RegisteredSeeder {
|
|
12
|
+
name: string;
|
|
13
|
+
order: number;
|
|
14
|
+
target: new (...args: any[]) => Seeder;
|
|
15
|
+
}
|
|
16
|
+
export interface SeederResult {
|
|
17
|
+
name: string;
|
|
18
|
+
ok: boolean;
|
|
19
|
+
error?: unknown;
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/** A database seeder. Implement `run()` and tag the class with `@Seeder()`. */ export { };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { signUrl, verifySignedUrl } from './signed-url';
|
|
2
|
+
export type { SignedUrlOptions } from './signed-url';
|
|
3
|
+
export { expandPathTemplate, joinStoragePath } from './path-template';
|
|
4
|
+
export type { StorageBody, StorageDriver, PresignMethod, UploadOptions, UploadResult, DownloadResult, PresignedUrlResult, } from './storage.types';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function expandPathTemplate(template: string, now?: Date): string;
|
|
2
|
+
/**
|
|
3
|
+
* Join a (templated) root with a relative path into a storage key. Segments are
|
|
4
|
+
* sanitized: `.`, `..`, and empty segments are dropped, so a caller-supplied
|
|
5
|
+
* `relativePath` containing `..` can never traverse above the disk root (a real
|
|
6
|
+
* hazard for the presign proxy, where the key flows through `new URL()` which
|
|
7
|
+
* would otherwise normalize `..`). Backslashes are treated as separators too.
|
|
8
|
+
*/
|
|
9
|
+
export declare function joinStoragePath(root: string | undefined, relativePath: string, now?: Date): string;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Expand path-template variables in a disk root / key. Supported tokens:
|
|
2
|
+
// {date} → YYYY-MM-DD {year} → YYYY {month} → MM {day} → DD
|
|
3
|
+
// {uuid} → crypto.randomUUID()
|
|
4
|
+
// `now` is injectable for deterministic tests.
|
|
5
|
+
export function expandPathTemplate(template, now = new Date()) {
|
|
6
|
+
const yyyy = now.getUTCFullYear().toString();
|
|
7
|
+
const mm = (now.getUTCMonth() + 1).toString().padStart(2, '0');
|
|
8
|
+
const dd = now.getUTCDate().toString().padStart(2, '0');
|
|
9
|
+
return template.replace(/\{date\}/g, `${yyyy}-${mm}-${dd}`).replace(/\{year\}/g, yyyy).replace(/\{month\}/g, mm).replace(/\{day\}/g, dd).replace(/\{uuid\}/g, ()=>crypto.randomUUID());
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Join a (templated) root with a relative path into a storage key. Segments are
|
|
13
|
+
* sanitized: `.`, `..`, and empty segments are dropped, so a caller-supplied
|
|
14
|
+
* `relativePath` containing `..` can never traverse above the disk root (a real
|
|
15
|
+
* hazard for the presign proxy, where the key flows through `new URL()` which
|
|
16
|
+
* would otherwise normalize `..`). Backslashes are treated as separators too.
|
|
17
|
+
*/ export function joinStoragePath(root, relativePath, now) {
|
|
18
|
+
const expandedRoot = root ? expandPathTemplate(root, now) : '';
|
|
19
|
+
return `${expandedRoot}/${relativePath}`.split(/[/\\]+/).filter((segment)=>segment !== '' && segment !== '.' && segment !== '..').join('/');
|
|
20
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface SignedUrlOptions {
|
|
2
|
+
/** Time-to-live in seconds; a matching `expires` param is added + enforced. */
|
|
3
|
+
expiresIn?: number;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Sign a URL (or path) with HMAC-SHA256, appending `signature` (and `expires`).
|
|
7
|
+
* The signature covers `pathname + search` (minus the signature param). Returns
|
|
8
|
+
* the full URL for absolute inputs, or `pathname?search` for relative ones.
|
|
9
|
+
*/
|
|
10
|
+
export declare function signUrl(url: string, secret: string, options?: SignedUrlOptions): Promise<string>;
|
|
11
|
+
/** Verify a signed URL (timing-safe) and enforce `expires`. */
|
|
12
|
+
export declare function verifySignedUrl(url: string, secret: string): Promise<boolean>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Signed-URL utilities using HMAC-SHA256 via the Web Crypto API (edge-safe;
|
|
2
|
+
// no node:crypto). Verification uses crypto.subtle.verify for timing-safety.
|
|
3
|
+
// Pattern: https://developers.cloudflare.com/workers/examples/signing-requests/
|
|
4
|
+
async function importKey(secret) {
|
|
5
|
+
return crypto.subtle.importKey('raw', new TextEncoder().encode(secret), {
|
|
6
|
+
name: 'HMAC',
|
|
7
|
+
hash: 'SHA-256'
|
|
8
|
+
}, false, [
|
|
9
|
+
'sign',
|
|
10
|
+
'verify'
|
|
11
|
+
]);
|
|
12
|
+
}
|
|
13
|
+
function toBase64Url(buffer) {
|
|
14
|
+
const bytes = new Uint8Array(buffer);
|
|
15
|
+
let binary = '';
|
|
16
|
+
for (const byte of bytes)binary += String.fromCharCode(byte);
|
|
17
|
+
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
18
|
+
}
|
|
19
|
+
function fromBase64Url(value) {
|
|
20
|
+
const base64 = value.replace(/-/g, '+').replace(/_/g, '/');
|
|
21
|
+
const binary = atob(base64);
|
|
22
|
+
const bytes = new Uint8Array(new ArrayBuffer(binary.length));
|
|
23
|
+
for(let i = 0; i < binary.length; i++)bytes[i] = binary.charCodeAt(i);
|
|
24
|
+
return bytes;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Sign a URL (or path) with HMAC-SHA256, appending `signature` (and `expires`).
|
|
28
|
+
* The signature covers `pathname + search` (minus the signature param). Returns
|
|
29
|
+
* the full URL for absolute inputs, or `pathname?search` for relative ones.
|
|
30
|
+
*/ export async function signUrl(url, secret, options) {
|
|
31
|
+
const parsed = new URL(url, 'https://placeholder.local');
|
|
32
|
+
const key = await importKey(secret);
|
|
33
|
+
if (options?.expiresIn) {
|
|
34
|
+
const expires = Math.floor(Date.now() / 1000) + options.expiresIn;
|
|
35
|
+
parsed.searchParams.set('expires', String(expires));
|
|
36
|
+
}
|
|
37
|
+
const dataToSign = `${parsed.pathname}?${parsed.searchParams.toString()}`;
|
|
38
|
+
const signatureBuffer = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(dataToSign));
|
|
39
|
+
parsed.searchParams.set('signature', toBase64Url(signatureBuffer));
|
|
40
|
+
return url.startsWith('http') ? parsed.toString() : `${parsed.pathname}?${parsed.searchParams.toString()}`;
|
|
41
|
+
}
|
|
42
|
+
/** Verify a signed URL (timing-safe) and enforce `expires`. */ export async function verifySignedUrl(url, secret) {
|
|
43
|
+
const parsed = new URL(url, 'https://placeholder.local');
|
|
44
|
+
const signature = parsed.searchParams.get('signature');
|
|
45
|
+
if (!signature) return false;
|
|
46
|
+
const expires = parsed.searchParams.get('expires');
|
|
47
|
+
if (expires) {
|
|
48
|
+
const expiryTime = parseInt(expires, 10);
|
|
49
|
+
if (Number.isNaN(expiryTime) || Math.floor(Date.now() / 1000) > expiryTime) return false;
|
|
50
|
+
}
|
|
51
|
+
parsed.searchParams.delete('signature');
|
|
52
|
+
const dataToVerify = `${parsed.pathname}?${parsed.searchParams.toString()}`;
|
|
53
|
+
const key = await importKey(secret);
|
|
54
|
+
return crypto.subtle.verify('HMAC', key, fromBase64Url(signature), new TextEncoder().encode(dataToVerify));
|
|
55
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Body payloads accepted by uploads. */
|
|
2
|
+
export type StorageBody = ReadableStream | ArrayBuffer | ArrayBufferView | string | Blob | null;
|
|
3
|
+
export type PresignMethod = 'GET' | 'PUT' | 'DELETE' | 'HEAD';
|
|
4
|
+
export interface UploadOptions {
|
|
5
|
+
size?: number;
|
|
6
|
+
mimeType?: string;
|
|
7
|
+
metadata?: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
export interface UploadResult {
|
|
10
|
+
path: string;
|
|
11
|
+
disk: string;
|
|
12
|
+
size?: number;
|
|
13
|
+
mimeType: string;
|
|
14
|
+
uploadedAt: Date;
|
|
15
|
+
}
|
|
16
|
+
export interface DownloadResult {
|
|
17
|
+
toStream(): ReadableStream;
|
|
18
|
+
toArrayBuffer(): Promise<ArrayBuffer>;
|
|
19
|
+
toText(): Promise<string>;
|
|
20
|
+
contentType: string;
|
|
21
|
+
size: number;
|
|
22
|
+
metadata?: Record<string, string>;
|
|
23
|
+
}
|
|
24
|
+
export interface PresignedUrlResult {
|
|
25
|
+
url: string;
|
|
26
|
+
method: PresignMethod;
|
|
27
|
+
expiresIn: number;
|
|
28
|
+
expiresAt: Date;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Runtime-agnostic storage driver contract. Implement per backend (R2 in
|
|
32
|
+
* `@velajs/cloudflare`, and e.g. S3/GCS elsewhere) so the manager/service and
|
|
33
|
+
* multi-disk logic stay driver-independent.
|
|
34
|
+
*/
|
|
35
|
+
export interface StorageDriver {
|
|
36
|
+
upload(body: StorageBody, path: string, options: UploadOptions): Promise<UploadResult>;
|
|
37
|
+
download(path: string): Promise<DownloadResult>;
|
|
38
|
+
delete(path: string): Promise<void>;
|
|
39
|
+
exists(path: string): Promise<boolean>;
|
|
40
|
+
getPresignedUrl(path: string, method: PresignMethod, expiresIn: number): Promise<PresignedUrlResult>;
|
|
41
|
+
}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import type { AsyncModuleOptions, DynamicModule } from '../module/types';
|
|
2
1
|
import type { ThrottlerModuleOptions } from './throttler.types';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
static forRootAsync(options: AsyncModuleOptions<ThrottlerModuleOptions> & {
|
|
8
|
-
key?: string;
|
|
9
|
-
}): DynamicModule;
|
|
2
|
+
declare const ConfigurableModuleClass: import("..").ConfigurableModuleClassType<ThrottlerModuleOptions, "forRoot", "create", {
|
|
3
|
+
isGlobal?: boolean;
|
|
4
|
+
}>;
|
|
5
|
+
export declare class ThrottlerModule extends ConfigurableModuleClass {
|
|
10
6
|
}
|
|
7
|
+
export {};
|
|
@@ -1,48 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
}
|
|
7
|
+
import { Module } from "../module/decorators.js";
|
|
8
|
+
import { ConfigurableModuleBuilder } from "../module/configurable-module.builder.js";
|
|
2
9
|
import { APP_GUARD } from "../pipeline/tokens.js";
|
|
3
10
|
import { ThrottlerGuard } from "./throttler.guard.js";
|
|
4
11
|
import { ThrottlerStorage } from "./throttler.storage.js";
|
|
5
12
|
import { THROTTLER_OPTIONS, THROTTLER_STORAGE } from "./throttler.tokens.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
useValue: options.storage ?? new ThrottlerStorage()
|
|
16
|
-
},
|
|
17
|
-
ThrottlerGuard,
|
|
18
|
-
{
|
|
19
|
-
provide: APP_GUARD,
|
|
20
|
-
useExisting: ThrottlerGuard
|
|
21
|
-
}
|
|
22
|
-
];
|
|
23
|
-
return {
|
|
24
|
-
module: ThrottlerModule,
|
|
25
|
-
key: options.key ?? stableHash(options),
|
|
26
|
-
providers,
|
|
27
|
-
exports: [
|
|
28
|
-
THROTTLER_OPTIONS,
|
|
29
|
-
THROTTLER_STORAGE,
|
|
30
|
-
ThrottlerGuard
|
|
31
|
-
]
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
static forRootAsync(options) {
|
|
35
|
-
const providers = [
|
|
36
|
-
{
|
|
37
|
-
provide: THROTTLER_OPTIONS,
|
|
38
|
-
useFactory: options.useFactory,
|
|
39
|
-
inject: options.inject ?? []
|
|
40
|
-
},
|
|
13
|
+
const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder({
|
|
14
|
+
moduleName: 'Throttler',
|
|
15
|
+
optionsInjectionToken: THROTTLER_OPTIONS
|
|
16
|
+
}).build();
|
|
17
|
+
export class ThrottlerModule extends ConfigurableModuleClass {
|
|
18
|
+
}
|
|
19
|
+
ThrottlerModule = _ts_decorate([
|
|
20
|
+
Module({
|
|
21
|
+
providers: [
|
|
41
22
|
{
|
|
42
23
|
provide: THROTTLER_STORAGE,
|
|
43
|
-
useFactory: (
|
|
24
|
+
useFactory: (options)=>options.storage ?? new ThrottlerStorage(),
|
|
44
25
|
inject: [
|
|
45
|
-
|
|
26
|
+
MODULE_OPTIONS_TOKEN
|
|
46
27
|
]
|
|
47
28
|
},
|
|
48
29
|
ThrottlerGuard,
|
|
@@ -50,20 +31,11 @@ export class ThrottlerModule {
|
|
|
50
31
|
provide: APP_GUARD,
|
|
51
32
|
useExisting: ThrottlerGuard
|
|
52
33
|
}
|
|
53
|
-
]
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
providers,
|
|
62
|
-
exports: [
|
|
63
|
-
THROTTLER_OPTIONS,
|
|
64
|
-
THROTTLER_STORAGE,
|
|
65
|
-
ThrottlerGuard
|
|
66
|
-
]
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
}
|
|
34
|
+
],
|
|
35
|
+
exports: [
|
|
36
|
+
THROTTLER_OPTIONS,
|
|
37
|
+
THROTTLER_STORAGE,
|
|
38
|
+
ThrottlerGuard
|
|
39
|
+
]
|
|
40
|
+
})
|
|
41
|
+
], ThrottlerModule);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { WebSocketGateway, SubscribeMessage, MessageBody, ConnectedSocket, WebSocketServer, } from './websocket.decorators';
|
|
2
|
+
export { WebSocketModule } from './websocket.module';
|
|
3
|
+
export type { WebSocketModuleOptions } from './websocket.module';
|
|
4
|
+
export { WsDispatcher, type WsEntrypointMeta } from './ws-dispatcher';
|
|
5
|
+
export { WsServerImpl, BroadcastOperatorImpl } from './ws-server';
|
|
6
|
+
export { InMemoryRoomRegistry, local } from './ws-sync';
|
|
7
|
+
export type { SyncDriver, RoomRegistry } from './ws-sync';
|
|
8
|
+
export { buildWsExecutionContext } from './ws-execution-context';
|
|
9
|
+
export { WsException, toErrorFrame } from './ws-exception';
|
|
10
|
+
export { resolveWsArgs } from './ws-argument-resolver';
|
|
11
|
+
export { WS_SERVER, WS_SYNC_DRIVER, WS_ROOM_REGISTRY, WS_MODULE_OPTIONS, WS_GATEWAY_METADATA, WS_SUBSCRIBE_METADATA, WsParamType, } from './websocket.tokens';
|
|
12
|
+
export type { WsClient, WsServer, WsMessage, WsResponse, BroadcastCommand, BroadcastOperator, WebSocketGatewayOptions, SubscribeMessageMetadata, WsExecutionContext, WsArgumentsHost, OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect, } from './websocket.types';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Decorators
|
|
2
|
+
export { WebSocketGateway, SubscribeMessage, MessageBody, ConnectedSocket, WebSocketServer } from "./websocket.decorators.js";
|
|
3
|
+
// Module
|
|
4
|
+
export { WebSocketModule } from "./websocket.module.js";
|
|
5
|
+
// Dispatcher (injected/called by transports) + the 'websocket' entrypoint meta
|
|
6
|
+
export { WsDispatcher } from "./ws-dispatcher.js";
|
|
7
|
+
// Server handle + rooms + sync
|
|
8
|
+
export { WsServerImpl, BroadcastOperatorImpl } from "./ws-server.js";
|
|
9
|
+
export { InMemoryRoomRegistry, local } from "./ws-sync.js";
|
|
10
|
+
// Execution context + exceptions
|
|
11
|
+
export { buildWsExecutionContext } from "./ws-execution-context.js";
|
|
12
|
+
export { WsException, toErrorFrame } from "./ws-exception.js";
|
|
13
|
+
// Argument resolution (used by transports building custom dispatch)
|
|
14
|
+
export { resolveWsArgs } from "./ws-argument-resolver.js";
|
|
15
|
+
// Tokens
|
|
16
|
+
export { WS_SERVER, WS_SYNC_DRIVER, WS_ROOM_REGISTRY, WS_MODULE_OPTIONS, WS_GATEWAY_METADATA, WS_SUBSCRIBE_METADATA, WsParamType } from "./websocket.tokens.js";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { WebSocketGatewayOptions } from './websocket.types';
|
|
2
|
+
/**
|
|
3
|
+
* Marks a class as a WebSocket gateway. Mirrors `@Controller` for the socket
|
|
4
|
+
* transport: registers the class as an injectable singleton and stores its
|
|
5
|
+
* routing options (`path`, Cloudflare `binding`).
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* @WebSocketGateway({ path: '/rooms/:id/ws', binding: 'CHAT_ROOM' })
|
|
10
|
+
* class ChatGateway {}
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare function WebSocketGateway(options?: WebSocketGatewayOptions): ClassDecorator;
|
|
14
|
+
/**
|
|
15
|
+
* Binds a gateway method to an inbound event. Stackable — one class-level list,
|
|
16
|
+
* exactly like `@OnEvent` (`event-emitter.decorators.ts`).
|
|
17
|
+
*/
|
|
18
|
+
export declare function SubscribeMessage(event: string): MethodDecorator;
|
|
19
|
+
/** Injects the inbound message payload (the envelope's `data`) into a handler parameter. */
|
|
20
|
+
export declare const MessageBody: () => ParameterDecorator;
|
|
21
|
+
/** Injects the connected socket (`WsClient`) into a handler parameter. */
|
|
22
|
+
export declare const ConnectedSocket: () => ParameterDecorator;
|
|
23
|
+
/**
|
|
24
|
+
* Injects the gateway server handle (`WsServer`). Constructor injection only —
|
|
25
|
+
* the container has no property-injection pass, so this is sugar for
|
|
26
|
+
* `@Inject(WS_SERVER)` on a constructor parameter.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* constructor(@WebSocketServer() private server: WsServer) {}
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function WebSocketServer(): ParameterDecorator;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Scope } from "../constants.js";
|
|
2
|
+
import { Inject } from "../container/decorators.js";
|
|
3
|
+
import { MetadataRegistry } from "../registry/metadata.registry.js";
|
|
4
|
+
import { WS_GATEWAY_METADATA, WS_SERVER, WS_SUBSCRIBE_METADATA, WsParamType } from "./websocket.tokens.js";
|
|
5
|
+
/**
|
|
6
|
+
* Marks a class as a WebSocket gateway. Mirrors `@Controller` for the socket
|
|
7
|
+
* transport: registers the class as an injectable singleton and stores its
|
|
8
|
+
* routing options (`path`, Cloudflare `binding`).
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* @WebSocketGateway({ path: '/rooms/:id/ws', binding: 'CHAT_ROOM' })
|
|
13
|
+
* class ChatGateway {}
|
|
14
|
+
* ```
|
|
15
|
+
*/ export function WebSocketGateway(options = {}) {
|
|
16
|
+
return (target)=>{
|
|
17
|
+
const ctor = target;
|
|
18
|
+
MetadataRegistry.setCustomClassMeta(ctor, WS_GATEWAY_METADATA, options);
|
|
19
|
+
MetadataRegistry.markInjectable(ctor);
|
|
20
|
+
MetadataRegistry.setScope(ctor, Scope.SINGLETON);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Binds a gateway method to an inbound event. Stackable — one class-level list,
|
|
25
|
+
* exactly like `@OnEvent` (`event-emitter.decorators.ts`).
|
|
26
|
+
*/ export function SubscribeMessage(event) {
|
|
27
|
+
return (target, propertyKey)=>{
|
|
28
|
+
MetadataRegistry.appendCustomClassMeta(target.constructor, WS_SUBSCRIBE_METADATA, {
|
|
29
|
+
event,
|
|
30
|
+
methodName: String(propertyKey)
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function wsParamDecorator(type) {
|
|
35
|
+
return ()=>(target, propertyKey, parameterIndex)=>{
|
|
36
|
+
if (propertyKey === undefined) {
|
|
37
|
+
throw new Error('WebSocket parameter decorators can only be used on method parameters');
|
|
38
|
+
}
|
|
39
|
+
MetadataRegistry.addParameter(target.constructor, propertyKey, {
|
|
40
|
+
index: parameterIndex,
|
|
41
|
+
type
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Injects the inbound message payload (the envelope's `data`) into a handler parameter. */ export const MessageBody = wsParamDecorator(WsParamType.BODY);
|
|
46
|
+
/** Injects the connected socket (`WsClient`) into a handler parameter. */ export const ConnectedSocket = wsParamDecorator(WsParamType.SOCKET);
|
|
47
|
+
/**
|
|
48
|
+
* Injects the gateway server handle (`WsServer`). Constructor injection only —
|
|
49
|
+
* the container has no property-injection pass, so this is sugar for
|
|
50
|
+
* `@Inject(WS_SERVER)` on a constructor parameter.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* constructor(@WebSocketServer() private server: WsServer) {}
|
|
55
|
+
* ```
|
|
56
|
+
*/ export function WebSocketServer() {
|
|
57
|
+
return Inject(WS_SERVER);
|
|
58
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type RoomRegistry, type SyncDriver } from './ws-sync';
|
|
2
|
+
export interface WebSocketModuleOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Cross-instance sync driver. Defaults to `local()` (single instance). Use
|
|
5
|
+
* `durableObject()` from `@velajs/cloudflare` or `redis()` from
|
|
6
|
+
* `@velajs/vela/websocket-node` for horizontal scale.
|
|
7
|
+
*/
|
|
8
|
+
sync?: SyncDriver;
|
|
9
|
+
/**
|
|
10
|
+
* Local room registry the transport reads/writes. Defaults to the in-memory
|
|
11
|
+
* implementation (node/bun/deno); the Cloudflare transport supplies its own.
|
|
12
|
+
*/
|
|
13
|
+
registry?: RoomRegistry;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Registers the WebSocket gateway machinery: the message dispatcher (discovers
|
|
17
|
+
* `@WebSocketGateway` classes via DiscoveryService at bootstrap), the room
|
|
18
|
+
* registry, the sync driver, and the `@WebSocketServer()`-injected server
|
|
19
|
+
* handle.
|
|
20
|
+
*
|
|
21
|
+
* Construction lives in chained provider factories — registry → driver
|
|
22
|
+
* (bound to THAT registry) → server — so the single shared registry instance
|
|
23
|
+
* is preserved and everything materializes at bootstrap's eager
|
|
24
|
+
* instantiation, before any lifecycle hook or message dispatch.
|
|
25
|
+
*
|
|
26
|
+
* The instance key derives from the sync driver kind: two `forRoot()` calls
|
|
27
|
+
* with the same driver kind dedup (HMR-idempotent); pass an explicit `key`
|
|
28
|
+
* to run multiple same-kind instances side by side.
|
|
29
|
+
*/
|
|
30
|
+
declare const ConfigurableModuleClass: import("..").ConfigurableModuleClassType<WebSocketModuleOptions, "forRoot", "create", {
|
|
31
|
+
isGlobal?: boolean;
|
|
32
|
+
}>;
|
|
33
|
+
export declare class WebSocketModule extends ConfigurableModuleClass {
|
|
34
|
+
}
|
|
35
|
+
export {};
|