appos 0.3.1-0 → 0.3.3-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/dist/bin/auth-schema-CcqAJY9P.mjs +2 -0
- package/dist/bin/better-sqlite3-CuQ3hsWl.mjs +2 -0
- package/dist/bin/bun-sql-DGeo-s_M.mjs +2 -0
- package/dist/bin/cache-3oO07miM.mjs +2 -0
- package/dist/bin/chunk-l9p7A9gZ.mjs +2 -0
- package/dist/bin/cockroach-BaICwY7N.mjs +2 -0
- package/dist/bin/database-CaysWPpa.mjs +2 -0
- package/dist/bin/esm-BvsccvmM.mjs +2 -0
- package/dist/bin/esm-CGKzJ7Am.mjs +3 -0
- package/dist/bin/event-DnSe3eh0.mjs +8 -0
- package/dist/bin/extract-blob-metadata-iqwTl2ft.mjs +170 -0
- package/dist/bin/generate-image-variant-Lyx0vhM6.mjs +2 -0
- package/dist/bin/generate-preview-0MrKxslA.mjs +2 -0
- package/dist/bin/libsql-DQJrZsU9.mjs +2 -0
- package/dist/bin/logger-BAGZLUzj.mjs +2 -0
- package/dist/bin/main.mjs +1201 -190
- package/dist/bin/migrator-B7iNKM8N.mjs +2 -0
- package/dist/bin/migrator-BKE1cSQQ.mjs +2 -0
- package/dist/bin/migrator-BXcbc9zs.mjs +2 -0
- package/dist/bin/migrator-B_XhRWZC.mjs +8 -0
- package/dist/bin/migrator-Bz52Gtr8.mjs +2 -0
- package/dist/bin/migrator-C7W-cZHB.mjs +2 -0
- package/dist/bin/migrator-CEnKyGSW.mjs +2 -0
- package/dist/bin/migrator-CHzIIl5X.mjs +2 -0
- package/dist/bin/migrator-CR-rjZdM.mjs +2 -0
- package/dist/bin/migrator-CjIr1ZCx.mjs +8 -0
- package/dist/bin/migrator-Cuubh2dg.mjs +2 -0
- package/dist/bin/migrator-D8m-ORbr.mjs +8 -0
- package/dist/bin/migrator-DBFwrhZH.mjs +2 -0
- package/dist/bin/migrator-DLmhW9u_.mjs +2 -0
- package/dist/bin/migrator-DLoHx807.mjs +4 -0
- package/dist/bin/migrator-DtN_iS87.mjs +2 -0
- package/dist/bin/migrator-Yc57lb3w.mjs +2 -0
- package/dist/bin/migrator-cEVXH3xC.mjs +2 -0
- package/dist/bin/migrator-hWi-sYIq.mjs +2 -0
- package/dist/bin/mysql2-DufFWkj4.mjs +2 -0
- package/dist/bin/neon-serverless-5a4h2VFz.mjs +2 -0
- package/dist/bin/node-CiOp4xrR.mjs +22 -0
- package/dist/bin/node-mssql-DvZGaUkB.mjs +322 -0
- package/dist/bin/node-postgres-BqbJVBQY.mjs +2 -0
- package/dist/bin/node-postgres-DnhRTTO8.mjs +2 -0
- package/dist/bin/open-0ksnL0S8.mjs +2 -0
- package/dist/bin/pdf-sUYeFPr4.mjs +14 -0
- package/dist/bin/pg-CaH8ptj-.mjs +2 -0
- package/dist/bin/pg-core-BLTZt9AH.mjs +8 -0
- package/dist/bin/pg-core-CGzidKaA.mjs +2 -0
- package/dist/bin/pglite-BJB9z7Ju.mjs +2 -0
- package/dist/bin/planetscale-serverless-H3RfLlMK.mjs +13 -0
- package/dist/bin/postgres-js-DuOf1eWm.mjs +2 -0
- package/dist/bin/purge-attachment-DQXpTtTx.mjs +2 -0
- package/dist/bin/purge-audit-logs-BEt2J2gD.mjs +2 -0
- package/dist/bin/{purge-unattached-blobs-Duvv8Izd.mjs → purge-unattached-blobs-DOmk4ddJ.mjs} +1 -1
- package/dist/bin/query-builder-DSRrR6X_.mjs +8 -0
- package/dist/bin/query-builder-V8-LDhvA.mjs +3 -0
- package/dist/bin/session-CdB1A-LB.mjs +14 -0
- package/dist/bin/session-Cl2e-_i8.mjs +8 -0
- package/dist/bin/singlestore-COft6TlR.mjs +8 -0
- package/dist/bin/sql-D-eKV1Dn.mjs +2 -0
- package/dist/bin/sqlite-cloud-Co9jOn5G.mjs +2 -0
- package/dist/bin/sqlite-proxy-Cpu78gJF.mjs +2 -0
- package/dist/bin/src-C-oXmCzx.mjs +6 -0
- package/dist/bin/table-3zUpWkMg.mjs +2 -0
- package/dist/bin/track-db-changes-DWyY5jXm.mjs +2 -0
- package/dist/bin/utils-CyoeCJlf.mjs +2 -0
- package/dist/bin/utils-EoqYQKy1.mjs +2 -0
- package/dist/bin/utils-bsypyqPl.mjs +2 -0
- package/dist/bin/vercel-postgres-HWL6xtqi.mjs +2 -0
- package/dist/bin/workflow-zxHDyfLq.mjs +2 -0
- package/dist/bin/youch-handler-DrYdbUhe.mjs +2 -0
- package/dist/bin/zod-MJjkEkRY.mjs +24 -0
- package/dist/exports/api/_virtual/rolldown_runtime.mjs +36 -1
- package/dist/exports/api/app-context.mjs +24 -1
- package/dist/exports/api/auth-schema.mjs +373 -1
- package/dist/exports/api/auth.d.mts +4 -0
- package/dist/exports/api/auth.mjs +188 -1
- package/dist/exports/api/cache.d.mts +2 -2
- package/dist/exports/api/cache.mjs +28 -1
- package/dist/exports/api/config.mjs +72 -1
- package/dist/exports/api/constants.mjs +92 -1
- package/dist/exports/api/container.mjs +49 -1
- package/dist/exports/api/database.mjs +218 -1
- package/dist/exports/api/event.mjs +236 -1
- package/dist/exports/api/i18n.mjs +45 -1
- package/dist/exports/api/index.mjs +20 -1
- package/dist/exports/api/instrumentation.mjs +40 -1
- package/dist/exports/api/logger.mjs +26 -1
- package/dist/exports/api/mailer.mjs +37 -1
- package/dist/exports/api/middleware.mjs +73 -1
- package/dist/exports/api/openapi.mjs +507 -1
- package/dist/exports/api/orm.mjs +43 -1
- package/dist/exports/api/otel.mjs +56 -1
- package/dist/exports/api/redis.mjs +41 -1
- package/dist/exports/api/storage-schema.mjs +72 -1
- package/dist/exports/api/storage.mjs +833 -1
- package/dist/exports/api/web/auth.mjs +17 -1
- package/dist/exports/api/workflow.mjs +196 -1
- package/dist/exports/api/workflows/_virtual/rolldown_runtime.mjs +36 -1
- package/dist/exports/api/workflows/api/auth-schema.mjs +373 -1
- package/dist/exports/api/workflows/api/auth.d.mts +4 -0
- package/dist/exports/api/workflows/api/cache.d.mts +2 -2
- package/dist/exports/api/workflows/api/event.mjs +126 -1
- package/dist/exports/api/workflows/api/redis.mjs +3 -1
- package/dist/exports/api/workflows/api/workflow.mjs +135 -1
- package/dist/exports/api/workflows/constants.mjs +23 -1
- package/dist/exports/api/workflows/extract-blob-metadata.mjs +132 -1
- package/dist/exports/api/workflows/generate-image-variant.mjs +118 -1
- package/dist/exports/api/workflows/generate-preview.mjs +160 -1
- package/dist/exports/api/workflows/index.mjs +3 -1
- package/dist/exports/api/workflows/purge-attachment.mjs +34 -1
- package/dist/exports/api/workflows/purge-audit-logs.mjs +47 -1
- package/dist/exports/api/workflows/purge-unattached-blobs.mjs +46 -1
- package/dist/exports/api/workflows/track-db-changes.mjs +110 -1
- package/dist/exports/cli/_virtual/rolldown_runtime.mjs +36 -1
- package/dist/exports/cli/api/auth-schema.mjs +373 -1
- package/dist/exports/cli/api/auth.d.mts +4 -0
- package/dist/exports/cli/api/cache.d.mts +2 -2
- package/dist/exports/cli/api/event.mjs +126 -1
- package/dist/exports/cli/api/redis.mjs +3 -1
- package/dist/exports/cli/api/workflow.mjs +135 -1
- package/dist/exports/cli/api/workflows/extract-blob-metadata.mjs +132 -1
- package/dist/exports/cli/api/workflows/generate-image-variant.mjs +118 -1
- package/dist/exports/cli/api/workflows/generate-preview.mjs +160 -1
- package/dist/exports/cli/api/workflows/purge-attachment.mjs +34 -1
- package/dist/exports/cli/api/workflows/purge-audit-logs.mjs +47 -1
- package/dist/exports/cli/api/workflows/purge-unattached-blobs.mjs +46 -1
- package/dist/exports/cli/api/workflows/track-db-changes.mjs +110 -1
- package/dist/exports/cli/command.d.mts +2 -0
- package/dist/exports/cli/command.mjs +43 -1
- package/dist/exports/cli/constants.mjs +23 -1
- package/dist/exports/cli/index.mjs +3 -1
- package/dist/exports/devtools/index.js +4 -1
- package/dist/exports/tests/api/auth.d.mts +4 -0
- package/dist/exports/tests/api/cache.d.mts +2 -2
- package/dist/exports/tests/api/middleware/i18n.mjs +1 -1
- package/dist/exports/tests/api/middleware/youch-handler.mjs +1 -1
- package/dist/exports/tests/api/openapi.mjs +1 -1
- package/dist/exports/tests/api/server.mjs +1 -1
- package/dist/exports/tests/constants.mjs +1 -1
- package/dist/exports/vendors/date.js +1 -1
- package/dist/exports/vendors/toolkit.js +1 -1
- package/dist/exports/vendors/zod.js +1 -1
- package/dist/exports/vitest/globals.mjs +1 -1
- package/dist/exports/web/auth.js +75 -1
- package/dist/exports/web/i18n.js +45 -1
- package/dist/exports/web/index.js +8 -1
- package/package.json +19 -17
- package/dist/bin/auth-schema-Va0CYicu.mjs +0 -2
- package/dist/bin/event-8JibGFH_.mjs +0 -2
- package/dist/bin/extract-blob-metadata-DjPfHtQ2.mjs +0 -2
- package/dist/bin/generate-image-variant-D5VDFyWj.mjs +0 -2
- package/dist/bin/generate-preview-Dssw7w5U.mjs +0 -2
- package/dist/bin/purge-attachment-BBPzIxwt.mjs +0 -2
- package/dist/bin/purge-audit-logs-BeZy3IFM.mjs +0 -2
- package/dist/bin/track-db-changes-CFykw_YO.mjs +0 -2
- package/dist/bin/workflow-BNUZrj4F.mjs +0 -2
- package/dist/bin/youch-handler-BadUgHb0.mjs +0 -2
|
@@ -1 +1,236 @@
|
|
|
1
|
-
import{defineRedisClient
|
|
1
|
+
import { defineRedisClient } from "./redis.mjs";
|
|
2
|
+
import { APPOS_DIR, EVENTS_DIR, FILE_EXT } from "./constants.mjs";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { basename, join } from "node:path";
|
|
5
|
+
import { glob } from "node:fs/promises";
|
|
6
|
+
import { camelCase } from "es-toolkit";
|
|
7
|
+
|
|
8
|
+
//#region src/api/event.ts
|
|
9
|
+
/**
|
|
10
|
+
* Defines the event bus for pub/sub messaging.
|
|
11
|
+
* Uses Redis for cross-server communication.
|
|
12
|
+
*
|
|
13
|
+
* Algorithm:
|
|
14
|
+
* 1. Create lazy Redis publisher client
|
|
15
|
+
* 2. Create lazy Redis subscriber client
|
|
16
|
+
* 3. Manage subscriptions via callback registry
|
|
17
|
+
* 4. Provide publish/subscribe/close methods
|
|
18
|
+
*
|
|
19
|
+
* @param opts - Event bus configuration
|
|
20
|
+
* @returns Event bus instance
|
|
21
|
+
*/
|
|
22
|
+
function defineEventBus(opts) {
|
|
23
|
+
const publisherClient = defineRedisClient({
|
|
24
|
+
logger: opts.logger,
|
|
25
|
+
url: opts.dbUrl
|
|
26
|
+
});
|
|
27
|
+
let subscriberClient = null;
|
|
28
|
+
const subscribers = /* @__PURE__ */ new Map();
|
|
29
|
+
return {
|
|
30
|
+
async publish(channel, message) {
|
|
31
|
+
await publisherClient.publish(channel, JSON.stringify(message));
|
|
32
|
+
},
|
|
33
|
+
async subscribe(channel, callback) {
|
|
34
|
+
if (!subscriberClient) {
|
|
35
|
+
subscriberClient = publisherClient.duplicate();
|
|
36
|
+
await subscriberClient.connect();
|
|
37
|
+
}
|
|
38
|
+
if (!subscribers.has(channel)) {
|
|
39
|
+
subscribers.set(channel, /* @__PURE__ */ new Set());
|
|
40
|
+
await subscriberClient.subscribe(channel, (msg) => {
|
|
41
|
+
const callbacks = subscribers.get(channel);
|
|
42
|
+
if (callbacks) {
|
|
43
|
+
let parsed;
|
|
44
|
+
try {
|
|
45
|
+
parsed = JSON.parse(msg);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
opts.logger.error({
|
|
48
|
+
err,
|
|
49
|
+
channel,
|
|
50
|
+
msg
|
|
51
|
+
}, "Failed to parse event message");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
for (const cb of callbacks) try {
|
|
55
|
+
cb(parsed);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
opts.logger.error({
|
|
58
|
+
err,
|
|
59
|
+
channel
|
|
60
|
+
}, "Event handler error");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
subscribers.get(channel).add(callback);
|
|
66
|
+
return () => {
|
|
67
|
+
const callbacks = subscribers.get(channel);
|
|
68
|
+
if (callbacks) callbacks.delete(callback);
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
hasSubscribers(channel) {
|
|
72
|
+
const callbacks = subscribers.get(channel);
|
|
73
|
+
return callbacks !== void 0 && callbacks.size > 0;
|
|
74
|
+
},
|
|
75
|
+
async close() {
|
|
76
|
+
if (subscriberClient?.isOpen) await subscriberClient.quit();
|
|
77
|
+
if (publisherClient.isOpen) await publisherClient.quit();
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Defines a type-safe event with in-memory and Redis pub/sub support.
|
|
83
|
+
*
|
|
84
|
+
* Algorithm:
|
|
85
|
+
* 1. Define event with input schema and in-memory run handler
|
|
86
|
+
* 2. On emit(): validate input, run in-memory handler, publish to Redis (fire-and-forget)
|
|
87
|
+
* 3. .subscribe() creates Redis subscription for tRPC/SSE/WebSocket handlers
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```typescript
|
|
91
|
+
* // api/events/order-status.ts
|
|
92
|
+
* export default defineEvent({
|
|
93
|
+
* input: z.object({
|
|
94
|
+
* orderId: z.string(),
|
|
95
|
+
* status: z.enum(["pending", "shipped", "delivered"]),
|
|
96
|
+
* }),
|
|
97
|
+
* async run(ctx) {
|
|
98
|
+
* ctx.container.logger.info(`Order ${ctx.input.orderId} is ${ctx.input.status}`);
|
|
99
|
+
* },
|
|
100
|
+
* });
|
|
101
|
+
*
|
|
102
|
+
* // Emit from anywhere
|
|
103
|
+
* await orderStatus.emit({ orderId: "123", status: "shipped" });
|
|
104
|
+
*
|
|
105
|
+
* // Subscribe (e.g., in tRPC router)
|
|
106
|
+
* const unsubscribe = await orderStatus.subscribe(async (ctx) => {
|
|
107
|
+
* // Push to client via SSE/WebSocket
|
|
108
|
+
* });
|
|
109
|
+
* // Cleanup when client disconnects
|
|
110
|
+
* unsubscribe();
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
function defineEvent(options) {
|
|
114
|
+
let container = null;
|
|
115
|
+
let eventName = null;
|
|
116
|
+
return {
|
|
117
|
+
inputSchema: options.input,
|
|
118
|
+
get name() {
|
|
119
|
+
return eventName;
|
|
120
|
+
},
|
|
121
|
+
register(c, name) {
|
|
122
|
+
container = c;
|
|
123
|
+
eventName = name;
|
|
124
|
+
},
|
|
125
|
+
async emit(input) {
|
|
126
|
+
if (!container || !eventName) throw new Error("Event not registered. Ensure the worker is started before emitting events.");
|
|
127
|
+
const validated = options.input.parse(input);
|
|
128
|
+
const ctx = {
|
|
129
|
+
container,
|
|
130
|
+
input: validated
|
|
131
|
+
};
|
|
132
|
+
await options.run(ctx);
|
|
133
|
+
container.eventBus.publish(eventName, validated).catch((err) => {
|
|
134
|
+
container.logger.error({
|
|
135
|
+
err,
|
|
136
|
+
event: eventName
|
|
137
|
+
}, "Redis publish failed");
|
|
138
|
+
});
|
|
139
|
+
},
|
|
140
|
+
async subscribe(handler) {
|
|
141
|
+
if (!container || !eventName) throw new Error("Event not registered. Ensure the worker is started before subscribing.");
|
|
142
|
+
return container.eventBus.subscribe(eventName, async (message) => {
|
|
143
|
+
const validated = options.input.parse(message);
|
|
144
|
+
const ctx = {
|
|
145
|
+
container,
|
|
146
|
+
input: validated
|
|
147
|
+
};
|
|
148
|
+
try {
|
|
149
|
+
await handler(ctx);
|
|
150
|
+
} catch (err) {
|
|
151
|
+
container.logger.error({
|
|
152
|
+
err,
|
|
153
|
+
event: eventName
|
|
154
|
+
}, "Event subscription handler error");
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Input schema for dbChangesEvent.
|
|
162
|
+
*/
|
|
163
|
+
const dbChangeInputSchema = z.object({
|
|
164
|
+
action: z.enum([
|
|
165
|
+
"INSERT",
|
|
166
|
+
"UPDATE",
|
|
167
|
+
"DELETE"
|
|
168
|
+
]),
|
|
169
|
+
newData: z.record(z.string(), z.unknown()).nullable(),
|
|
170
|
+
oldData: z.record(z.string(), z.unknown()).nullable(),
|
|
171
|
+
organizationId: z.string().nullable(),
|
|
172
|
+
tableName: z.string(),
|
|
173
|
+
timestamp: z.string(),
|
|
174
|
+
userId: z.string().nullable()
|
|
175
|
+
});
|
|
176
|
+
/**
|
|
177
|
+
* Built-in event for database changes.
|
|
178
|
+
* Emitted by trackDbChanges workflow.
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```typescript
|
|
182
|
+
* // Subscribe to DB changes (e.g., in tRPC subscription)
|
|
183
|
+
* import { dbChangesEvent } from "appos/api";
|
|
184
|
+
*
|
|
185
|
+
* const unsubscribe = await dbChangesEvent.subscribe(async (ctx) => {
|
|
186
|
+
* wsServer.publish("db-changes", JSON.stringify(ctx.input));
|
|
187
|
+
* });
|
|
188
|
+
*
|
|
189
|
+
* // Cleanup when done
|
|
190
|
+
* unsubscribe();
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
const dbChangesEvent = defineEvent({
|
|
194
|
+
input: dbChangeInputSchema,
|
|
195
|
+
async run() {}
|
|
196
|
+
});
|
|
197
|
+
/**
|
|
198
|
+
* Extracts event name from filepath as camelCase.
|
|
199
|
+
*/
|
|
200
|
+
function getEventName(filepath) {
|
|
201
|
+
return camelCase(basename(filepath, ".ts"));
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Auto-discovers and registers all events from the given directory.
|
|
205
|
+
*
|
|
206
|
+
* Algorithm:
|
|
207
|
+
* 1. Register built-in dbChangesEvent
|
|
208
|
+
* 2. Glob all .ts files in the directory (excluding test files)
|
|
209
|
+
* 3. Import each module's default export
|
|
210
|
+
* 4. If it's an Event (has emit + subscribe), call register(container, name)
|
|
211
|
+
*
|
|
212
|
+
* @param opts - Load events options
|
|
213
|
+
*/
|
|
214
|
+
async function loadEvents(opts) {
|
|
215
|
+
const { container } = opts;
|
|
216
|
+
const eventsDir = opts.eventsDir ?? join(process.cwd(), APPOS_DIR, EVENTS_DIR);
|
|
217
|
+
dbChangesEvent.register(container, "dbChanges");
|
|
218
|
+
const files = await Array.fromAsync(glob(`${eventsDir}/**/*.${FILE_EXT}`, { exclude: [
|
|
219
|
+
"**/*.test.ts",
|
|
220
|
+
"**/*.spec.ts",
|
|
221
|
+
"**/*.test.js",
|
|
222
|
+
"**/*.spec.js"
|
|
223
|
+
] }));
|
|
224
|
+
for (const file of files) {
|
|
225
|
+
const mod = await import(file);
|
|
226
|
+
if (mod.default && typeof mod.default === "object") {
|
|
227
|
+
if ("emit" in mod.default && "subscribe" in mod.default) {
|
|
228
|
+
const name = getEventName(file);
|
|
229
|
+
mod.default.register(container, name);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
//#endregion
|
|
236
|
+
export { dbChangeInputSchema, dbChangesEvent, defineEvent, defineEventBus, loadEvents };
|
|
@@ -1 +1,45 @@
|
|
|
1
|
-
import{LOCALES_DIR
|
|
1
|
+
import { LOCALES_DIR, PUBLIC_DIR } from "./constants.mjs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { lstatSync, readdirSync, watch } from "node:fs";
|
|
4
|
+
import { createInstance } from "i18next";
|
|
5
|
+
import I18nextFsBackend from "i18next-fs-backend";
|
|
6
|
+
import { initReactI18next } from "react-i18next";
|
|
7
|
+
|
|
8
|
+
//#region src/api/i18n.ts
|
|
9
|
+
/**
|
|
10
|
+
* Type-safe default options for i18n.
|
|
11
|
+
* Apps should spread these and override as needed.
|
|
12
|
+
*/
|
|
13
|
+
const defaultI18nOptions = {
|
|
14
|
+
fallbackLng: "en",
|
|
15
|
+
supportedLngs: ["en"],
|
|
16
|
+
defaultNS: "translation",
|
|
17
|
+
ns: ["translation"],
|
|
18
|
+
interpolation: { escapeValue: false },
|
|
19
|
+
react: { useSuspense: false }
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Initializes i18next for server-side with filesystem backend.
|
|
23
|
+
*
|
|
24
|
+
* @param opts - Initialization options for i18next.
|
|
25
|
+
* @returns A promise that resolves to the initialized i18next instance.
|
|
26
|
+
*/
|
|
27
|
+
async function defineI18n(opts) {
|
|
28
|
+
const localesDir = join(PUBLIC_DIR, LOCALES_DIR);
|
|
29
|
+
const i18n$1 = createInstance();
|
|
30
|
+
if (process.env.NODE_ENV !== "production" && !process.env.VITEST && !process.env.CI) watch(localesDir, { recursive: true }, async (event, fn) => {
|
|
31
|
+
await i18n$1.reloadResources(opts.supportedLngs || []);
|
|
32
|
+
}).unref();
|
|
33
|
+
await i18n$1.use(I18nextFsBackend).use(initReactI18next).init({
|
|
34
|
+
...opts,
|
|
35
|
+
backend: { loadPath: `${localesDir}/{{lng}}/{{ns}}.json` },
|
|
36
|
+
initAsync: false,
|
|
37
|
+
preload: readdirSync(localesDir).filter((fileName) => {
|
|
38
|
+
return lstatSync(join(localesDir, fileName)).isDirectory();
|
|
39
|
+
})
|
|
40
|
+
});
|
|
41
|
+
return i18n$1;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//#endregion
|
|
45
|
+
export { defaultI18nOptions, defineI18n };
|
|
@@ -1 +1,20 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { defineAppContext } from "./app-context.mjs";
|
|
2
|
+
import { auditActionSchema, createAccessControl, defineAuth } from "./auth.mjs";
|
|
3
|
+
import { defineAuthSchema } from "./auth-schema.mjs";
|
|
4
|
+
import { defineRedisClient } from "./redis.mjs";
|
|
5
|
+
import { defineCache } from "./cache.mjs";
|
|
6
|
+
import { baseSchema, defineConfig } from "./config.mjs";
|
|
7
|
+
import { defineAppContainer } from "./container.mjs";
|
|
8
|
+
import { dbChanges, defineDatabase, defineMigrationOpts, defineTestDatabase, migrationsSchema } from "./database.mjs";
|
|
9
|
+
import { dbChangeInputSchema, dbChangesEvent, defineEvent, defineEventBus, loadEvents } from "./event.mjs";
|
|
10
|
+
import { defaultI18nOptions, defineI18n } from "./i18n.mjs";
|
|
11
|
+
import { defineLogger } from "./logger.mjs";
|
|
12
|
+
import { defineMailer } from "./mailer.mjs";
|
|
13
|
+
import { defineMiddleware, loadMiddleware } from "./middleware.mjs";
|
|
14
|
+
import { defineOpenAPI, defineOpenAPIConfig, defineOpenAPIEndpoint, defineTypedResponses, generateOpenAPIDocument, loadAndRegisterAPIRoutes, registerRoutes, scanAPIRoutes, writeOpenAPISpecs } from "./openapi.mjs";
|
|
15
|
+
import { withOtelSpan } from "./otel.mjs";
|
|
16
|
+
import { defineScheduledWorkflow, defineWorkflow, loadWorkflows } from "./workflow.mjs";
|
|
17
|
+
import { StorageService, defineS3Disk, defineStorage } from "./storage.mjs";
|
|
18
|
+
import { defineStorageSchema } from "./storage-schema.mjs";
|
|
19
|
+
|
|
20
|
+
export { StorageService, auditActionSchema, baseSchema, createAccessControl, dbChangeInputSchema, dbChanges, dbChangesEvent, defaultI18nOptions, defineAppContainer, defineAppContext, defineAuth, defineAuthSchema, defineCache, defineConfig, defineDatabase, defineEvent, defineEventBus, defineI18n, defineLogger, defineMailer, defineMiddleware, defineMigrationOpts, defineOpenAPI, defineOpenAPIConfig, defineOpenAPIEndpoint, defineRedisClient, defineS3Disk, defineScheduledWorkflow, defineStorage, defineStorageSchema, defineTestDatabase, defineTypedResponses, defineWorkflow, generateOpenAPIDocument, loadAndRegisterAPIRoutes, loadEvents, loadMiddleware, loadWorkflows, migrationsSchema, registerRoutes, scanAPIRoutes, withOtelSpan, writeOpenAPISpecs };
|
|
@@ -1 +1,40 @@
|
|
|
1
|
-
import{__reExport
|
|
1
|
+
import { __reExport } from "./_virtual/rolldown_runtime.mjs";
|
|
2
|
+
import { register } from "node:module";
|
|
3
|
+
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
|
|
4
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
5
|
+
import { PinoInstrumentation } from "@opentelemetry/instrumentation-pino";
|
|
6
|
+
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
7
|
+
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
8
|
+
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-node";
|
|
9
|
+
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
|
|
10
|
+
|
|
11
|
+
export * from "@opentelemetry/api"
|
|
12
|
+
|
|
13
|
+
//#region src/instrumentation.ts
|
|
14
|
+
var instrumentation_exports = {};
|
|
15
|
+
import * as import__opentelemetry_api from "@opentelemetry/api";
|
|
16
|
+
__reExport(instrumentation_exports, import__opentelemetry_api);
|
|
17
|
+
register("@opentelemetry/instrumentation/hook.mjs", import.meta.url);
|
|
18
|
+
const service = {
|
|
19
|
+
name: process.env.APP_NAME || "appos",
|
|
20
|
+
version: process.env.APP_VERSION || "development"
|
|
21
|
+
};
|
|
22
|
+
new NodeSDK({
|
|
23
|
+
resource: resourceFromAttributes({
|
|
24
|
+
[ATTR_SERVICE_NAME]: service.name,
|
|
25
|
+
[ATTR_SERVICE_VERSION]: service.version
|
|
26
|
+
}),
|
|
27
|
+
spanProcessors: [new BatchSpanProcessor(new OTLPTraceExporter({
|
|
28
|
+
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "http://localhost:4318/v1/traces",
|
|
29
|
+
headers: process.env.OTEL_EXPORTER_OTLP_HEADERS ? JSON.parse(process.env.OTEL_EXPORTER_OTLP_HEADERS) : void 0
|
|
30
|
+
}))],
|
|
31
|
+
instrumentations: [getNodeAutoInstrumentations({
|
|
32
|
+
"@opentelemetry/instrumentation-fs": { enabled: false },
|
|
33
|
+
"@opentelemetry/instrumentation-dns": { enabled: false }
|
|
34
|
+
}), new PinoInstrumentation({ logHook: (_span, record) => {
|
|
35
|
+
record["service.name"] = service.name;
|
|
36
|
+
} })]
|
|
37
|
+
}).start();
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
export { instrumentation_exports };
|
|
@@ -1 +1,26 @@
|
|
|
1
|
-
import
|
|
1
|
+
import pino from "pino";
|
|
2
|
+
import pinoPretty from "pino-pretty";
|
|
3
|
+
|
|
4
|
+
//#region src/api/logger.ts
|
|
5
|
+
/**
|
|
6
|
+
* Define the logger instance.
|
|
7
|
+
*
|
|
8
|
+
* @param opts - The options.
|
|
9
|
+
* @returns The logger.
|
|
10
|
+
*/
|
|
11
|
+
async function defineLogger(opts) {
|
|
12
|
+
const transports = [];
|
|
13
|
+
const commonOpts = {
|
|
14
|
+
level: opts.level || "info",
|
|
15
|
+
redact: opts.redact || [],
|
|
16
|
+
...transports.length > 0 && { transport: transports.length === 1 ? transports[0] : { targets: transports } }
|
|
17
|
+
};
|
|
18
|
+
try {
|
|
19
|
+
return pino(commonOpts, process.env.NODE_ENV !== "production" && !process.env.VITEST ? pinoPretty() : void 0);
|
|
20
|
+
} catch (_) {
|
|
21
|
+
return pino(commonOpts);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
export { defineLogger };
|
|
@@ -1 +1,37 @@
|
|
|
1
|
-
import{render
|
|
1
|
+
import { render } from "@react-email/render";
|
|
2
|
+
import { createTransport } from "nodemailer";
|
|
3
|
+
|
|
4
|
+
//#region src/api/mailer.ts
|
|
5
|
+
/**
|
|
6
|
+
* Define the mailer.
|
|
7
|
+
*
|
|
8
|
+
* @param env - The environment variables.
|
|
9
|
+
* @returns The mailer.
|
|
10
|
+
*/
|
|
11
|
+
function defineMailer({ from, smtpUrl }) {
|
|
12
|
+
const mailer = createTransport({ url: smtpUrl });
|
|
13
|
+
return { async send(payload) {
|
|
14
|
+
let html;
|
|
15
|
+
let text;
|
|
16
|
+
let mailOptions;
|
|
17
|
+
if ("react" in payload) {
|
|
18
|
+
[html, text] = await Promise.all([render(payload.react), render(payload.react, { plainText: true })]);
|
|
19
|
+
const { react: _, ...rest } = payload;
|
|
20
|
+
mailOptions = rest;
|
|
21
|
+
} else {
|
|
22
|
+
html = payload.html;
|
|
23
|
+
text = payload.text;
|
|
24
|
+
const { html: _, text: __, ...rest } = payload;
|
|
25
|
+
mailOptions = rest;
|
|
26
|
+
}
|
|
27
|
+
await mailer.sendMail({
|
|
28
|
+
from: payload.from || from,
|
|
29
|
+
...mailOptions,
|
|
30
|
+
html,
|
|
31
|
+
text
|
|
32
|
+
});
|
|
33
|
+
} };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
export { defineMailer };
|
|
@@ -1 +1,73 @@
|
|
|
1
|
-
import{FILE_EXT
|
|
1
|
+
import { FILE_EXT } from "./constants.mjs";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
import fs, { glob } from "node:fs/promises";
|
|
4
|
+
import { camelCase } from "es-toolkit";
|
|
5
|
+
|
|
6
|
+
//#region src/api/middleware.ts
|
|
7
|
+
/**
|
|
8
|
+
* Defines a user middleware loaded from api/middleware/ directory.
|
|
9
|
+
*
|
|
10
|
+
* Use numeric prefixes (000_, 001_, etc.) to control load order.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* // api/middleware/000_custom-auth.ts
|
|
14
|
+
* export default defineMiddleware({
|
|
15
|
+
* handler: (container) => (req, res, next) => {
|
|
16
|
+
* // Custom logic using container
|
|
17
|
+
* next();
|
|
18
|
+
* },
|
|
19
|
+
* });
|
|
20
|
+
*/
|
|
21
|
+
function defineMiddleware(config) {
|
|
22
|
+
return {
|
|
23
|
+
name: config.name ?? null,
|
|
24
|
+
handler: config.handler
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Extracts middleware name from filename, removing numeric prefix.
|
|
29
|
+
* "000_custom-auth.ts" -> "customAuth"
|
|
30
|
+
*/
|
|
31
|
+
function getMiddlewareName(filename) {
|
|
32
|
+
return camelCase(basename(filename, ".ts").replace(/^\d+_/, ""));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Loads user middleware from a directory and registers them with the app.
|
|
36
|
+
*
|
|
37
|
+
* Middleware files are loaded in alphabetical order by filename.
|
|
38
|
+
* Use numeric prefixes (000_, 001_, etc.) to control the load order.
|
|
39
|
+
*
|
|
40
|
+
* @param middlewareDir - The directory containing middleware files
|
|
41
|
+
* @param container - The application container
|
|
42
|
+
* @param app - The Express application instance
|
|
43
|
+
*/
|
|
44
|
+
async function loadMiddleware(middlewareDir, container, app) {
|
|
45
|
+
try {
|
|
46
|
+
await fs.access(middlewareDir);
|
|
47
|
+
} catch {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const files = [];
|
|
51
|
+
for await (const file of glob(`${middlewareDir}/**/*.${FILE_EXT}`)) if (!file.endsWith(".test.ts") && !file.endsWith(".spec.ts") && !file.endsWith(".test.js") && !file.endsWith(".spec.js")) files.push(file);
|
|
52
|
+
files.sort((a, b) => basename(a).localeCompare(basename(b)));
|
|
53
|
+
for (const file of files) try {
|
|
54
|
+
const middleware = (await import(file)).default;
|
|
55
|
+
if (middleware && typeof middleware.handler === "function") {
|
|
56
|
+
const name = middleware.name ?? getMiddlewareName(file);
|
|
57
|
+
container.logger.debug({
|
|
58
|
+
name,
|
|
59
|
+
file
|
|
60
|
+
}, "Loading user middleware");
|
|
61
|
+
app.use(middleware.handler(container));
|
|
62
|
+
} else container.logger.warn({ file }, "Middleware file missing default export with handler function");
|
|
63
|
+
} catch (err) {
|
|
64
|
+
container.logger.error({
|
|
65
|
+
file,
|
|
66
|
+
error: err instanceof Error ? err.message : err
|
|
67
|
+
}, "Failed to load middleware");
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
//#endregion
|
|
73
|
+
export { defineMiddleware, loadMiddleware };
|