@yaghmori/storage-service 0.1.1

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 ADDED
@@ -0,0 +1,20 @@
1
+ # @yaghmori/storage-service
2
+
3
+ Configurable client for **storage-service** (HTTP · TCP · Kafka · auth).
4
+
5
+ ## Docs
6
+
7
+ - **[docs/PROTOCOL_GUIDE.md](./docs/PROTOCOL_GUIDE.md)** — full HTTP / TCP / Kafka for Node and .NET
8
+ - **[docs/USAGE.md](./docs/USAGE.md)** — practical usage
9
+ - **[docs/NUGET_TRUSTED_PUBLISHING.md](./docs/NUGET_TRUSTED_PUBLISHING.md)** — publish `Yaghmori.StorageService` without API keys
10
+
11
+ SDKs: npm · `sdk/dotnet`
12
+
13
+ ```ts
14
+ import { createStorageHttpClient } from '@yaghmori/storage-service';
15
+
16
+ const storage = createStorageHttpClient({
17
+ baseUrl: process.env.STORAGE_SERVICE_URL,
18
+ auth: { apiKey: process.env.STORAGE_SERVICE_API_KEY! },
19
+ });
20
+ ```
package/contracts.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "storage-service",
3
+ "npmPackage": "@yaghmori/storage-service",
4
+ "nugetPackage": "Yaghmori.StorageService",
5
+ "dockerImage": "ghcr.io/yaghmori/storage-service",
6
+ "description": "Client SDK for the standalone storage-service (TCP, Kafka, HTTP)",
7
+ "ports": {
8
+ "tcp": 4002,
9
+ "http": 4000
10
+ },
11
+ "injectionToken": "STORAGE_SERVICE",
12
+ "env": {
13
+ "httpBaseUrl": "STORAGE_SERVICE_URL",
14
+ "host": "STORAGE_SERVICE_HOST",
15
+ "tcpPort": "STORAGE_SERVICE_TCP_PORT",
16
+ "httpPort": "STORAGE_SERVICE_HTTP_PORT"
17
+ },
18
+ "patterns": {
19
+ "GET_FILE_INFO": "storage.get_file_info",
20
+ "DELETE_FILE": "storage.delete_file",
21
+ "BATCH_OPERATIONS": "storage.batch_operations",
22
+ "GET_SIGNED_URL": "storage.get_signed_url",
23
+ "UPLOAD_FILE": "uploadFile",
24
+ "GET_ASSET_URL": "getAssetUrl",
25
+ "DELETE_ASSET": "deleteAsset",
26
+ "HEALTH_CHECK": "health.check"
27
+ },
28
+ "httpPaths": {
29
+ "UPLOAD": "/upload",
30
+ "GET_FILE": "/files/{id}",
31
+ "DELETE_FILE": "/files/{id}",
32
+ "DOWNLOAD": "/files/{id}/download",
33
+ "SIGNED_URL": "/files/{id}/signed-url",
34
+ "HEALTH": "/health"
35
+ },
36
+ "topics": {
37
+ "FILE_UPLOADED": "file.uploaded",
38
+ "FILE_DELETED": "file.deleted",
39
+ "FILE_PROCESSED": "file.processed"
40
+ },
41
+ "eventTypes": {
42
+ "UPLOADED": "evt.storage.file.uploaded.v1",
43
+ "DELETED": "evt.storage.file.deleted.v1",
44
+ "PROCESSED": "evt.storage.file.processed.v1"
45
+ }
46
+ }
package/dist/auth.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ export type ClientAuth = {
2
+ type: 'apiKey';
3
+ apiKey: string;
4
+ } | {
5
+ type: 'bearer';
6
+ token: string;
7
+ } | {
8
+ apiKey: string;
9
+ bearerToken?: never;
10
+ } | {
11
+ bearerToken: string;
12
+ apiKey?: never;
13
+ } | {
14
+ apiKey?: string;
15
+ bearerToken?: string;
16
+ };
17
+ export declare const AUTH_HEADERS: {
18
+ readonly apiKey: "x-api-key";
19
+ readonly authorization: "authorization";
20
+ };
21
+ export declare function buildAuthHeaders(auth?: ClientAuth | string, envApiKeyVar?: string): Record<string, string>;
22
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,KAAK,CAAA;CAAE,GACvC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,CAAA;CAAE,GACvC;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9C,eAAO,MAAM,YAAY;;;CAGf,CAAC;AAEX,wBAAgB,gBAAgB,CAC9B,IAAI,CAAC,EAAE,UAAU,GAAG,MAAM,EAC1B,YAAY,SAA4B,GACvC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAyBxB"}
package/dist/auth.js ADDED
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AUTH_HEADERS = void 0;
4
+ exports.buildAuthHeaders = buildAuthHeaders;
5
+ exports.AUTH_HEADERS = {
6
+ apiKey: 'x-api-key',
7
+ authorization: 'authorization',
8
+ };
9
+ function buildAuthHeaders(auth, envApiKeyVar = 'STORAGE_SERVICE_API_KEY') {
10
+ if (!auth) {
11
+ const fromEnv = process.env[envApiKeyVar] ||
12
+ process.env.SERVICE_API_KEY ||
13
+ process.env.API_KEY;
14
+ if (fromEnv)
15
+ return { [exports.AUTH_HEADERS.apiKey]: fromEnv };
16
+ const bearer = process.env.STORAGE_SERVICE_BEARER || process.env.SERVICE_BEARER_TOKEN;
17
+ if (bearer)
18
+ return { [exports.AUTH_HEADERS.authorization]: `Bearer ${bearer}` };
19
+ return {};
20
+ }
21
+ if (typeof auth === 'string') {
22
+ return { [exports.AUTH_HEADERS.apiKey]: auth };
23
+ }
24
+ if ('type' in auth) {
25
+ if (auth.type === 'apiKey')
26
+ return { [exports.AUTH_HEADERS.apiKey]: auth.apiKey };
27
+ return { [exports.AUTH_HEADERS.authorization]: `Bearer ${auth.token}` };
28
+ }
29
+ const headers = {};
30
+ if (auth.apiKey)
31
+ headers[exports.AUTH_HEADERS.apiKey] = auth.apiKey;
32
+ if (auth.bearerToken)
33
+ headers[exports.AUTH_HEADERS.authorization] = `Bearer ${auth.bearerToken}`;
34
+ return headers;
35
+ }
36
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":";;;AAYA,4CA4BC;AAjCY,QAAA,YAAY,GAAG;IAC1B,MAAM,EAAE,WAAW;IACnB,aAAa,EAAE,eAAe;CACtB,CAAC;AAEX,SAAgB,gBAAgB,CAC9B,IAA0B,EAC1B,YAAY,GAAG,yBAAyB;IAExC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,OAAO,GACX,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;YACzB,OAAO,CAAC,GAAG,CAAC,eAAe;YAC3B,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;QACtB,IAAI,OAAO;YAAE,OAAO,EAAE,CAAC,oBAAY,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;QACvD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;QACtF,IAAI,MAAM;YAAE,OAAO,EAAE,CAAC,oBAAY,CAAC,aAAa,CAAC,EAAE,UAAU,MAAM,EAAE,EAAE,CAAC;QACxE,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,OAAO,EAAE,CAAC,oBAAY,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC;IACzC,CAAC;IAED,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;QACnB,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAC,oBAAY,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;QAC1E,OAAO,EAAE,CAAC,oBAAY,CAAC,aAAa,CAAC,EAAE,UAAU,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;IAClE,CAAC;IAED,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,IAAI,IAAI,CAAC,MAAM;QAAE,OAAO,CAAC,oBAAY,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;IAC5D,IAAI,IAAI,CAAC,WAAW;QAAE,OAAO,CAAC,oBAAY,CAAC,aAAa,CAAC,GAAG,UAAU,IAAI,CAAC,WAAW,EAAE,CAAC;IACzF,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,14 @@
1
+ export type ServiceEndpoint = {
2
+ baseUrl?: string;
3
+ host?: string;
4
+ port?: number;
5
+ protocol?: 'http' | 'https';
6
+ };
7
+ export declare function resolveHttpBaseUrl(options?: ServiceEndpoint): string;
8
+ export declare function resolveTcpEndpoint(options?: ServiceEndpoint): {
9
+ host: string;
10
+ port: number;
11
+ };
12
+ export declare function joinUrl(base: string, path: string): string;
13
+ export declare function fillPath(template: string, params: Record<string, string | number>): string;
14
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAC7B,CAAC;AAOF,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,eAAoB,GAAG,MAAM,CAaxE;AAED,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,eAAoB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAMhG;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAI1D;AAED,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,CAK1F"}
package/dist/config.js ADDED
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveHttpBaseUrl = resolveHttpBaseUrl;
4
+ exports.resolveTcpEndpoint = resolveTcpEndpoint;
5
+ exports.joinUrl = joinUrl;
6
+ exports.fillPath = fillPath;
7
+ const generated_1 = require("./generated");
8
+ function env(name) {
9
+ const v = process.env[name];
10
+ return v && v.length > 0 ? v : undefined;
11
+ }
12
+ function resolveHttpBaseUrl(options = {}) {
13
+ if (options.baseUrl)
14
+ return options.baseUrl.replace(/\/$/, '');
15
+ const fromEnv = env(generated_1.ENV_KEYS.httpBaseUrl) ?? env('STORAGE_SERVICE_URL');
16
+ if (fromEnv)
17
+ return fromEnv.replace(/\/$/, '');
18
+ const host = options.host ?? env(generated_1.ENV_KEYS.host) ?? env('STORAGE_HOST') ?? '127.0.0.1';
19
+ const port = Number(options.port ?? env(generated_1.ENV_KEYS.httpPort) ?? env('STORAGE_HTTP_PORT') ?? generated_1.PORTS.http);
20
+ const protocol = options.protocol ?? (port === 443 ? 'https' : 'http');
21
+ const defaultPort = protocol === 'https' ? 443 : 80;
22
+ const authority = port === defaultPort ? host : `${host}:${port}`;
23
+ return `${protocol}://${authority}`;
24
+ }
25
+ function resolveTcpEndpoint(options = {}) {
26
+ const host = options.host ?? env(generated_1.ENV_KEYS.host) ?? env('STORAGE_HOST') ?? '127.0.0.1';
27
+ const port = Number(options.port ?? env(generated_1.ENV_KEYS.tcpPort) ?? env('STORAGE_TCP_PORT') ?? generated_1.PORTS.tcp);
28
+ return { host, port };
29
+ }
30
+ function joinUrl(base, path) {
31
+ const b = base.replace(/\/$/, '');
32
+ const p = path.startsWith('/') ? path : `/${path}`;
33
+ return `${b}${p}`;
34
+ }
35
+ function fillPath(template, params) {
36
+ return template.replace(/\{(\w+)\}/g, (_, key) => {
37
+ if (params[key] === undefined)
38
+ throw new Error(`Missing path param: ${key}`);
39
+ return encodeURIComponent(String(params[key]));
40
+ });
41
+ }
42
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ export declare const PORTS: {
2
+ readonly tcp: 4002;
3
+ readonly http: 4000;
4
+ };
5
+ export declare const INJECTION_TOKEN: "STORAGE_SERVICE";
6
+ export declare const DOCKER_IMAGE: "ghcr.io/yaghmori/storage-service";
7
+ export declare const SERVICE_NAME: "storage-service";
8
+ export declare const ENV_KEYS: {
9
+ readonly httpBaseUrl: "STORAGE_SERVICE_URL";
10
+ readonly host: "STORAGE_SERVICE_HOST";
11
+ readonly tcpPort: "STORAGE_SERVICE_TCP_PORT";
12
+ readonly httpPort: "STORAGE_SERVICE_HTTP_PORT";
13
+ };
14
+ export declare const PATTERNS: {
15
+ readonly GET_FILE_INFO: "storage.get_file_info";
16
+ readonly DELETE_FILE: "storage.delete_file";
17
+ readonly BATCH_OPERATIONS: "storage.batch_operations";
18
+ readonly GET_SIGNED_URL: "storage.get_signed_url";
19
+ readonly UPLOAD_FILE: "uploadFile";
20
+ readonly GET_ASSET_URL: "getAssetUrl";
21
+ readonly DELETE_ASSET: "deleteAsset";
22
+ readonly HEALTH_CHECK: "health.check";
23
+ };
24
+ export declare const HTTP_PATHS: {
25
+ readonly UPLOAD: "/upload";
26
+ readonly GET_FILE: "/files/{id}";
27
+ readonly DELETE_FILE: "/files/{id}";
28
+ readonly DOWNLOAD: "/files/{id}/download";
29
+ readonly SIGNED_URL: "/files/{id}/signed-url";
30
+ readonly HEALTH: "/health";
31
+ };
32
+ export declare const TOPICS: {
33
+ readonly FILE_UPLOADED: "file.uploaded";
34
+ readonly FILE_DELETED: "file.deleted";
35
+ readonly FILE_PROCESSED: "file.processed";
36
+ };
37
+ export declare const EVENT_TYPES: {
38
+ readonly UPLOADED: "evt.storage.file.uploaded.v1";
39
+ readonly DELETED: "evt.storage.file.deleted.v1";
40
+ readonly PROCESSED: "evt.storage.file.processed.v1";
41
+ };
42
+ export type StoragePattern = (typeof PATTERNS)[keyof typeof PATTERNS];
43
+ export type StorageTopic = (typeof TOPICS)[keyof typeof TOPICS];
44
+ export type StorageEventType = (typeof EVENT_TYPES)[keyof typeof EVENT_TYPES];
45
+ export type StorageHttpPath = (typeof HTTP_PATHS)[keyof typeof HTTP_PATHS];
46
+ export declare const StorageService: {
47
+ readonly name: "storage-service";
48
+ readonly token: "STORAGE_SERVICE";
49
+ readonly image: "ghcr.io/yaghmori/storage-service";
50
+ readonly ports: {
51
+ readonly tcp: 4002;
52
+ readonly http: 4000;
53
+ };
54
+ readonly env: {
55
+ readonly httpBaseUrl: "STORAGE_SERVICE_URL";
56
+ readonly host: "STORAGE_SERVICE_HOST";
57
+ readonly tcpPort: "STORAGE_SERVICE_TCP_PORT";
58
+ readonly httpPort: "STORAGE_SERVICE_HTTP_PORT";
59
+ };
60
+ readonly patterns: {
61
+ readonly GET_FILE_INFO: "storage.get_file_info";
62
+ readonly DELETE_FILE: "storage.delete_file";
63
+ readonly BATCH_OPERATIONS: "storage.batch_operations";
64
+ readonly GET_SIGNED_URL: "storage.get_signed_url";
65
+ readonly UPLOAD_FILE: "uploadFile";
66
+ readonly GET_ASSET_URL: "getAssetUrl";
67
+ readonly DELETE_ASSET: "deleteAsset";
68
+ readonly HEALTH_CHECK: "health.check";
69
+ };
70
+ readonly httpPaths: {
71
+ readonly UPLOAD: "/upload";
72
+ readonly GET_FILE: "/files/{id}";
73
+ readonly DELETE_FILE: "/files/{id}";
74
+ readonly DOWNLOAD: "/files/{id}/download";
75
+ readonly SIGNED_URL: "/files/{id}/signed-url";
76
+ readonly HEALTH: "/health";
77
+ };
78
+ readonly topics: {
79
+ readonly FILE_UPLOADED: "file.uploaded";
80
+ readonly FILE_DELETED: "file.deleted";
81
+ readonly FILE_PROCESSED: "file.processed";
82
+ };
83
+ readonly eventTypes: {
84
+ readonly UPLOADED: "evt.storage.file.uploaded.v1";
85
+ readonly DELETED: "evt.storage.file.deleted.v1";
86
+ readonly PROCESSED: "evt.storage.file.processed.v1";
87
+ };
88
+ };
89
+ //# sourceMappingURL=generated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generated.d.ts","sourceRoot":"","sources":["../src/generated.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,KAAK;;;CAGR,CAAC;AAEX,eAAO,MAAM,eAAe,EAAG,iBAA0B,CAAC;AAC1D,eAAO,MAAM,YAAY,EAAG,kCAA2C,CAAC;AACxE,eAAO,MAAM,YAAY,EAAG,iBAA0B,CAAC;AAEvD,eAAO,MAAM,QAAQ;;;;;CAKX,CAAC;AACX,eAAO,MAAM,QAAQ;;;;;;;;;CASX,CAAC;AACX,eAAO,MAAM,UAAU;;;;;;;CAOb,CAAC;AACX,eAAO,MAAM,MAAM;;;;CAIT,CAAC;AACX,eAAO,MAAM,WAAW;;;;CAId,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,OAAO,QAAQ,CAAC,CAAC;AACtE,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,MAAM,CAAC,CAAC,MAAM,OAAO,MAAM,CAAC,CAAC;AAChE,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,WAAW,CAAC,CAAC;AAC9E,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,OAAO,UAAU,CAAC,CAAC;AAE3E,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAUjB,CAAC"}
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StorageService = exports.EVENT_TYPES = exports.TOPICS = exports.HTTP_PATHS = exports.PATTERNS = exports.ENV_KEYS = exports.SERVICE_NAME = exports.DOCKER_IMAGE = exports.INJECTION_TOKEN = exports.PORTS = void 0;
4
+ /* AUTO-GENERATED from contracts.json - do not edit by hand. Run: pnpm run codegen */
5
+ exports.PORTS = {
6
+ tcp: 4002,
7
+ http: 4000,
8
+ };
9
+ exports.INJECTION_TOKEN = "STORAGE_SERVICE";
10
+ exports.DOCKER_IMAGE = "ghcr.io/yaghmori/storage-service";
11
+ exports.SERVICE_NAME = "storage-service";
12
+ exports.ENV_KEYS = {
13
+ httpBaseUrl: "STORAGE_SERVICE_URL",
14
+ host: "STORAGE_SERVICE_HOST",
15
+ tcpPort: "STORAGE_SERVICE_TCP_PORT",
16
+ httpPort: "STORAGE_SERVICE_HTTP_PORT",
17
+ };
18
+ exports.PATTERNS = {
19
+ GET_FILE_INFO: "storage.get_file_info",
20
+ DELETE_FILE: "storage.delete_file",
21
+ BATCH_OPERATIONS: "storage.batch_operations",
22
+ GET_SIGNED_URL: "storage.get_signed_url",
23
+ UPLOAD_FILE: "uploadFile",
24
+ GET_ASSET_URL: "getAssetUrl",
25
+ DELETE_ASSET: "deleteAsset",
26
+ HEALTH_CHECK: "health.check",
27
+ };
28
+ exports.HTTP_PATHS = {
29
+ UPLOAD: "/upload",
30
+ GET_FILE: "/files/{id}",
31
+ DELETE_FILE: "/files/{id}",
32
+ DOWNLOAD: "/files/{id}/download",
33
+ SIGNED_URL: "/files/{id}/signed-url",
34
+ HEALTH: "/health",
35
+ };
36
+ exports.TOPICS = {
37
+ FILE_UPLOADED: "file.uploaded",
38
+ FILE_DELETED: "file.deleted",
39
+ FILE_PROCESSED: "file.processed",
40
+ };
41
+ exports.EVENT_TYPES = {
42
+ UPLOADED: "evt.storage.file.uploaded.v1",
43
+ DELETED: "evt.storage.file.deleted.v1",
44
+ PROCESSED: "evt.storage.file.processed.v1",
45
+ };
46
+ exports.StorageService = {
47
+ name: exports.SERVICE_NAME,
48
+ token: exports.INJECTION_TOKEN,
49
+ image: exports.DOCKER_IMAGE,
50
+ ports: exports.PORTS,
51
+ env: exports.ENV_KEYS,
52
+ patterns: exports.PATTERNS,
53
+ httpPaths: exports.HTTP_PATHS,
54
+ topics: exports.TOPICS,
55
+ eventTypes: exports.EVENT_TYPES,
56
+ };
57
+ //# sourceMappingURL=generated.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generated.js","sourceRoot":"","sources":["../src/generated.ts"],"names":[],"mappings":";;;AAAA,qFAAqF;AACxE,QAAA,KAAK,GAAG;IACnB,GAAG,EAAE,IAAI;IACT,IAAI,EAAE,IAAI;CACF,CAAC;AAEE,QAAA,eAAe,GAAG,iBAA0B,CAAC;AAC7C,QAAA,YAAY,GAAG,kCAA2C,CAAC;AAC3D,QAAA,YAAY,GAAG,iBAA0B,CAAC;AAE1C,QAAA,QAAQ,GAAG;IACtB,WAAW,EAAE,qBAAqB;IAClC,IAAI,EAAE,sBAAsB;IAC5B,OAAO,EAAE,0BAA0B;IACnC,QAAQ,EAAE,2BAA2B;CAC7B,CAAC;AACE,QAAA,QAAQ,GAAG;IACtB,aAAa,EAAE,uBAAuB;IACtC,WAAW,EAAE,qBAAqB;IAClC,gBAAgB,EAAE,0BAA0B;IAC5C,cAAc,EAAE,wBAAwB;IACxC,WAAW,EAAE,YAAY;IACzB,aAAa,EAAE,aAAa;IAC5B,YAAY,EAAE,aAAa;IAC3B,YAAY,EAAE,cAAc;CACpB,CAAC;AACE,QAAA,UAAU,GAAG;IACxB,MAAM,EAAE,SAAS;IACjB,QAAQ,EAAE,aAAa;IACvB,WAAW,EAAE,aAAa;IAC1B,QAAQ,EAAE,sBAAsB;IAChC,UAAU,EAAE,wBAAwB;IACpC,MAAM,EAAE,SAAS;CACT,CAAC;AACE,QAAA,MAAM,GAAG;IACpB,aAAa,EAAE,eAAe;IAC9B,YAAY,EAAE,cAAc;IAC5B,cAAc,EAAE,gBAAgB;CACxB,CAAC;AACE,QAAA,WAAW,GAAG;IACzB,QAAQ,EAAE,8BAA8B;IACxC,OAAO,EAAE,6BAA6B;IACtC,SAAS,EAAE,+BAA+B;CAClC,CAAC;AAOE,QAAA,cAAc,GAAG;IAC5B,IAAI,EAAE,oBAAY;IAClB,KAAK,EAAE,uBAAe;IACtB,KAAK,EAAE,oBAAY;IACnB,KAAK,EAAE,aAAK;IACZ,GAAG,EAAE,gBAAQ;IACb,QAAQ,EAAE,gBAAQ;IAClB,SAAS,EAAE,kBAAU;IACrB,MAAM,EAAE,cAAM;IACd,UAAU,EAAE,mBAAW;CACf,CAAC"}
package/dist/http.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { type ServiceEndpoint } from './config';
2
+ import { type ClientAuth } from './auth';
3
+ export type StorageHttpClientOptions = ServiceEndpoint & {
4
+ auth?: ClientAuth | string;
5
+ apiKey?: string;
6
+ headers?: Record<string, string>;
7
+ fetch?: typeof fetch;
8
+ };
9
+ export declare class StorageHttpClient {
10
+ readonly baseUrl: string;
11
+ private readonly headers;
12
+ private readonly fetchImpl;
13
+ constructor(options?: StorageHttpClientOptions);
14
+ getFile(id: string): Promise<unknown>;
15
+ deleteFile(id: string): Promise<unknown>;
16
+ getSignedUrl(id: string): Promise<unknown>;
17
+ health(): Promise<unknown>;
18
+ upload(formData: FormData): Promise<unknown>;
19
+ private request;
20
+ }
21
+ export declare function createStorageHttpClient(options?: StorageHttpClientOptions): StorageHttpClient;
22
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,QAAQ,CAAC;AAG3D,MAAM,MAAM,wBAAwB,GAAG,eAAe,GAAG;IACvD,IAAI,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB,CAAC;AAEF,qBAAa,iBAAiB;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;IACjD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;gBAE7B,OAAO,GAAE,wBAA6B;IAW5C,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIrC,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIxC,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI1C,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAI1B,MAAM,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;YAepC,OAAO;CAYtB;AAED,wBAAgB,uBAAuB,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,iBAAiB,CAE7F"}
package/dist/http.js ADDED
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StorageHttpClient = void 0;
4
+ exports.createStorageHttpClient = createStorageHttpClient;
5
+ const config_1 = require("./config");
6
+ const auth_1 = require("./auth");
7
+ const generated_1 = require("./generated");
8
+ class StorageHttpClient {
9
+ constructor(options = {}) {
10
+ this.baseUrl = (0, config_1.resolveHttpBaseUrl)(options);
11
+ this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
12
+ const auth = options.auth ?? (options.apiKey ? { apiKey: options.apiKey } : undefined);
13
+ this.headers = {
14
+ accept: 'application/json',
15
+ ...(0, auth_1.buildAuthHeaders)(auth, 'STORAGE_SERVICE_API_KEY'),
16
+ ...options.headers,
17
+ };
18
+ }
19
+ async getFile(id) {
20
+ return this.request('GET', (0, config_1.fillPath)(generated_1.HTTP_PATHS.GET_FILE, { id }));
21
+ }
22
+ async deleteFile(id) {
23
+ return this.request('DELETE', (0, config_1.fillPath)(generated_1.HTTP_PATHS.DELETE_FILE, { id }));
24
+ }
25
+ async getSignedUrl(id) {
26
+ return this.request('GET', (0, config_1.fillPath)(generated_1.HTTP_PATHS.SIGNED_URL, { id }));
27
+ }
28
+ async health() {
29
+ return this.request('GET', generated_1.HTTP_PATHS.HEALTH);
30
+ }
31
+ async upload(formData) {
32
+ const headers = { ...this.headers };
33
+ delete headers['content-type'];
34
+ const res = await this.fetchImpl((0, config_1.joinUrl)(this.baseUrl, generated_1.HTTP_PATHS.UPLOAD), {
35
+ method: 'POST',
36
+ headers,
37
+ body: formData,
38
+ });
39
+ if (!res.ok) {
40
+ const text = await res.text().catch(() => '');
41
+ throw new Error(`storage-service HTTP ${res.status} POST ${generated_1.HTTP_PATHS.UPLOAD}: ${text}`);
42
+ }
43
+ return res.json();
44
+ }
45
+ async request(method, path) {
46
+ const res = await this.fetchImpl((0, config_1.joinUrl)(this.baseUrl, path), {
47
+ method,
48
+ headers: this.headers,
49
+ });
50
+ if (!res.ok) {
51
+ const text = await res.text().catch(() => '');
52
+ throw new Error(`storage-service HTTP ${res.status} ${method} ${path}: ${text}`);
53
+ }
54
+ if (res.status === 204)
55
+ return undefined;
56
+ return (await res.json());
57
+ }
58
+ }
59
+ exports.StorageHttpClient = StorageHttpClient;
60
+ function createStorageHttpClient(options) {
61
+ return new StorageHttpClient(options);
62
+ }
63
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export { StorageService, PORTS, PATTERNS, HTTP_PATHS, TOPICS, EVENT_TYPES, ENV_KEYS, INJECTION_TOKEN, DOCKER_IMAGE, SERVICE_NAME, } from './generated';
2
+ export type { StoragePattern, StorageTopic, StorageEventType, StorageHttpPath, } from './generated';
3
+ export * from './schemas';
4
+ export { resolveHttpBaseUrl, resolveTcpEndpoint, joinUrl, fillPath, } from './config';
5
+ export type { ServiceEndpoint } from './config';
6
+ export { buildAuthHeaders, AUTH_HEADERS } from './auth';
7
+ export type { ClientAuth } from './auth';
8
+ export { StorageHttpClient, createStorageHttpClient } from './http';
9
+ export type { StorageHttpClientOptions } from './http';
10
+ export { StorageKafka, resolveKafkaConnection } from './kafka';
11
+ export type { KafkaConnectionEnv } from './kafka';
12
+ export { storageTcpClient } from './nest';
13
+ export type { StorageTcpClientOptions } from './nest';
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,KAAK,EACL,QAAQ,EACR,UAAU,EACV,MAAM,EACN,WAAW,EACX,QAAQ,EACR,eAAe,EACf,YAAY,EACZ,YAAY,GACb,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,eAAe,GAChB,MAAM,aAAa,CAAC;AAErB,cAAc,WAAW,CAAC;AAC1B,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,OAAO,EACP,QAAQ,GACT,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhD,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACxD,YAAY,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,QAAQ,CAAC;AACpE,YAAY,EAAE,wBAAwB,EAAE,MAAM,QAAQ,CAAC;AAEvD,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAC/D,YAAY,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD,OAAO,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAC1C,YAAY,EAAE,uBAAuB,EAAE,MAAM,QAAQ,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.storageTcpClient = exports.resolveKafkaConnection = exports.StorageKafka = exports.createStorageHttpClient = exports.StorageHttpClient = exports.AUTH_HEADERS = exports.buildAuthHeaders = exports.fillPath = exports.joinUrl = exports.resolveTcpEndpoint = exports.resolveHttpBaseUrl = exports.SERVICE_NAME = exports.DOCKER_IMAGE = exports.INJECTION_TOKEN = exports.ENV_KEYS = exports.EVENT_TYPES = exports.TOPICS = exports.HTTP_PATHS = exports.PATTERNS = exports.PORTS = exports.StorageService = void 0;
18
+ var generated_1 = require("./generated");
19
+ Object.defineProperty(exports, "StorageService", { enumerable: true, get: function () { return generated_1.StorageService; } });
20
+ Object.defineProperty(exports, "PORTS", { enumerable: true, get: function () { return generated_1.PORTS; } });
21
+ Object.defineProperty(exports, "PATTERNS", { enumerable: true, get: function () { return generated_1.PATTERNS; } });
22
+ Object.defineProperty(exports, "HTTP_PATHS", { enumerable: true, get: function () { return generated_1.HTTP_PATHS; } });
23
+ Object.defineProperty(exports, "TOPICS", { enumerable: true, get: function () { return generated_1.TOPICS; } });
24
+ Object.defineProperty(exports, "EVENT_TYPES", { enumerable: true, get: function () { return generated_1.EVENT_TYPES; } });
25
+ Object.defineProperty(exports, "ENV_KEYS", { enumerable: true, get: function () { return generated_1.ENV_KEYS; } });
26
+ Object.defineProperty(exports, "INJECTION_TOKEN", { enumerable: true, get: function () { return generated_1.INJECTION_TOKEN; } });
27
+ Object.defineProperty(exports, "DOCKER_IMAGE", { enumerable: true, get: function () { return generated_1.DOCKER_IMAGE; } });
28
+ Object.defineProperty(exports, "SERVICE_NAME", { enumerable: true, get: function () { return generated_1.SERVICE_NAME; } });
29
+ __exportStar(require("./schemas"), exports);
30
+ var config_1 = require("./config");
31
+ Object.defineProperty(exports, "resolveHttpBaseUrl", { enumerable: true, get: function () { return config_1.resolveHttpBaseUrl; } });
32
+ Object.defineProperty(exports, "resolveTcpEndpoint", { enumerable: true, get: function () { return config_1.resolveTcpEndpoint; } });
33
+ Object.defineProperty(exports, "joinUrl", { enumerable: true, get: function () { return config_1.joinUrl; } });
34
+ Object.defineProperty(exports, "fillPath", { enumerable: true, get: function () { return config_1.fillPath; } });
35
+ var auth_1 = require("./auth");
36
+ Object.defineProperty(exports, "buildAuthHeaders", { enumerable: true, get: function () { return auth_1.buildAuthHeaders; } });
37
+ Object.defineProperty(exports, "AUTH_HEADERS", { enumerable: true, get: function () { return auth_1.AUTH_HEADERS; } });
38
+ var http_1 = require("./http");
39
+ Object.defineProperty(exports, "StorageHttpClient", { enumerable: true, get: function () { return http_1.StorageHttpClient; } });
40
+ Object.defineProperty(exports, "createStorageHttpClient", { enumerable: true, get: function () { return http_1.createStorageHttpClient; } });
41
+ var kafka_1 = require("./kafka");
42
+ Object.defineProperty(exports, "StorageKafka", { enumerable: true, get: function () { return kafka_1.StorageKafka; } });
43
+ Object.defineProperty(exports, "resolveKafkaConnection", { enumerable: true, get: function () { return kafka_1.resolveKafkaConnection; } });
44
+ var nest_1 = require("./nest");
45
+ Object.defineProperty(exports, "storageTcpClient", { enumerable: true, get: function () { return nest_1.storageTcpClient; } });
46
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,yCAWqB;AAVnB,2GAAA,cAAc,OAAA;AACd,kGAAA,KAAK,OAAA;AACL,qGAAA,QAAQ,OAAA;AACR,uGAAA,UAAU,OAAA;AACV,mGAAA,MAAM,OAAA;AACN,wGAAA,WAAW,OAAA;AACX,qGAAA,QAAQ,OAAA;AACR,4GAAA,eAAe,OAAA;AACf,yGAAA,YAAY,OAAA;AACZ,yGAAA,YAAY,OAAA;AASd,4CAA0B;AAC1B,mCAKkB;AAJhB,4GAAA,kBAAkB,OAAA;AAClB,4GAAA,kBAAkB,OAAA;AAClB,iGAAA,OAAO,OAAA;AACP,kGAAA,QAAQ,OAAA;AAIV,+BAAwD;AAA/C,wGAAA,gBAAgB,OAAA;AAAE,oGAAA,YAAY,OAAA;AAGvC,+BAAoE;AAA3D,yGAAA,iBAAiB,OAAA;AAAE,+GAAA,uBAAuB,OAAA;AAGnD,iCAA+D;AAAtD,qGAAA,YAAY,OAAA;AAAE,+GAAA,sBAAsB,OAAA;AAG7C,+BAA0C;AAAjC,wGAAA,gBAAgB,OAAA"}
@@ -0,0 +1,25 @@
1
+ export type KafkaConnectionEnv = {
2
+ brokers: string[];
3
+ clientId: string;
4
+ ssl?: boolean;
5
+ sasl?: {
6
+ mechanism: 'plain' | 'scram-sha-256' | 'scram-sha-512';
7
+ username: string;
8
+ password: string;
9
+ };
10
+ };
11
+ export declare function resolveKafkaConnection(env?: NodeJS.ProcessEnv): KafkaConnectionEnv;
12
+ export declare const StorageKafka: {
13
+ readonly topics: {
14
+ readonly FILE_UPLOADED: "file.uploaded";
15
+ readonly FILE_DELETED: "file.deleted";
16
+ readonly FILE_PROCESSED: "file.processed";
17
+ };
18
+ readonly eventTypes: {
19
+ readonly UPLOADED: "evt.storage.file.uploaded.v1";
20
+ readonly DELETED: "evt.storage.file.deleted.v1";
21
+ readonly PROCESSED: "evt.storage.file.processed.v1";
22
+ };
23
+ readonly connection: typeof resolveKafkaConnection;
24
+ };
25
+ //# sourceMappingURL=kafka.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kafka.d.ts","sourceRoot":"","sources":["../src/kafka.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,IAAI,CAAC,EAAE;QACL,SAAS,EAAE,OAAO,GAAG,eAAe,GAAG,eAAe,CAAC;QACvD,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;CACH,CAAC;AAEF,wBAAgB,sBAAsB,CACpC,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,kBAAkB,CAqBpB;AAED,eAAO,MAAM,YAAY;;;;;;;;;;;;CAIf,CAAC"}
package/dist/kafka.js ADDED
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StorageKafka = void 0;
4
+ exports.resolveKafkaConnection = resolveKafkaConnection;
5
+ const generated_1 = require("./generated");
6
+ function resolveKafkaConnection(env = process.env) {
7
+ const brokers = (env.KAFKA_BROKERS || env.KAFKA_BOOTSTRAP_SERVERS || 'localhost:9092')
8
+ .split(',')
9
+ .map((s) => s.trim())
10
+ .filter(Boolean);
11
+ const clientId = env.KAFKA_CLIENT_ID || 'storage-service-client';
12
+ const ssl = env.KAFKA_SSL === 'true' || env.KAFKA_SSL === '1';
13
+ const username = env.KAFKA_SASL_USERNAME || env.KAFKA_USERNAME;
14
+ const password = env.KAFKA_SASL_PASSWORD || env.KAFKA_PASSWORD;
15
+ const mechanism = (env.KAFKA_SASL_MECHANISM || 'plain').toLowerCase();
16
+ return {
17
+ brokers,
18
+ clientId,
19
+ ...(ssl ? { ssl: true } : {}),
20
+ ...(username && password ? { sasl: { mechanism, username, password } } : {}),
21
+ };
22
+ }
23
+ exports.StorageKafka = {
24
+ topics: generated_1.TOPICS,
25
+ eventTypes: generated_1.EVENT_TYPES,
26
+ connection: resolveKafkaConnection,
27
+ };
28
+ //# sourceMappingURL=kafka.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kafka.js","sourceRoot":"","sources":["../src/kafka.ts"],"names":[],"mappings":";;;AAaA,wDAuBC;AApCD,2CAAkD;AAalD,SAAgB,sBAAsB,CACpC,MAAyB,OAAO,CAAC,GAAG;IAEpC,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,aAAa,IAAI,GAAG,CAAC,uBAAuB,IAAI,gBAAgB,CAAC;SACnF,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC,CAAC;IAEnB,MAAM,QAAQ,GAAG,GAAG,CAAC,eAAe,IAAI,wBAAwB,CAAC;IACjE,MAAM,GAAG,GAAG,GAAG,CAAC,SAAS,KAAK,MAAM,IAAI,GAAG,CAAC,SAAS,KAAK,GAAG,CAAC;IAC9D,MAAM,QAAQ,GAAG,GAAG,CAAC,mBAAmB,IAAI,GAAG,CAAC,cAAc,CAAC;IAC/D,MAAM,QAAQ,GAAG,GAAG,CAAC,mBAAmB,IAAI,GAAG,CAAC,cAAc,CAAC;IAC/D,MAAM,SAAS,GAAG,CAAC,GAAG,CAAC,oBAAoB,IAAI,OAAO,CAAC,CAAC,WAAW,EAGhD,CAAC;IAEpB,OAAO;QACL,OAAO;QACP,QAAQ;QACR,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7B,GAAG,CAAC,QAAQ,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC7E,CAAC;AACJ,CAAC;AAEY,QAAA,YAAY,GAAG;IAC1B,MAAM,EAAE,kBAAM;IACd,UAAU,EAAE,uBAAW;IACvB,UAAU,EAAE,sBAAsB;CAC1B,CAAC"}
package/dist/nest.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import type { ClientProviderOptions } from '@nestjs/microservices';
2
+ import { type ServiceEndpoint } from './config';
3
+ export type StorageTcpClientOptions = ServiceEndpoint & {
4
+ name?: string | symbol;
5
+ };
6
+ export declare function storageTcpClient(options?: StorageTcpClientOptions): ClientProviderOptions;
7
+ //# sourceMappingURL=nest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nest.d.ts","sourceRoot":"","sources":["../src/nest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAEnE,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC;AAGpE,MAAM,MAAM,uBAAuB,GAAG,eAAe,GAAG;IACtD,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,uBAA4B,GAAG,qBAAqB,CAO7F"}
package/dist/nest.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.storageTcpClient = storageTcpClient;
4
+ const microservices_1 = require("@nestjs/microservices");
5
+ const config_1 = require("./config");
6
+ const generated_1 = require("./generated");
7
+ function storageTcpClient(options = {}) {
8
+ const { host, port } = (0, config_1.resolveTcpEndpoint)(options);
9
+ return {
10
+ name: options.name ?? generated_1.INJECTION_TOKEN,
11
+ transport: microservices_1.Transport.TCP,
12
+ options: { host, port },
13
+ };
14
+ }
15
+ //# sourceMappingURL=nest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nest.js","sourceRoot":"","sources":["../src/nest.ts"],"names":[],"mappings":";;AASA,4CAOC;AAfD,yDAAkD;AAClD,qCAAoE;AACpE,2CAA8C;AAM9C,SAAgB,gBAAgB,CAAC,UAAmC,EAAE;IACpE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,IAAA,2BAAkB,EAAC,OAAO,CAAC,CAAC;IACnD,OAAO;QACL,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,2BAAe;QACrC,SAAS,EAAE,yBAAS,CAAC,GAAG;QACxB,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;KACxB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { z } from 'zod';
2
+ export declare const getFileInfoRequestSchema: z.ZodObject<{
3
+ id: z.ZodOptional<z.ZodString>;
4
+ key: z.ZodOptional<z.ZodString>;
5
+ }, "strip", z.ZodTypeAny, {
6
+ id?: string | undefined;
7
+ key?: string | undefined;
8
+ }, {
9
+ id?: string | undefined;
10
+ key?: string | undefined;
11
+ }>;
12
+ export type GetFileInfoRequest = z.infer<typeof getFileInfoRequestSchema>;
13
+ export declare const getSignedUrlRequestSchema: z.ZodObject<{
14
+ id: z.ZodOptional<z.ZodString>;
15
+ key: z.ZodOptional<z.ZodString>;
16
+ expiresInSeconds: z.ZodOptional<z.ZodNumber>;
17
+ }, "strip", z.ZodTypeAny, {
18
+ id?: string | undefined;
19
+ key?: string | undefined;
20
+ expiresInSeconds?: number | undefined;
21
+ }, {
22
+ id?: string | undefined;
23
+ key?: string | undefined;
24
+ expiresInSeconds?: number | undefined;
25
+ }>;
26
+ export type GetSignedUrlRequest = z.infer<typeof getSignedUrlRequestSchema>;
27
+ export declare const deleteFileRequestSchema: z.ZodObject<{
28
+ id: z.ZodOptional<z.ZodString>;
29
+ key: z.ZodOptional<z.ZodString>;
30
+ }, "strip", z.ZodTypeAny, {
31
+ id?: string | undefined;
32
+ key?: string | undefined;
33
+ }, {
34
+ id?: string | undefined;
35
+ key?: string | undefined;
36
+ }>;
37
+ export type DeleteFileRequest = z.infer<typeof deleteFileRequestSchema>;
38
+ export declare const BATCH_OPERATION_TYPES: {
39
+ readonly GET: "get";
40
+ readonly DELETE: "delete";
41
+ };
42
+ //# sourceMappingURL=schemas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,eAAO,MAAM,wBAAwB;;;;;;;;;EAGnC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,eAAO,MAAM,yBAAyB;;;;;;;;;;;;EAIpC,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E,eAAO,MAAM,uBAAuB;;;;;;;;;EAGlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,eAAO,MAAM,qBAAqB;;;CAGxB,CAAC"}
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BATCH_OPERATION_TYPES = exports.deleteFileRequestSchema = exports.getSignedUrlRequestSchema = exports.getFileInfoRequestSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ const uuidSchema = zod_1.z.string().uuid();
6
+ const nonEmptyStringSchema = zod_1.z.string().min(1);
7
+ exports.getFileInfoRequestSchema = zod_1.z.object({
8
+ id: uuidSchema.optional(),
9
+ key: nonEmptyStringSchema.optional(),
10
+ });
11
+ exports.getSignedUrlRequestSchema = zod_1.z.object({
12
+ id: uuidSchema.optional(),
13
+ key: nonEmptyStringSchema.optional(),
14
+ expiresInSeconds: zod_1.z.number().int().positive().optional(),
15
+ });
16
+ exports.deleteFileRequestSchema = zod_1.z.object({
17
+ id: uuidSchema.optional(),
18
+ key: nonEmptyStringSchema.optional(),
19
+ });
20
+ exports.BATCH_OPERATION_TYPES = {
21
+ GET: 'get',
22
+ DELETE: 'delete',
23
+ };
24
+ //# sourceMappingURL=schemas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AAExB,MAAM,UAAU,GAAG,OAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC;AACrC,MAAM,oBAAoB,GAAG,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAElC,QAAA,wBAAwB,GAAG,OAAC,CAAC,MAAM,CAAC;IAC/C,EAAE,EAAE,UAAU,CAAC,QAAQ,EAAE;IACzB,GAAG,EAAE,oBAAoB,CAAC,QAAQ,EAAE;CACrC,CAAC,CAAC;AAGU,QAAA,yBAAyB,GAAG,OAAC,CAAC,MAAM,CAAC;IAChD,EAAE,EAAE,UAAU,CAAC,QAAQ,EAAE;IACzB,GAAG,EAAE,oBAAoB,CAAC,QAAQ,EAAE;IACpC,gBAAgB,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;CACzD,CAAC,CAAC;AAGU,QAAA,uBAAuB,GAAG,OAAC,CAAC,MAAM,CAAC;IAC9C,EAAE,EAAE,UAAU,CAAC,QAAQ,EAAE;IACzB,GAAG,EAAE,oBAAoB,CAAC,QAAQ,EAAE;CACrC,CAAC,CAAC;AAGU,QAAA,qBAAqB,GAAG;IACnC,GAAG,EAAE,KAAK;IACV,MAAM,EAAE,QAAQ;CACR,CAAC"}
@@ -0,0 +1,9 @@
1
+ # Best practices — storage-service client
2
+
3
+ See also email-service `docs/BEST_PRACTICES.md` (same transport / auth model).
4
+
5
+ - **HTTP** for signed URL / upload from apps with `x-api-key`.
6
+ - **TCP** for internal Nest↔Nest or .NET↔Nest (`StorageServiceTcpClient`).
7
+ - **Kafka** for async file events (`StorageService.Topics.*`).
8
+ - Configure with `STORAGE_SERVICE_*` env or `StorageServiceOptions` — any host/port.
9
+ - Keep TCP private; do not expose `TCP_PORT` publicly.
@@ -0,0 +1,18 @@
1
+ # Publish .NET SDK to nuget.org (Trusted Publishing)
2
+
3
+ No long-lived API key required. Full steps mirror email-service:
4
+
5
+ → [Trusted Publishing on nuget.org](https://learn.microsoft.com/en-us/nuget/nuget-org/trusted-publishing)
6
+
7
+ ## Quick setup
8
+
9
+ 1. nuget.org → **Trusted Publishing** → policy:
10
+ - Repository owner: `yaghmori`
11
+ - Repository: `storage-service`
12
+ - Workflow: `cd-main.yml` (filename only)
13
+ 2. GitHub → Settings → Secrets and variables → Actions → **Variables** → `NUGET_USER` = nuget.org **username** (not email, not API key).
14
+ 3. CD uses `permissions.id-token: write` + `NuGet/login@v1` → temp key → `dotnet nuget push`.
15
+
16
+ Private repos: one successful login within 7 days permanently activates the policy.
17
+
18
+ After it works, revoke any old nuget.org API keys / `NUGET_TOKEN`.
@@ -0,0 +1,200 @@
1
+ # Multi-protocol client guide (storage-service)
2
+
3
+ Full integration guide for **storage-service** over **HTTP**, **NestJS TCP**, and **Kafka** from **Node.js**, **.NET**. Companion to the email-service protocol guide — same auth, framing, and env conventions, different contracts.
4
+
5
+ | Artifact | Location |
6
+ |----------|----------|
7
+ | Truth file | `packages/client/contracts.json` |
8
+ | Node SDK | `@yaghmori/storage-service` |
9
+ | .NET SDK | `Yaghmori.StorageService` |
10
+ | Docker | `ghcr.io/yaghmori/storage-service` |
11
+
12
+ ```bash
13
+ cd packages/client && pnpm run codegen
14
+ ```
15
+
16
+ Default suggestions: TCP **4002**, HTTP **4000** (override freely).
17
+
18
+ ---
19
+
20
+ ## 1. Protocol selection
21
+
22
+ | Protocol | Use for | Auth |
23
+ |----------|---------|------|
24
+ | **HTTP** | Uploads/downloads/signed URLs from apps and gateways | `x-api-key` / Bearer JWT |
25
+ | **TCP** | Metadata RPCs (file info, delete, signed URL) inside the mesh | Network trust |
26
+ | **Kafka** | Async lifecycle (`file.uploaded` / `deleted` / `processed`) | Cluster SASL/SSL |
27
+
28
+ Binary uploads are **HTTP multipart** (or your existing upload path). Prefer signing downloads rather than streaming huge files over TCP.
29
+
30
+ ---
31
+
32
+ ## 2. Address resolution
33
+
34
+ | Client env | Purpose |
35
+ |------------|---------|
36
+ | `STORAGE_SERVICE_URL` | Full HTTP base |
37
+ | `STORAGE_SERVICE_HOST` | Host for TCP/HTTP fallback |
38
+ | `STORAGE_SERVICE_TCP_PORT` | TCP dial port |
39
+ | `STORAGE_SERVICE_HTTP_PORT` | HTTP dial port if URL omitted |
40
+ | `STORAGE_SERVICE_API_KEY` / `STORAGE_SERVICE_BEARER` | HTTP auth |
41
+
42
+ Service listen: `PORT` (HTTP), `TCP_PORT` (Nest TCP). Kafka: same `KAFKA_*` variables as email-service.
43
+
44
+ Priority: constructor options → env → contract defaults.
45
+
46
+ ---
47
+
48
+ ## 3. Authentication (HTTP)
49
+
50
+ Identical header contract to email-service:
51
+
52
+ - `x-api-key: <key>` (preferred for API keys)
53
+ - `Authorization: Bearer <jwt>`
54
+ - `Authorization: ApiKey <key>`
55
+
56
+ Service: `AUTH_API_KEYS`, `JWT_SECRET`, or `AUTH_DISABLED=true` on trusted meshes only. `GET /health` may be `@Public()`.
57
+
58
+ Never expose TCP publicly. Never put API keys in `Authorization: Bearer`.
59
+
60
+ ---
61
+
62
+ ## 4. HTTP paths (contracts)
63
+
64
+ | Constant | Path | Notes |
65
+ |----------|------|-------|
66
+ | `UPLOAD` | `POST /upload` | Multipart / binary upload |
67
+ | `GET_FILE` | `GET /files/{id}` | Metadata |
68
+ | `DELETE_FILE` | `DELETE /files/{id}` | Soft/hard delete per service |
69
+ | `DOWNLOAD` | `GET /files/{id}/download` | Stream or redirect |
70
+ | `SIGNED_URL` | `GET /files/{id}/signed-url` | Time-limited URL |
71
+ | `HEALTH` | `GET /health` | Liveness |
72
+
73
+ SDK HTTP helpers currently emphasize signed URL + health; extend with multipart upload using the same auth headers and `HttpPaths.Upload`.
74
+
75
+ Best practices:
76
+
77
+ - Short-lived signed URLs for browsers/CDN.
78
+ - Timeouts longer for upload/download (60–120s) than for metadata.
79
+ - Retry 408/429/5xx with backoff; never retry non-idempotent uploads without an idempotency key.
80
+ - Do not log storage keys or full signed URLs in cleartext logs.
81
+
82
+ ---
83
+
84
+ ## 5. NestJS TCP
85
+
86
+ Framing: `<utf8-byte-length>#<json>` (same as email).
87
+
88
+ | Constant | Pattern |
89
+ |----------|---------|
90
+ | `GET_FILE_INFO` | `storage.get_file_info` |
91
+ | `DELETE_FILE` | `storage.delete_file` |
92
+ | `BATCH_OPERATIONS` | `storage.batch_operations` |
93
+ | `GET_SIGNED_URL` | `storage.get_signed_url` |
94
+ | `UPLOAD_FILE` | `uploadFile` |
95
+ | `GET_ASSET_URL` | `getAssetUrl` |
96
+ | `DELETE_ASSET` | `deleteAsset` |
97
+ | `HEALTH_CHECK` | `health.check` |
98
+
99
+ Request/response includes `id`; events omit `id`. SDKs throw when response `err` is set.
100
+
101
+ ---
102
+
103
+ ## 6. Kafka
104
+
105
+ | Topic constant | Topic | Event type |
106
+ |----------------|-------|------------|
107
+ | `FILE_UPLOADED` | `file.uploaded` | `evt.storage.file.uploaded.v1` |
108
+ | `FILE_DELETED` | `file.deleted` | `evt.storage.file.deleted.v1` |
109
+ | `FILE_PROCESSED` | `file.processed` | `evt.storage.file.processed.v1` |
110
+
111
+ Producer defaults: `acks=all`, idempotent, retries, JSON values. Consumers: manual commit, dedicated group IDs, subscribe to the topics above.
112
+
113
+ Typical pattern: service publishes lifecycle events after successful storage mutations; other services react (thumbnails, antivirus, analytics) without calling TCP on the hot path.
114
+
115
+ ---
116
+
117
+ ## 7. Node.js
118
+
119
+ ```ts
120
+ import {
121
+ createStorageHttpClient,
122
+ storageTcpClient,
123
+ StorageService,
124
+ resolveKafkaConnection,
125
+ } from '@yaghmori/storage-service';
126
+
127
+ const http = createStorageHttpClient({
128
+ baseUrl: process.env.STORAGE_SERVICE_URL,
129
+ auth: { apiKey: process.env.STORAGE_SERVICE_API_KEY! },
130
+ });
131
+
132
+ const tcp = storageTcpClient({
133
+ host: process.env.STORAGE_SERVICE_HOST,
134
+ port: Number(process.env.STORAGE_SERVICE_TCP_PORT),
135
+ });
136
+
137
+ const kafkaConn = resolveKafkaConnection();
138
+ // pass kafkaConn to kafkajs; use StorageService.topics.*
139
+ ```
140
+
141
+ ---
142
+
143
+ ## 8. .NET
144
+
145
+ ```csharp
146
+ services.AddStorageServiceClient();
147
+
148
+ await using var tcp = new StorageServiceTcpClient(new StorageServiceOptions
149
+ {
150
+ Host = "storage-service",
151
+ TcpPort = 4002,
152
+ });
153
+ var info = await tcp.GetFileInfoAsync<JsonElement>(new { id = fileId });
154
+
155
+ using var http = new StorageServiceHttpClient(new StorageServiceOptions
156
+ {
157
+ BaseUrl = "https://storage.prod.example.com",
158
+ ApiKey = Environment.GetEnvironmentVariable("STORAGE_SERVICE_API_KEY"),
159
+ });
160
+ using var signed = await http.GetSignedUrlAsync(fileId);
161
+
162
+ using var producer = new StorageKafkaProducer(new StorageKafkaOptions());
163
+ await producer.PublishAsync(StorageService.Topics.FileUploaded, new { id = fileId });
164
+
165
+ using var consumer = new StorageKafkaConsumer(new StorageKafkaOptions(), "media-pipeline");
166
+ ```
167
+
168
+ `NestJsTcpClient` implements framing. Constants are PascalCase (`StorageService.Patterns.GetSignedUrl`).
169
+
170
+ ---
171
+
172
+
173
+ ## 10. Security checklist
174
+
175
+ - [ ] Authenticated HTTP on every non-health route
176
+ - [ ] Private TCP
177
+ - [ ] Signed URLs expire and use HTTPS
178
+ - [ ] Kafka SASL/SSL in shared clusters
179
+ - [ ] Separate API keys per environment
180
+ - [ ] No PII in object keys if avoidable; scrub logs
181
+
182
+ ---
183
+
184
+ ## 11. Debugging
185
+
186
+ | Symptom | Check |
187
+ |---------|-------|
188
+ | 401 | `x-api-key` vs `AUTH_API_KEYS` |
189
+ | Wrong file host | `STORAGE_SERVICE_URL` vs service `PORT` |
190
+ | TCP pattern miss | Compare to `contracts.json` (legacy `uploadFile` vs `storage.*`) |
191
+ | Signed URL 404 | File id / soft-delete |
192
+ | Missed Kafka events | Group id, topic name, consumer lag |
193
+
194
+ ---
195
+
196
+ ## 12. Codegen
197
+
198
+ Edit `contracts.json` only; regenerate TS/C#. Align breaking path/pattern/topic changes with a SemVer bump of the client packages.
199
+
200
+ See also [`USAGE.md`](./USAGE.md) and email-service `PROTOCOL_GUIDE.md` for framing/auth deep dives shared across services.
package/docs/USAGE.md ADDED
@@ -0,0 +1,162 @@
1
+ # Full SDK usage — @yaghmori/storage-service
2
+
3
+ HTTP + TCP + Kafka names + auth. Deploy on **any host/port**; configure the SDK.
4
+
5
+ ## Install / run
6
+
7
+ ```bash
8
+ pnpm add @yaghmori/storage-service zod
9
+ docker pull ghcr.io/yaghmori/storage-service:latest
10
+ ```
11
+
12
+ ```bash
13
+ docker run --rm -p 9000:9000 -p 5002:5002 \
14
+ -e PORT=9000 -e TCP_PORT=5002 \
15
+ -e AUTH_API_KEYS=dev-key \
16
+ -e DATABASE_URL=... \
17
+ ghcr.io/yaghmori/storage-service:latest
18
+ ```
19
+
20
+ ## Configure address / ports
21
+
22
+ | | Client options | Client env | Service env |
23
+ |--|----------------|------------|-------------|
24
+ | HTTP | `baseUrl` / `host`+`port` | `STORAGE_SERVICE_URL` or `HOST`+`HTTP_PORT` | `PORT` |
25
+ | TCP | `storageTcpClient({ host, port })` | `STORAGE_SERVICE_HOST`, `STORAGE_SERVICE_TCP_PORT` | `TCP_PORT` |
26
+ | Kafka | `StorageKafka.connection()` | `KAFKA_BROKERS`, SASL_*, SSL | same |
27
+
28
+ ## Auth (HTTP)
29
+
30
+ Same contract as email-service:
31
+
32
+ | Mode | Service config | Client |
33
+ |------|----------------|--------|
34
+ | API key | `AUTH_API_KEYS=k1,k2` | `auth: { apiKey }` → `x-api-key` |
35
+ | JWT | `JWT_SECRET` + Bearer with `serviceName` | `auth: { bearerToken }` |
36
+ | Open | `AUTH_DISABLED=true` | omit auth |
37
+ | Health | always public | `GET /health` |
38
+
39
+ TCP: private network. Kafka: broker SASL/SSL via env (not HTTP API keys).
40
+
41
+ ```ts
42
+ import { createStorageHttpClient } from '@yaghmori/storage-service';
43
+
44
+ const storage = createStorageHttpClient({
45
+ baseUrl: process.env.STORAGE_SERVICE_URL,
46
+ auth: { apiKey: process.env.STORAGE_SERVICE_API_KEY! },
47
+ });
48
+ ```
49
+
50
+ ## HTTP
51
+
52
+ ```ts
53
+ await storage.upload(formData);
54
+ await storage.getFile(id);
55
+ await storage.getSignedUrl(id);
56
+ await storage.deleteFile(id);
57
+ await storage.health();
58
+ ```
59
+
60
+ ## Nest TCP
61
+
62
+ ```ts
63
+ import { ClientsModule } from '@nestjs/microservices';
64
+ import { storageTcpClient, StorageService } from '@yaghmori/storage-service';
65
+
66
+ ClientsModule.register([
67
+ storageTcpClient({
68
+ host: process.env.STORAGE_SERVICE_HOST ?? 'storage-service',
69
+ port: Number(process.env.STORAGE_SERVICE_TCP_PORT ?? 4002),
70
+ }),
71
+ ]);
72
+
73
+ // client.send(StorageService.patterns.GET_SIGNED_URL, { id })
74
+ ```
75
+
76
+ ## Kafka
77
+
78
+ ```ts
79
+ import { Kafka } from 'kafkajs';
80
+ import { StorageKafka, StorageService } from '@yaghmori/storage-service';
81
+
82
+ const conn = StorageKafka.connection();
83
+ const kafka = new Kafka({
84
+ clientId: conn.clientId,
85
+ brokers: conn.brokers,
86
+ ssl: conn.ssl,
87
+ sasl: conn.sasl as any,
88
+ });
89
+
90
+ // Consume / produce using package topic names:
91
+ StorageService.topics.FILE_UPLOADED;
92
+ StorageService.eventTypes.UPLOADED;
93
+ ```
94
+
95
+ ## .NET
96
+
97
+ ```xml
98
+ <PackageReference Include="Yaghmori.StorageService" Version="0.1.*" />
99
+ ```
100
+
101
+ ### TCP (easiest)
102
+
103
+ ```csharp
104
+ using Yaghmori.StorageService;
105
+
106
+ await using var storage = new StorageServiceTcpClient("storage-service", 5002);
107
+
108
+ var signed = await storage.GetSignedUrlAsync<JsonElement>(new { id = fileId });
109
+ var info = await storage.GetFileInfoAsync<JsonElement>(new { id = fileId });
110
+
111
+ // Or any pattern
112
+ await storage.SendAsync<JsonElement>(StorageService.Patterns.DeleteFile, new { id = fileId });
113
+ ```
114
+
115
+ ### DI
116
+
117
+ ```csharp
118
+ builder.Services.AddStorageServiceClient(o =>
119
+ {
120
+ o.Host = "storage-service";
121
+ o.TcpPort = 4002;
122
+ o.BaseUrl = "http://storage-service:4000";
123
+ o.ApiKey = builder.Configuration["Storage:ApiKey"];
124
+ });
125
+ ```
126
+
127
+ ### HTTP
128
+
129
+ ```csharp
130
+ using var http = new StorageServiceHttpClient(new StorageServiceOptions
131
+ {
132
+ BaseUrl = Environment.GetEnvironmentVariable("STORAGE_SERVICE_URL"),
133
+ ApiKey = Environment.GetEnvironmentVariable("STORAGE_SERVICE_API_KEY"),
134
+ });
135
+ await http.GetSignedUrlAsync(fileId);
136
+ await http.HealthAsync();
137
+ ```
138
+
139
+ ### Kafka producer + consumer (.NET)
140
+
141
+ ```csharp
142
+ using var producer = new StorageKafkaProducer(new StorageKafkaOptions());
143
+ await producer.PublishAsync(StorageService.Topics.FileUploaded, new { id = fileId });
144
+
145
+ using var consumer = new StorageKafkaConsumer(new StorageKafkaOptions(), "media-pipeline");
146
+ ```
147
+
148
+
149
+ ## Full multi-protocol guide
150
+
151
+ → **[PROTOCOL_GUIDE.md](./PROTOCOL_GUIDE.md)** — HTTP / TCP / Kafka for Node and .NET.
152
+
153
+ ## Service env
154
+
155
+ ```bash
156
+ PORT=9000
157
+ TCP_PORT=5002
158
+ AUTH_API_KEYS=prod-key
159
+ # AUTH_DISABLED=true
160
+ JWT_SECRET=... # optional for Bearer
161
+ KAFKA_BROKERS=...
162
+ ```
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@yaghmori/storage-service",
3
+ "version": "0.1.1",
4
+ "description": "Client SDK for storage-service — ports, TCP patterns, Kafka topics, Zod DTOs",
5
+ "license": "MIT",
6
+ "type": "commonjs",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./nest": {
15
+ "types": "./dist/nest.d.ts",
16
+ "default": "./dist/nest.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "contracts.json",
22
+ "README.md",
23
+ "docs"
24
+ ],
25
+ "dependencies": {
26
+ "zod": "^3.23.8"
27
+ },
28
+ "peerDependencies": {
29
+ "@nestjs/common": "^11.0.0",
30
+ "@nestjs/microservices": "^11.0.0",
31
+ "rxjs": "^7.8.0"
32
+ },
33
+ "peerDependenciesMeta": {
34
+ "@nestjs/common": {
35
+ "optional": true
36
+ },
37
+ "@nestjs/microservices": {
38
+ "optional": true
39
+ },
40
+ "rxjs": {
41
+ "optional": true
42
+ }
43
+ },
44
+ "devDependencies": {
45
+ "@nestjs/common": "^11.0.0",
46
+ "@nestjs/microservices": "^11.0.0",
47
+ "@types/node": "^22.15.3",
48
+ "rxjs": "^7.8.0",
49
+ "typescript": "^5.9.3"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public",
53
+ "registry": "https://registry.npmjs.org/"
54
+ },
55
+ "repository": {
56
+ "type": "git",
57
+ "url": "git+https://github.com/yaghmori/storage-service.git",
58
+ "directory": "packages/client"
59
+ },
60
+ "engines": {
61
+ "node": ">=18.0.0"
62
+ },
63
+ "scripts": {
64
+ "codegen": "node scripts/codegen.cjs",
65
+ "build": "pnpm run codegen && tsc -p tsconfig.json",
66
+ "check-types": "tsc -p tsconfig.json --noEmit"
67
+ }
68
+ }