fiberx-backend-toolkit 1.0.7 → 1.0.8
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/dist/fibase/app_event_payload_builder.d.ts +8 -0
- package/dist/fibase/app_event_payload_builder.js +25 -0
- package/dist/fibase/fibase_app_client.d.ts +52 -0
- package/dist/fibase/fibase_app_client.js +193 -0
- package/dist/fibase/main.d.ts +3 -0
- package/dist/fibase/main.js +10 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/processors/base_batch_processor.d.ts +29 -0
- package/dist/processors/base_batch_processor.js +106 -0
- package/dist/processors/main.d.ts +2 -0
- package/dist/processors/main.js +8 -0
- package/dist/types/fibase_type.d.ts +78 -0
- package/dist/types/fibase_type.js +2 -0
- package/dist/types/main.d.ts +2 -0
- package/dist/types/main.js +2 -0
- package/dist/types/processor_type.d.ts +32 -0
- package/dist/types/processor_type.js +2 -0
- package/package.json +11 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { FibaseAppEventBuildInputInterface, FibaseAppEventEnvelopeInterface } from "../types/fibase_type";
|
|
2
|
+
declare class FibaseAppEventPayloadBuilder {
|
|
3
|
+
/**
|
|
4
|
+
* Build the shared app-event envelope expected by the Fibase central app intake endpoint.
|
|
5
|
+
*/
|
|
6
|
+
static build<TPayload>(input: FibaseAppEventBuildInputInterface<TPayload>): FibaseAppEventEnvelopeInterface<TPayload>;
|
|
7
|
+
}
|
|
8
|
+
export default FibaseAppEventPayloadBuilder;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const main_1 = require("../utils/main");
|
|
4
|
+
class FibaseAppEventPayloadBuilder {
|
|
5
|
+
/**
|
|
6
|
+
* Build the shared app-event envelope expected by the Fibase central app intake endpoint.
|
|
7
|
+
*/
|
|
8
|
+
static build(input) {
|
|
9
|
+
const occurred_at = input.occurred_at
|
|
10
|
+
? new Date(input.occurred_at).toISOString()
|
|
11
|
+
: new Date().toISOString();
|
|
12
|
+
const source_event_id = input.source_event_id ?? main_1.UUIDGeneratorUtil.generateUUIDV2("EVT");
|
|
13
|
+
return {
|
|
14
|
+
event_type: input.event_type.trim().toLowerCase(),
|
|
15
|
+
event_version: input.event_version?.trim().toLowerCase() ?? "v1",
|
|
16
|
+
source_event_id,
|
|
17
|
+
idempotency_key: input.idempotency_key ?? source_event_id,
|
|
18
|
+
correlation_id: input.correlation_id ?? null,
|
|
19
|
+
occurred_at,
|
|
20
|
+
payload: input.payload,
|
|
21
|
+
metadata: input.metadata ?? null,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.default = FibaseAppEventPayloadBuilder;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { FibaseAppClientConfigInterface, FibaseAppClientEnvOptionsInterface, FibaseAppEventBuildInputInterface, FibaseHttpResponseInterface, FibaseRequestOptionsInterface, FibaseSendAppEventOptionsInterface, FibaseSignedRequestDataInterface, FibaseSignedRequestInputInterface } from "../types/fibase_type";
|
|
2
|
+
declare class FibaseAppClient {
|
|
3
|
+
private static instance;
|
|
4
|
+
readonly name = "fibase_app_client";
|
|
5
|
+
private readonly logger;
|
|
6
|
+
private readonly config;
|
|
7
|
+
private readonly headers;
|
|
8
|
+
private static readonly DEFAULT_HEADERS;
|
|
9
|
+
private static readonly DEFAULT_BASE_URLS;
|
|
10
|
+
constructor(config: FibaseAppClientConfigInterface);
|
|
11
|
+
/**
|
|
12
|
+
* Create a singleton client for applications that prefer process-wide reuse.
|
|
13
|
+
*/
|
|
14
|
+
static initialize(config: FibaseAppClientConfigInterface): FibaseAppClient;
|
|
15
|
+
/**
|
|
16
|
+
* Return the initialized singleton client.
|
|
17
|
+
*/
|
|
18
|
+
static getInstance(): FibaseAppClient;
|
|
19
|
+
/**
|
|
20
|
+
* Build a client from environment variables loaded by EnvManagerUtil.
|
|
21
|
+
*/
|
|
22
|
+
static fromEnv(options?: FibaseAppClientEnvOptionsInterface): FibaseAppClient;
|
|
23
|
+
/**
|
|
24
|
+
* Validate the minimum data required to sign Fibase app requests.
|
|
25
|
+
*/
|
|
26
|
+
private validateConfig;
|
|
27
|
+
/**
|
|
28
|
+
* Normalize request paths so signature generation matches the server authenticator.
|
|
29
|
+
*/
|
|
30
|
+
private normalizePath;
|
|
31
|
+
/**
|
|
32
|
+
* Build the same body hash format used by Fibase's RequestAuthenticatorUtil.
|
|
33
|
+
*/
|
|
34
|
+
hashBody(body: unknown): string;
|
|
35
|
+
/**
|
|
36
|
+
* Build the canonical string signed by external apps and verified by Fibase.
|
|
37
|
+
*/
|
|
38
|
+
buildCanonicalString(method: string, path: string, timestamp: string, nonce: string, body: unknown): string;
|
|
39
|
+
/**
|
|
40
|
+
* Build a signed request without sending it. Useful for tests and custom transports.
|
|
41
|
+
*/
|
|
42
|
+
buildSignedRequest<TBody = unknown>(input: FibaseSignedRequestInputInterface<TBody>): FibaseSignedRequestDataInterface<TBody>;
|
|
43
|
+
/**
|
|
44
|
+
* Send a signed request to Fibase using the standard app-auth headers.
|
|
45
|
+
*/
|
|
46
|
+
request<TResponse = unknown, TBody = unknown>(method: string, path: string, body?: TBody, options?: FibaseRequestOptionsInterface): Promise<FibaseHttpResponseInterface<TResponse>>;
|
|
47
|
+
/**
|
|
48
|
+
* Build and send a standard app event to the Fibase central app.
|
|
49
|
+
*/
|
|
50
|
+
sendAppEvent<TPayload, TResponse = unknown>(input: FibaseAppEventBuildInputInterface<TPayload>, options?: FibaseSendAppEventOptionsInterface): Promise<FibaseHttpResponseInterface<TResponse>>;
|
|
51
|
+
}
|
|
52
|
+
export default FibaseAppClient;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const axios_1 = __importDefault(require("axios"));
|
|
7
|
+
const main_1 = require("../utils/main");
|
|
8
|
+
const app_event_payload_builder_1 = __importDefault(require("./app_event_payload_builder"));
|
|
9
|
+
class FibaseAppClient {
|
|
10
|
+
static instance = null;
|
|
11
|
+
name = "fibase_app_client";
|
|
12
|
+
logger = new main_1.LoggerUtil(this.name);
|
|
13
|
+
config;
|
|
14
|
+
headers;
|
|
15
|
+
static DEFAULT_HEADERS = {
|
|
16
|
+
app_id: "X-App-Id",
|
|
17
|
+
app_key_version: "X-Key-Version",
|
|
18
|
+
timestamp: "X-Timestamp",
|
|
19
|
+
nonce: "X-Nonce",
|
|
20
|
+
signature: "x-Signature",
|
|
21
|
+
request_id: "X-Request-Id",
|
|
22
|
+
content_type: "Content-Type",
|
|
23
|
+
};
|
|
24
|
+
static DEFAULT_BASE_URLS = {
|
|
25
|
+
development: "http://localhost:4000",
|
|
26
|
+
staging: "https://staging-api.fibase.app",
|
|
27
|
+
production: "https://api.fibase.app",
|
|
28
|
+
};
|
|
29
|
+
constructor(config) {
|
|
30
|
+
this.config = {
|
|
31
|
+
...config,
|
|
32
|
+
base_url: config.base_url.replace(/\/+$/, ""),
|
|
33
|
+
key_algorithm: config.key_algorithm ?? "sha256",
|
|
34
|
+
timeout_ms: config.timeout_ms ?? 30000,
|
|
35
|
+
};
|
|
36
|
+
this.headers = { ...FibaseAppClient.DEFAULT_HEADERS, ...(config.headers ?? {}) };
|
|
37
|
+
this.validateConfig();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Create a singleton client for applications that prefer process-wide reuse.
|
|
41
|
+
*/
|
|
42
|
+
static initialize(config) {
|
|
43
|
+
if (this.instance) {
|
|
44
|
+
throw new Error("FibaseAppClient already initialized.");
|
|
45
|
+
}
|
|
46
|
+
this.instance = new FibaseAppClient(config);
|
|
47
|
+
return this.instance;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Return the initialized singleton client.
|
|
51
|
+
*/
|
|
52
|
+
static getInstance() {
|
|
53
|
+
if (!this.instance) {
|
|
54
|
+
throw new Error("FibaseAppClient not initialized.");
|
|
55
|
+
}
|
|
56
|
+
return this.instance;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Build a client from environment variables loaded by EnvManagerUtil.
|
|
60
|
+
*/
|
|
61
|
+
static fromEnv(options = {}) {
|
|
62
|
+
const env_manager = main_1.EnvManagerUtil.getInstance();
|
|
63
|
+
const mode = env_manager.getEnvVar("MODE", "development") ?? "development";
|
|
64
|
+
const default_base_urls = {
|
|
65
|
+
...FibaseAppClient.DEFAULT_BASE_URLS,
|
|
66
|
+
...(options.default_base_urls ?? {}),
|
|
67
|
+
};
|
|
68
|
+
const base_url = env_manager.getEnvVar(options.base_url_key ?? "FIBASE_BASE_URL") ??
|
|
69
|
+
default_base_urls[mode] ??
|
|
70
|
+
default_base_urls.development ??
|
|
71
|
+
FibaseAppClient.DEFAULT_BASE_URLS.development;
|
|
72
|
+
const app_id = env_manager.getEnvVar(options.app_id_key ?? "FIBASE_APP_ID");
|
|
73
|
+
const app_private_key = env_manager.getEnvVar(options.app_private_key_key ?? "FIBASE_APP_PRIVATE_KEY");
|
|
74
|
+
if (!app_id || !app_private_key) {
|
|
75
|
+
throw new Error("FIBASE_APP_ID and FIBASE_APP_PRIVATE_KEY are required");
|
|
76
|
+
}
|
|
77
|
+
return new FibaseAppClient({
|
|
78
|
+
base_url,
|
|
79
|
+
app_id,
|
|
80
|
+
app_private_key,
|
|
81
|
+
app_key_version: env_manager.getEnvVar(options.app_key_version_key ?? "FIBASE_APP_KEY_VERSION", 1),
|
|
82
|
+
key_algorithm: env_manager.getEnvVar(options.key_algorithm_key ?? "FIBASE_APP_KEY_ALGORITHM", "sha256"),
|
|
83
|
+
timeout_ms: Number(env_manager.getEnvVar(options.timeout_ms_key ?? "FIBASE_TIMEOUT_MS", 30000)),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Validate the minimum data required to sign Fibase app requests.
|
|
88
|
+
*/
|
|
89
|
+
validateConfig() {
|
|
90
|
+
if (!this.config.base_url) {
|
|
91
|
+
throw new Error("Fibase base_url is required");
|
|
92
|
+
}
|
|
93
|
+
if (!this.config.app_id) {
|
|
94
|
+
throw new Error("Fibase app_id is required");
|
|
95
|
+
}
|
|
96
|
+
if (!this.config.app_private_key) {
|
|
97
|
+
throw new Error("Fibase app_private_key is required");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Normalize request paths so signature generation matches the server authenticator.
|
|
102
|
+
*/
|
|
103
|
+
normalizePath(path) {
|
|
104
|
+
const [pathname] = path.split("?");
|
|
105
|
+
return pathname.startsWith("/") ? pathname : `/${pathname}`;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Build the same body hash format used by Fibase's RequestAuthenticatorUtil.
|
|
109
|
+
*/
|
|
110
|
+
hashBody(body) {
|
|
111
|
+
const json = JSON.stringify(body ?? {});
|
|
112
|
+
return main_1.CryptoKeyUtil.hash(json, "sha256", "hex");
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Build the canonical string signed by external apps and verified by Fibase.
|
|
116
|
+
*/
|
|
117
|
+
buildCanonicalString(method, path, timestamp, nonce, body) {
|
|
118
|
+
return [
|
|
119
|
+
method.toUpperCase(),
|
|
120
|
+
this.normalizePath(path),
|
|
121
|
+
timestamp,
|
|
122
|
+
nonce,
|
|
123
|
+
this.hashBody(body),
|
|
124
|
+
].join("\n");
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Build a signed request without sending it. Useful for tests and custom transports.
|
|
128
|
+
*/
|
|
129
|
+
buildSignedRequest(input) {
|
|
130
|
+
const method = input.method.toUpperCase();
|
|
131
|
+
const path = this.normalizePath(input.path);
|
|
132
|
+
const body = (input.body ?? {});
|
|
133
|
+
const timestamp = input.timestamp ?? Date.now().toString();
|
|
134
|
+
const nonce = input.nonce ?? main_1.UUIDGeneratorUtil.generateUUIDV2("NONCE");
|
|
135
|
+
const canonical = this.buildCanonicalString(method, path, timestamp, nonce, body);
|
|
136
|
+
const signature = main_1.CryptoKeyUtil.sign(canonical, this.config.app_private_key, this.config.key_algorithm, "base64");
|
|
137
|
+
return {
|
|
138
|
+
url: `${this.config.base_url}${path}`,
|
|
139
|
+
method,
|
|
140
|
+
path,
|
|
141
|
+
body,
|
|
142
|
+
canonical,
|
|
143
|
+
signature,
|
|
144
|
+
headers: {
|
|
145
|
+
[this.headers.content_type]: "application/json",
|
|
146
|
+
[this.headers.app_id]: this.config.app_id,
|
|
147
|
+
[this.headers.app_key_version]: String(this.config.app_key_version ?? 1),
|
|
148
|
+
[this.headers.timestamp]: timestamp,
|
|
149
|
+
[this.headers.nonce]: nonce,
|
|
150
|
+
[this.headers.signature]: signature,
|
|
151
|
+
[this.headers.request_id]: input.request_id ?? main_1.UUIDGeneratorUtil.generateUUIDV2("REQ"),
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Send a signed request to Fibase using the standard app-auth headers.
|
|
157
|
+
*/
|
|
158
|
+
async request(method, path, body, options = {}) {
|
|
159
|
+
const signed_request = this.buildSignedRequest({
|
|
160
|
+
method,
|
|
161
|
+
path,
|
|
162
|
+
body,
|
|
163
|
+
request_id: options.request_id,
|
|
164
|
+
});
|
|
165
|
+
const request_config = {
|
|
166
|
+
url: signed_request.url,
|
|
167
|
+
method: signed_request.method,
|
|
168
|
+
data: signed_request.body,
|
|
169
|
+
headers: { ...signed_request.headers, ...(options.headers ?? {}) },
|
|
170
|
+
timeout: this.config.timeout_ms,
|
|
171
|
+
};
|
|
172
|
+
this.logger.info("Sending signed Fibase app request", {
|
|
173
|
+
method: signed_request.method,
|
|
174
|
+
path: signed_request.path,
|
|
175
|
+
request_id: signed_request.headers[this.headers.request_id],
|
|
176
|
+
});
|
|
177
|
+
const response = await axios_1.default.request(request_config);
|
|
178
|
+
return {
|
|
179
|
+
status: response.status,
|
|
180
|
+
status_text: response.statusText,
|
|
181
|
+
data: response.data,
|
|
182
|
+
headers: response.headers,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Build and send a standard app event to the Fibase central app.
|
|
187
|
+
*/
|
|
188
|
+
async sendAppEvent(input, options = {}) {
|
|
189
|
+
const event_payload = app_event_payload_builder_1.default.build(input);
|
|
190
|
+
return this.request("POST", options.path ?? "/api/app-events", event_payload, options);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
exports.default = FibaseAppClient;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.FibaseAppClient = exports.FibaseAppEventPayloadBuilder = void 0;
|
|
7
|
+
const app_event_payload_builder_1 = __importDefault(require("./app_event_payload_builder"));
|
|
8
|
+
exports.FibaseAppEventPayloadBuilder = app_event_payload_builder_1.default;
|
|
9
|
+
const fibase_app_client_1 = __importDefault(require("./fibase_app_client"));
|
|
10
|
+
exports.FibaseAppClient = fibase_app_client_1.default;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -22,3 +22,5 @@ __exportStar(require("./validators/main"), exports);
|
|
|
22
22
|
__exportStar(require("./storage/main"), exports);
|
|
23
23
|
__exportStar(require("./mailer/main"), exports);
|
|
24
24
|
__exportStar(require("./rbac/main"), exports);
|
|
25
|
+
__exportStar(require("./processors/main"), exports);
|
|
26
|
+
__exportStar(require("./fibase/main"), exports);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { BatchProcessorAdapterInterface, BatchProcessorOptionsInterface, BatchProcessorRecordResultInterface, BatchProcessorRunResultInterface } from "../types/processor_type";
|
|
2
|
+
declare class BaseBatchProcessor<TRecord, TResult> {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
private readonly logger;
|
|
5
|
+
private readonly adapter;
|
|
6
|
+
private readonly options;
|
|
7
|
+
constructor(adapter: BatchProcessorAdapterInterface<TRecord, TResult>, options?: BatchProcessorOptionsInterface);
|
|
8
|
+
/**
|
|
9
|
+
* Convert a record id to a stable log-safe string.
|
|
10
|
+
*/
|
|
11
|
+
private getRecordId;
|
|
12
|
+
/**
|
|
13
|
+
* Mark a record as failed or dead-lettered, depending on adapter retry policy.
|
|
14
|
+
*/
|
|
15
|
+
private markFailure;
|
|
16
|
+
/**
|
|
17
|
+
* Process one record and update its lifecycle independently of the rest of the batch.
|
|
18
|
+
*/
|
|
19
|
+
processSingleRecord(record: TRecord): Promise<BatchProcessorRecordResultInterface<TRecord, TResult>>;
|
|
20
|
+
/**
|
|
21
|
+
* Process a provided list of records with the configured concurrency limit.
|
|
22
|
+
*/
|
|
23
|
+
processRecords(records: TRecord[]): Promise<BatchProcessorRecordResultInterface<TRecord, TResult>[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Fetch and process the next available batch of records.
|
|
26
|
+
*/
|
|
27
|
+
run(limit?: number): Promise<BatchProcessorRunResultInterface>;
|
|
28
|
+
}
|
|
29
|
+
export default BaseBatchProcessor;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const main_1 = require("../utils/main");
|
|
4
|
+
class BaseBatchProcessor {
|
|
5
|
+
name;
|
|
6
|
+
logger;
|
|
7
|
+
adapter;
|
|
8
|
+
options;
|
|
9
|
+
constructor(adapter, options = {}) {
|
|
10
|
+
this.name = options.name ?? "base_batch_processor";
|
|
11
|
+
this.logger = new main_1.LoggerUtil(this.name);
|
|
12
|
+
this.adapter = adapter;
|
|
13
|
+
this.options = {
|
|
14
|
+
name: this.name,
|
|
15
|
+
concurrency: options.concurrency ?? 5,
|
|
16
|
+
default_limit: options.default_limit ?? 50,
|
|
17
|
+
continue_on_error: options.continue_on_error ?? true,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Convert a record id to a stable log-safe string.
|
|
22
|
+
*/
|
|
23
|
+
getRecordId(record) {
|
|
24
|
+
return String(this.adapter.getRecordId(record));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Mark a record as failed or dead-lettered, depending on adapter retry policy.
|
|
28
|
+
*/
|
|
29
|
+
async markFailure(record, error) {
|
|
30
|
+
const record_id = this.getRecordId(record);
|
|
31
|
+
const should_dead_letter = (await this.adapter.shouldMoveToDeadLetter?.(record, error)) ?? false;
|
|
32
|
+
if (should_dead_letter && this.adapter.markRecordAsDeadLetter) {
|
|
33
|
+
await this.adapter.markRecordAsDeadLetter(record, error);
|
|
34
|
+
this.logger.error(`Record ${record_id} moved to dead letter`, { record_id, error });
|
|
35
|
+
return { record, record_id, status: "dead_letter", error };
|
|
36
|
+
}
|
|
37
|
+
await this.adapter.markRecordAsFailed(record, error);
|
|
38
|
+
this.logger.error(`Record ${record_id} failed`, { record_id, error });
|
|
39
|
+
return { record, record_id, status: "failed", error };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Process one record and update its lifecycle independently of the rest of the batch.
|
|
43
|
+
*/
|
|
44
|
+
async processSingleRecord(record) {
|
|
45
|
+
const record_id = this.getRecordId(record);
|
|
46
|
+
try {
|
|
47
|
+
this.logger.info(`Processing record ${record_id}`, { record_id });
|
|
48
|
+
await this.adapter.beforeProcessRecord?.(record);
|
|
49
|
+
await this.adapter.markRecordAsProcessing?.(record);
|
|
50
|
+
const result = await this.adapter.processRecord(record);
|
|
51
|
+
await this.adapter.markRecordAsCompleted(record, result);
|
|
52
|
+
const completed_result = {
|
|
53
|
+
record,
|
|
54
|
+
record_id,
|
|
55
|
+
status: "completed",
|
|
56
|
+
result,
|
|
57
|
+
};
|
|
58
|
+
await this.adapter.afterProcessRecord?.(record, completed_result);
|
|
59
|
+
this.logger.success(`Completed record ${record_id}`, { record_id });
|
|
60
|
+
return completed_result;
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
const failed_result = await this.markFailure(record, error);
|
|
64
|
+
await this.adapter.afterProcessRecord?.(record, failed_result);
|
|
65
|
+
if (!this.options.continue_on_error) {
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
return failed_result;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Process a provided list of records with the configured concurrency limit.
|
|
73
|
+
*/
|
|
74
|
+
async processRecords(records) {
|
|
75
|
+
if (!records.length) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
return main_1.PLimitUtil.map(records, this.options.concurrency, async (record) => {
|
|
79
|
+
return this.processSingleRecord(record);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Fetch and process the next available batch of records.
|
|
84
|
+
*/
|
|
85
|
+
async run(limit = this.options.default_limit) {
|
|
86
|
+
this.logger.info(`Starting batch processor ${this.name}`, {
|
|
87
|
+
limit,
|
|
88
|
+
concurrency: this.options.concurrency,
|
|
89
|
+
});
|
|
90
|
+
const records = await this.adapter.fetchRecordsToProcess(limit);
|
|
91
|
+
this.logger.info(`Fetched ${records.length} record(s) for ${this.name}`, {
|
|
92
|
+
count: records.length,
|
|
93
|
+
});
|
|
94
|
+
const results = await this.processRecords(records);
|
|
95
|
+
const summary = {
|
|
96
|
+
fetched_count: records.length,
|
|
97
|
+
processed_count: results.length,
|
|
98
|
+
succeeded_count: results.filter((result) => result.status === "completed").length,
|
|
99
|
+
failed_count: results.filter((result) => result.status === "failed").length,
|
|
100
|
+
dead_letter_count: results.filter((result) => result.status === "dead_letter").length,
|
|
101
|
+
};
|
|
102
|
+
this.logger.info(`Completed batch processor ${this.name}`, summary);
|
|
103
|
+
return summary;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
exports.default = BaseBatchProcessor;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.BaseBatchProcessor = void 0;
|
|
7
|
+
const base_batch_processor_1 = __importDefault(require("./base_batch_processor"));
|
|
8
|
+
exports.BaseBatchProcessor = base_batch_processor_1.default;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export type FibaseEnvironmentType = "development" | "staging" | "production" | string;
|
|
2
|
+
export interface FibaseHeaderNamesInterface {
|
|
3
|
+
app_id: string;
|
|
4
|
+
app_key_version: string;
|
|
5
|
+
timestamp: string;
|
|
6
|
+
nonce: string;
|
|
7
|
+
signature: string;
|
|
8
|
+
request_id: string;
|
|
9
|
+
content_type: string;
|
|
10
|
+
}
|
|
11
|
+
export interface FibaseAppClientConfigInterface {
|
|
12
|
+
base_url: string;
|
|
13
|
+
app_id: string;
|
|
14
|
+
app_private_key: string;
|
|
15
|
+
app_key_version?: number | string;
|
|
16
|
+
key_algorithm?: "sha256" | "sha384" | "sha512";
|
|
17
|
+
timeout_ms?: number;
|
|
18
|
+
headers?: Partial<FibaseHeaderNamesInterface>;
|
|
19
|
+
}
|
|
20
|
+
export interface FibaseAppClientEnvOptionsInterface {
|
|
21
|
+
base_url_key?: string;
|
|
22
|
+
app_id_key?: string;
|
|
23
|
+
app_private_key_key?: string;
|
|
24
|
+
app_key_version_key?: string;
|
|
25
|
+
key_algorithm_key?: string;
|
|
26
|
+
timeout_ms_key?: string;
|
|
27
|
+
default_base_urls?: Partial<Record<FibaseEnvironmentType, string>>;
|
|
28
|
+
}
|
|
29
|
+
export interface FibaseSignedRequestInputInterface<TBody = unknown> {
|
|
30
|
+
method: string;
|
|
31
|
+
path: string;
|
|
32
|
+
body?: TBody;
|
|
33
|
+
request_id?: string;
|
|
34
|
+
nonce?: string;
|
|
35
|
+
timestamp?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface FibaseSignedRequestDataInterface<TBody = unknown> {
|
|
38
|
+
url: string;
|
|
39
|
+
method: string;
|
|
40
|
+
path: string;
|
|
41
|
+
body: TBody;
|
|
42
|
+
headers: Record<string, string>;
|
|
43
|
+
canonical: string;
|
|
44
|
+
signature: string;
|
|
45
|
+
}
|
|
46
|
+
export interface FibaseRequestOptionsInterface {
|
|
47
|
+
request_id?: string;
|
|
48
|
+
headers?: Record<string, string>;
|
|
49
|
+
}
|
|
50
|
+
export interface FibaseAppEventEnvelopeInterface<TPayload = unknown> {
|
|
51
|
+
event_type: string;
|
|
52
|
+
event_version: string;
|
|
53
|
+
source_event_id: string;
|
|
54
|
+
idempotency_key: string;
|
|
55
|
+
correlation_id?: string | null;
|
|
56
|
+
occurred_at: string;
|
|
57
|
+
payload: TPayload;
|
|
58
|
+
metadata?: Record<string, unknown> | null;
|
|
59
|
+
}
|
|
60
|
+
export interface FibaseAppEventBuildInputInterface<TPayload = unknown> {
|
|
61
|
+
event_type: string;
|
|
62
|
+
payload: TPayload;
|
|
63
|
+
event_version?: string;
|
|
64
|
+
source_event_id?: string;
|
|
65
|
+
idempotency_key?: string;
|
|
66
|
+
correlation_id?: string | null;
|
|
67
|
+
occurred_at?: string | Date;
|
|
68
|
+
metadata?: Record<string, unknown> | null;
|
|
69
|
+
}
|
|
70
|
+
export interface FibaseSendAppEventOptionsInterface extends FibaseRequestOptionsInterface {
|
|
71
|
+
path?: string;
|
|
72
|
+
}
|
|
73
|
+
export interface FibaseHttpResponseInterface<TData = unknown> {
|
|
74
|
+
status: number;
|
|
75
|
+
status_text: string;
|
|
76
|
+
data: TData;
|
|
77
|
+
headers: Record<string, unknown>;
|
|
78
|
+
}
|
package/dist/types/main.d.ts
CHANGED
package/dist/types/main.js
CHANGED
|
@@ -25,3 +25,5 @@ __exportStar(require("./rbac_type"), exports);
|
|
|
25
25
|
__exportStar(require("./mailer_type"), exports);
|
|
26
26
|
__exportStar(require("./validator_type"), exports);
|
|
27
27
|
__exportStar(require("./storage_type"), exports);
|
|
28
|
+
__exportStar(require("./processor_type"), exports);
|
|
29
|
+
__exportStar(require("./fibase_type"), exports);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface BatchProcessorRunResultInterface {
|
|
2
|
+
fetched_count: number;
|
|
3
|
+
processed_count: number;
|
|
4
|
+
succeeded_count: number;
|
|
5
|
+
failed_count: number;
|
|
6
|
+
dead_letter_count: number;
|
|
7
|
+
}
|
|
8
|
+
export interface BatchProcessorRecordResultInterface<TRecord, TResult> {
|
|
9
|
+
record: TRecord;
|
|
10
|
+
record_id: string;
|
|
11
|
+
status: "completed" | "failed" | "dead_letter";
|
|
12
|
+
result?: TResult;
|
|
13
|
+
error?: unknown;
|
|
14
|
+
}
|
|
15
|
+
export interface BatchProcessorOptionsInterface {
|
|
16
|
+
name?: string;
|
|
17
|
+
concurrency?: number;
|
|
18
|
+
default_limit?: number;
|
|
19
|
+
continue_on_error?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface BatchProcessorAdapterInterface<TRecord, TResult> {
|
|
22
|
+
fetchRecordsToProcess(limit: number): Promise<TRecord[]>;
|
|
23
|
+
getRecordId(record: TRecord): string | number;
|
|
24
|
+
markRecordAsProcessing?(record: TRecord): Promise<TRecord | void>;
|
|
25
|
+
processRecord(record: TRecord): Promise<TResult>;
|
|
26
|
+
markRecordAsCompleted(record: TRecord, result: TResult): Promise<TRecord | void>;
|
|
27
|
+
markRecordAsFailed(record: TRecord, error: unknown): Promise<TRecord | void>;
|
|
28
|
+
shouldMoveToDeadLetter?(record: TRecord, error: unknown): boolean | Promise<boolean>;
|
|
29
|
+
markRecordAsDeadLetter?(record: TRecord, error: unknown): Promise<TRecord | void>;
|
|
30
|
+
beforeProcessRecord?(record: TRecord): Promise<void>;
|
|
31
|
+
afterProcessRecord?(record: TRecord, result: BatchProcessorRecordResultInterface<TRecord, TResult>): Promise<void>;
|
|
32
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fiberx-backend-toolkit",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "A TypeScript backend toolkit providing shared domain logic, infrastructure helpers, and utilities for FiberX server-side applications and services.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -51,6 +51,16 @@
|
|
|
51
51
|
"require": "./dist/mailer/main.js",
|
|
52
52
|
"default": "./dist/mailer/main.js"
|
|
53
53
|
},
|
|
54
|
+
"./processors": {
|
|
55
|
+
"types": "./dist/processors/main.d.ts",
|
|
56
|
+
"require": "./dist/processors/main.js",
|
|
57
|
+
"default": "./dist/processors/main.js"
|
|
58
|
+
},
|
|
59
|
+
"./fibase": {
|
|
60
|
+
"types": "./dist/fibase/main.d.ts",
|
|
61
|
+
"require": "./dist/fibase/main.js",
|
|
62
|
+
"default": "./dist/fibase/main.js"
|
|
63
|
+
},
|
|
54
64
|
"./rbac": {
|
|
55
65
|
"types": "./dist/rbac/main.d.ts",
|
|
56
66
|
"require": "./dist/rbac/main.js",
|