@flytedesk/app-kit 0.0.1 → 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.
Files changed (53) hide show
  1. package/dist/auth/guards.d.ts +5 -0
  2. package/dist/auth/guards.js +28 -0
  3. package/dist/auth/guards.js.map +1 -0
  4. package/dist/auth/index.d.ts +33 -5
  5. package/dist/auth/index.js +31 -5
  6. package/dist/auth/index.js.map +1 -1
  7. package/dist/auth/oidc-client.d.ts +47 -0
  8. package/dist/auth/oidc-client.js +162 -0
  9. package/dist/auth/oidc-client.js.map +1 -0
  10. package/dist/auth/pkce.d.ts +8 -0
  11. package/dist/auth/pkce.js +12 -0
  12. package/dist/auth/pkce.js.map +1 -0
  13. package/dist/auth/plugin.d.ts +8 -0
  14. package/dist/auth/plugin.js +362 -0
  15. package/dist/auth/plugin.js.map +1 -0
  16. package/dist/auth/tokens.d.ts +24 -0
  17. package/dist/auth/tokens.js +35 -0
  18. package/dist/auth/tokens.js.map +1 -0
  19. package/dist/auth/types.d.ts +156 -0
  20. package/dist/auth/types.js +2 -0
  21. package/dist/auth/types.js.map +1 -0
  22. package/dist/cli/trace-sync.d.ts +2 -0
  23. package/dist/cli/trace-sync.js +362 -0
  24. package/dist/cli/trace-sync.js.map +1 -0
  25. package/dist/rate-limit/index.d.ts +6 -0
  26. package/dist/rate-limit/index.js +7 -0
  27. package/dist/rate-limit/index.js.map +1 -0
  28. package/dist/rate-limit/plugin.d.ts +23 -0
  29. package/dist/rate-limit/plugin.js +75 -0
  30. package/dist/rate-limit/plugin.js.map +1 -0
  31. package/dist/rate-limit/store.d.ts +51 -0
  32. package/dist/rate-limit/store.js +59 -0
  33. package/dist/rate-limit/store.js.map +1 -0
  34. package/dist/trace/client.d.ts +20 -0
  35. package/dist/trace/client.js +21 -0
  36. package/dist/trace/client.js.map +1 -0
  37. package/dist/trace/errors.d.ts +31 -0
  38. package/dist/trace/errors.js +37 -0
  39. package/dist/trace/errors.js.map +1 -0
  40. package/dist/trace/index.d.ts +15 -4
  41. package/dist/trace/index.js +3 -6
  42. package/dist/trace/index.js.map +1 -1
  43. package/dist/trace/queries.d.ts +59 -0
  44. package/dist/trace/queries.js +147 -0
  45. package/dist/trace/queries.js.map +1 -0
  46. package/dist/trace/types.d.ts +72 -0
  47. package/dist/trace/types.js +14 -0
  48. package/dist/trace/types.js.map +1 -0
  49. package/package.json +54 -8
  50. package/prisma/fragments/trace.meta.json +4 -0
  51. package/prisma/fragments/trace.prisma +87 -0
  52. package/prisma/migrations/0001_app_kit_trace_init/migration.sql +91 -0
  53. package/prisma/migrations/manifest.json +8 -0
@@ -0,0 +1,362 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * trace-sync — the CLI half of @flytedesk/app-kit/trace (DEC-39).
4
+ *
5
+ * This package never generates its own Prisma Client. Instead this CLI copies
6
+ * prisma/fragments/trace.prisma into the *consumer's* own multi-file Prisma
7
+ * schema folder, applies the raw SQL migration directly against the consumer's
8
+ * configured datasource, and tracks what's been applied in a lock file
9
+ * (`.app-kit-trace.lock.json`) written at the consumer project root. The
10
+ * consumer's own `prisma generate` then produces one client that includes these
11
+ * models alongside their own.
12
+ *
13
+ * Subcommands:
14
+ * trace-sync init copy fragment + migration in, run `prisma validate` and
15
+ * `prisma db execute`, write the lock file.
16
+ * trace-sync diff read-only: compare this package's bundled fragment
17
+ * version / migration manifest against the consumer's lock
18
+ * file and report what's pending. Never applies anything.
19
+ * trace-sync apply apply pending migrations + refresh the fragment copy,
20
+ * update the lock file. Does not re-run `prisma validate`
21
+ * against a fresh init — use `init` for first install.
22
+ * trace-sync status read-only report: fragment version, applied migrations,
23
+ * and whether the consumer's copy of the fragment has been
24
+ * hand-edited (checksum mismatch).
25
+ *
26
+ * This is a real, runnable CLI — it shells out to the consumer's local `prisma`
27
+ * via `npx` and touches the consumer's real filesystem — not a stub.
28
+ */
29
+ import { createHash } from "node:crypto";
30
+ import spawn from "cross-spawn";
31
+ import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
32
+ import path from "node:path";
33
+ import { fileURLToPath, pathToFileURL } from "node:url";
34
+ // ---------------------------------------------------------------------------
35
+ // Package-relative paths
36
+ // ---------------------------------------------------------------------------
37
+ /** Root of the installed @flytedesk/app-kit package (two levels up from
38
+ * dist/cli/trace-sync.js — this file, once built). */
39
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
40
+ const fragmentSrcPath = path.join(packageRoot, "prisma", "fragments", "trace.prisma");
41
+ const fragmentMetaPath = path.join(packageRoot, "prisma", "fragments", "trace.meta.json");
42
+ const migrationsSrcDir = path.join(packageRoot, "prisma", "migrations");
43
+ const manifestSrcPath = path.join(migrationsSrcDir, "manifest.json");
44
+ const LOCK_FILE_NAME = ".app-kit-trace.lock.json";
45
+ class TraceSyncError extends Error {
46
+ }
47
+ function fail(message) {
48
+ console.error(`trace-sync: ${message}`);
49
+ process.exitCode = 1;
50
+ throw new TraceSyncError(message);
51
+ }
52
+ function readJson(filePath) {
53
+ return JSON.parse(readFileSync(filePath, "utf8"));
54
+ }
55
+ function sha256(content) {
56
+ return `sha256:${createHash("sha256").update(content).digest("hex")}`;
57
+ }
58
+ function sha256File(filePath) {
59
+ return sha256(readFileSync(filePath));
60
+ }
61
+ function readFragmentMeta() {
62
+ return readJson(fragmentMetaPath);
63
+ }
64
+ function readManifest() {
65
+ return readJson(manifestSrcPath);
66
+ }
67
+ function lockFilePath(consumerRoot) {
68
+ return path.join(consumerRoot, LOCK_FILE_NAME);
69
+ }
70
+ function readLockFile(consumerRoot) {
71
+ const p = lockFilePath(consumerRoot);
72
+ if (!existsSync(p))
73
+ return null;
74
+ return readJson(p);
75
+ }
76
+ function writeLockFile(consumerRoot, lock) {
77
+ writeFileSync(lockFilePath(consumerRoot), `${JSON.stringify(lock, null, 2)}\n`, "utf8");
78
+ }
79
+ // ---------------------------------------------------------------------------
80
+ // Locating the consumer's Prisma schema folder via their prisma.config.ts
81
+ // ---------------------------------------------------------------------------
82
+ const CONSUMER_CONFIG_CANDIDATES = [
83
+ "prisma.config.ts",
84
+ "prisma.config.mts",
85
+ "prisma.config.js",
86
+ "prisma.config.mjs",
87
+ ];
88
+ function findConsumerConfigFile(consumerRoot) {
89
+ for (const candidate of CONSUMER_CONFIG_CANDIDATES) {
90
+ const p = path.join(consumerRoot, candidate);
91
+ if (existsSync(p))
92
+ return p;
93
+ }
94
+ fail(`could not find a prisma.config.{ts,mts,js,mjs} in ${consumerRoot}. ` +
95
+ `trace-sync reads this file to find your Prisma schema folder — it does not assume a path.`);
96
+ }
97
+ /**
98
+ * Best-effort read of the consumer's `schema` setting out of their
99
+ * prisma.config.ts default export. Supports the shapes Prisma's config API has
100
+ * used: a bare string path (file or folder), or `{ path: string }`. Relies on
101
+ * Node's native TypeScript type-stripping (unflagged as of the Node 24 baseline
102
+ * this package requires) to import a .ts file directly — if the consumer's
103
+ * config uses syntax that isn't erasable (enums, namespaces, parameter
104
+ * properties, etc.) this import will fail, and that failure is surfaced as-is
105
+ * rather than swallowed.
106
+ */
107
+ async function loadConsumerSchemaPath(consumerRoot) {
108
+ const configFile = findConsumerConfigFile(consumerRoot);
109
+ let mod;
110
+ try {
111
+ mod = await import(pathToFileURL(configFile).href);
112
+ }
113
+ catch (err) {
114
+ fail(`failed to load ${configFile}: ${err.message}\n` +
115
+ `If this config file uses TypeScript syntax that Node can't type-strip natively ` +
116
+ `(enums, namespaces, decorators, ...), trace-sync can't import it directly.`);
117
+ }
118
+ const config = mod.default ?? mod;
119
+ const schema = config?.schema;
120
+ let schemaPath;
121
+ if (typeof schema === "string") {
122
+ schemaPath = schema;
123
+ }
124
+ else if (schema &&
125
+ typeof schema === "object" &&
126
+ typeof schema.path === "string") {
127
+ schemaPath = schema.path;
128
+ }
129
+ if (!schemaPath) {
130
+ fail(`could not find a "schema" path in ${configFile}'s default export. ` +
131
+ `Expected \`defineConfig({ schema: "prisma/schema" })\` (or a { path } object).`);
132
+ }
133
+ return path.resolve(path.dirname(configFile), schemaPath);
134
+ }
135
+ /** Resolve the folder trace.prisma gets copied into, from the config's schema
136
+ * path (which may point at the folder itself, or at a single schema file
137
+ * inside it, e.g. "prisma/schema.prisma"). */
138
+ function resolveSchemaFolder(schemaPath) {
139
+ if (schemaPath.endsWith(".prisma")) {
140
+ return path.dirname(schemaPath);
141
+ }
142
+ return schemaPath;
143
+ }
144
+ /** The conventional "prisma root" a schema folder sits under, used to locate
145
+ * (or create) the sibling `migrations/` directory. For the common multi-file
146
+ * layout `prisma/schema/*.prisma`, that's `prisma/`; for a flat
147
+ * `prisma/schema.prisma` layout, `resolveSchemaFolder` already returned
148
+ * `prisma/`, so this is a no-op. */
149
+ function resolvePrismaRoot(schemaFolder) {
150
+ return path.basename(schemaFolder) === "schema"
151
+ ? path.dirname(schemaFolder)
152
+ : schemaFolder;
153
+ }
154
+ // ---------------------------------------------------------------------------
155
+ // Shelling out to the consumer's local `prisma`
156
+ // ---------------------------------------------------------------------------
157
+ function runPrisma(args, cwd) {
158
+ // On Windows, npx resolves to npx.cmd, a shell script — node:child_process's
159
+ // spawnSync can't exec it directly (EINVAL) without shell:true, and shell:true
160
+ // with an argument array is an unescaped-concatenation footgun (path arguments
161
+ // here can contain spaces). cross-spawn resolves and correctly quotes .cmd/.bat
162
+ // executables on Windows without going through a shell at all.
163
+ const result = spawn.sync("npx", ["prisma", ...args], { cwd, stdio: "inherit" });
164
+ if (result.error) {
165
+ fail(`failed to run "npx prisma ${args.join(" ")}": ${result.error.message}`);
166
+ }
167
+ if (result.status !== 0) {
168
+ fail(`"npx prisma ${args.join(" ")}" exited with code ${result.status}`);
169
+ }
170
+ }
171
+ // ---------------------------------------------------------------------------
172
+ // File copying
173
+ // ---------------------------------------------------------------------------
174
+ function copyFragment(schemaFolder) {
175
+ mkdirSync(schemaFolder, { recursive: true });
176
+ copyFileSync(fragmentSrcPath, path.join(schemaFolder, "trace.prisma"));
177
+ }
178
+ function copyMigration(migrationId, migrationsDestDir) {
179
+ const srcDir = path.join(migrationsSrcDir, migrationId);
180
+ const destDir = path.join(migrationsDestDir, migrationId);
181
+ mkdirSync(destDir, { recursive: true });
182
+ cpSync(srcDir, destDir, { recursive: true });
183
+ }
184
+ // ---------------------------------------------------------------------------
185
+ // Subcommands
186
+ // ---------------------------------------------------------------------------
187
+ async function cmdInit(consumerRoot) {
188
+ const schemaPath = await loadConsumerSchemaPath(consumerRoot);
189
+ const schemaFolder = resolveSchemaFolder(schemaPath);
190
+ const prismaRoot = resolvePrismaRoot(schemaFolder);
191
+ const migrationsDestDir = path.join(prismaRoot, "migrations");
192
+ console.log(`trace-sync: copying trace.prisma into ${schemaFolder}`);
193
+ copyFragment(schemaFolder);
194
+ console.log(`trace-sync: running "npx prisma validate" against ${consumerRoot}`);
195
+ runPrisma(["validate"], consumerRoot);
196
+ const manifest = readManifest();
197
+ const appliedMigrations = [];
198
+ for (const entry of manifest) {
199
+ console.log(`trace-sync: copying migration ${entry.id} into ${migrationsDestDir}`);
200
+ copyMigration(entry.id, migrationsDestDir);
201
+ const migrationSqlPath = path.join(migrationsDestDir, entry.id, "migration.sql");
202
+ console.log(`trace-sync: applying ${entry.id} via "npx prisma db execute"`);
203
+ runPrisma(["db", "execute", "--file", migrationSqlPath, "--schema", schemaFolder], consumerRoot);
204
+ appliedMigrations.push(entry.id);
205
+ }
206
+ const meta = readFragmentMeta();
207
+ writeLockFile(consumerRoot, {
208
+ fragmentVersion: meta.fragmentVersion,
209
+ fragmentChecksum: sha256File(fragmentSrcPath),
210
+ appliedMigrations,
211
+ lastSyncedAt: new Date().toISOString(),
212
+ });
213
+ console.log(`trace-sync: wrote ${lockFilePath(consumerRoot)}`);
214
+ console.log("trace-sync: init complete.");
215
+ }
216
+ function computePending(consumerRoot) {
217
+ const lock = readLockFile(consumerRoot);
218
+ if (!lock) {
219
+ fail(`no ${LOCK_FILE_NAME} found at ${consumerRoot}. Run "trace-sync init" first.`);
220
+ }
221
+ const meta = readFragmentMeta();
222
+ const manifest = readManifest();
223
+ const applied = new Set(lock.appliedMigrations);
224
+ const pendingMigrations = manifest.filter((entry) => !applied.has(entry.id));
225
+ return {
226
+ fragmentVersionCurrent: meta.fragmentVersion,
227
+ fragmentVersionInstalled: lock.fragmentVersion,
228
+ fragmentVersionChanged: meta.fragmentVersion !== lock.fragmentVersion,
229
+ pendingMigrations,
230
+ };
231
+ }
232
+ function cmdDiff(consumerRoot) {
233
+ const report = computePending(consumerRoot);
234
+ if (report.fragmentVersionChanged) {
235
+ console.log(`fragment version: ${report.fragmentVersionInstalled} -> ${report.fragmentVersionCurrent} (changed)`);
236
+ }
237
+ else {
238
+ console.log(`fragment version: ${report.fragmentVersionCurrent} (up to date)`);
239
+ }
240
+ if (report.pendingMigrations.length === 0) {
241
+ console.log("migrations: up to date, nothing pending.");
242
+ return;
243
+ }
244
+ console.log(`migrations: ${report.pendingMigrations.length} pending:`);
245
+ for (const entry of report.pendingMigrations) {
246
+ console.log(` - ${entry.id} (introduced in fragment ${entry.introducedInFragmentVersion})`);
247
+ }
248
+ console.log('trace-sync diff is read-only. Run "trace-sync apply" to apply these.');
249
+ }
250
+ async function cmdApply(consumerRoot) {
251
+ const report = computePending(consumerRoot);
252
+ const schemaPath = await loadConsumerSchemaPath(consumerRoot);
253
+ const schemaFolder = resolveSchemaFolder(schemaPath);
254
+ const prismaRoot = resolvePrismaRoot(schemaFolder);
255
+ const migrationsDestDir = path.join(prismaRoot, "migrations");
256
+ console.log(`trace-sync: refreshing trace.prisma in ${schemaFolder}`);
257
+ copyFragment(schemaFolder);
258
+ const lock = readLockFile(consumerRoot);
259
+ if (!lock) {
260
+ fail(`no ${LOCK_FILE_NAME} found at ${consumerRoot}. Run "trace-sync init" first.`);
261
+ }
262
+ const appliedMigrations = [...lock.appliedMigrations];
263
+ if (report.pendingMigrations.length === 0) {
264
+ console.log("trace-sync: no pending migrations to apply.");
265
+ }
266
+ for (const entry of report.pendingMigrations) {
267
+ console.log(`trace-sync: copying migration ${entry.id} into ${migrationsDestDir}`);
268
+ copyMigration(entry.id, migrationsDestDir);
269
+ const migrationSqlPath = path.join(migrationsDestDir, entry.id, "migration.sql");
270
+ console.log(`trace-sync: applying ${entry.id} via "npx prisma db execute"`);
271
+ runPrisma(["db", "execute", "--file", migrationSqlPath, "--schema", schemaFolder], consumerRoot);
272
+ appliedMigrations.push(entry.id);
273
+ }
274
+ const meta = readFragmentMeta();
275
+ writeLockFile(consumerRoot, {
276
+ fragmentVersion: meta.fragmentVersion,
277
+ fragmentChecksum: sha256File(fragmentSrcPath),
278
+ appliedMigrations,
279
+ lastSyncedAt: new Date().toISOString(),
280
+ });
281
+ console.log(`trace-sync: wrote ${lockFilePath(consumerRoot)}`);
282
+ console.log("trace-sync: apply complete.");
283
+ }
284
+ async function cmdStatus(consumerRoot) {
285
+ const lock = readLockFile(consumerRoot);
286
+ if (!lock) {
287
+ console.log(`no ${LOCK_FILE_NAME} found at ${consumerRoot}. Not yet initialized — run "trace-sync init".`);
288
+ return;
289
+ }
290
+ console.log(`fragment version (installed): ${lock.fragmentVersion}`);
291
+ console.log(`fragment version (package): ${readFragmentMeta().fragmentVersion}`);
292
+ console.log(`last synced: ${lock.lastSyncedAt}`);
293
+ console.log(`applied migrations (${lock.appliedMigrations.length}):`);
294
+ for (const id of lock.appliedMigrations) {
295
+ console.log(` - ${id}`);
296
+ }
297
+ try {
298
+ const schemaPath = await loadConsumerSchemaPath(consumerRoot);
299
+ const schemaFolder = resolveSchemaFolder(schemaPath);
300
+ const copiedFragmentPath = path.join(schemaFolder, "trace.prisma");
301
+ if (!existsSync(copiedFragmentPath)) {
302
+ console.log(`fragment copy: MISSING at ${copiedFragmentPath}`);
303
+ return;
304
+ }
305
+ const installedChecksum = sha256File(copiedFragmentPath);
306
+ if (installedChecksum === lock.fragmentChecksum) {
307
+ console.log("fragment copy: unmodified");
308
+ }
309
+ else {
310
+ console.log(`fragment copy: CHECKSUM MISMATCH — ${copiedFragmentPath} has been hand-edited ` +
311
+ `(or is out of sync with the lock file)`);
312
+ }
313
+ }
314
+ catch (err) {
315
+ console.log(`fragment copy: could not verify (${err.message})`);
316
+ }
317
+ }
318
+ // ---------------------------------------------------------------------------
319
+ // Entry point
320
+ // ---------------------------------------------------------------------------
321
+ const SUBCOMMANDS = ["init", "diff", "apply", "status"];
322
+ function isSubcommand(value) {
323
+ return SUBCOMMANDS.includes(value);
324
+ }
325
+ export async function main(argv) {
326
+ const [subcommand, ...rest] = argv;
327
+ if (!isSubcommand(subcommand)) {
328
+ console.error(`usage: trace-sync <${SUBCOMMANDS.join("|")}>`);
329
+ process.exitCode = 1;
330
+ return;
331
+ }
332
+ // Reserved for a future --cwd override; today trace-sync always operates on
333
+ // the directory it's invoked from, which is how `npx trace-sync ...` /
334
+ // an npm script in the consumer's own package.json will run it.
335
+ void rest;
336
+ const consumerRoot = process.cwd();
337
+ switch (subcommand) {
338
+ case "init":
339
+ await cmdInit(consumerRoot);
340
+ break;
341
+ case "diff":
342
+ cmdDiff(consumerRoot);
343
+ break;
344
+ case "apply":
345
+ await cmdApply(consumerRoot);
346
+ break;
347
+ case "status":
348
+ await cmdStatus(consumerRoot);
349
+ break;
350
+ }
351
+ }
352
+ const isMainModule = process.argv[1] !== undefined &&
353
+ import.meta.url === pathToFileURL(process.argv[1]).href;
354
+ if (isMainModule) {
355
+ main(process.argv.slice(2)).catch((err) => {
356
+ if (!(err instanceof TraceSyncError)) {
357
+ console.error(err);
358
+ }
359
+ process.exitCode = process.exitCode ?? 1;
360
+ });
361
+ }
362
+ //# sourceMappingURL=trace-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace-sync.js","sourceRoot":"","sources":["../../src/cli/trace-sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * In-memory-by-default Fastify rate-limit plugin (DEC-48). See ./plugin.ts
3
+ * for the scoping model and ./store.ts for the storage seam.
4
+ */
5
+ export { rateLimit, type RateLimitOptions } from "./plugin.js";
6
+ export { MemoryRateLimitStore, type RateLimitStore, type RateLimitStoreResult, } from "./store.js";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * In-memory-by-default Fastify rate-limit plugin (DEC-48). See ./plugin.ts
3
+ * for the scoping model and ./store.ts for the storage seam.
4
+ */
5
+ export { rateLimit } from "./plugin.js";
6
+ export { MemoryRateLimitStore, } from "./store.js";
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rate-limit/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAyB,MAAM,aAAa,CAAC;AAC/D,OAAO,EACL,oBAAoB,GAGrB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { FastifyPluginAsync, FastifyReply, FastifyRequest } from "fastify";
2
+ export interface RateLimitOptions {
3
+ /** Length of the rate-limit window in ms. Default 60_000 (1 minute). */
4
+ windowMs?: number;
5
+ /** Max requests allowed per key within `windowMs`. Default 100. */
6
+ max?: number;
7
+ /**
8
+ * Derives the bucket key for a request. Default: `request.ip`. Pass a
9
+ * custom function for e.g. per-user limiting once `request.authUser`
10
+ * exists (from the /auth module) — a common choice is
11
+ * `(request) => request.authUser?.id ?? request.ip`.
12
+ */
13
+ keyGenerator?: (request: FastifyRequest) => string;
14
+ /** Return true to bypass rate limiting entirely for a request. Default: never skip. */
15
+ skip?: (request: FastifyRequest) => boolean;
16
+ /**
17
+ * Called instead of the default response when a request exceeds its
18
+ * limit. Responsible for sending the reply itself. Default: sends a 429
19
+ * with `Retry-After` and a JSON body (see below).
20
+ */
21
+ onLimitExceeded?: (request: FastifyRequest, reply: FastifyReply) => void;
22
+ }
23
+ export declare const rateLimit: FastifyPluginAsync<RateLimitOptions>;
@@ -0,0 +1,75 @@
1
+ import fp from "fastify-plugin";
2
+ import { MemoryRateLimitStore } from "./store.js";
3
+ const DEFAULT_WINDOW_MS = 60_000;
4
+ const DEFAULT_MAX = 100;
5
+ function defaultKeyGenerator(request) {
6
+ return request.ip;
7
+ }
8
+ function neverSkip() {
9
+ return false;
10
+ }
11
+ /**
12
+ * In-memory-by-default rate-limit plugin (DEC-48). No new infra (no
13
+ * Redis/Memorystore) — consistent with this platform's real traffic volume
14
+ * (~20 total users, bursty, idle overnight; DEC-37), where a per-instance
15
+ * counter is more than enough headroom.
16
+ *
17
+ * Scoping: this plugin is wrapped with `fastify-plugin` so that, when
18
+ * registered, it adds its hook to the *current* encapsulation context
19
+ * instead of an isolated child context. That means:
20
+ * - `fastify.register(rateLimit, opts)` at the top level applies globally.
21
+ * - Registering it inside a normal (non-fp) sub-plugin/prefixed scope
22
+ * applies it only to routes in that scope, via Fastify's own
23
+ * encapsulation — e.g.:
24
+ *
25
+ * fastify.register(async (scoped) => {
26
+ * await scoped.register(rateLimit, { max: 5 });
27
+ * scoped.get("/expensive", handler);
28
+ * }, { prefix: "/api" });
29
+ *
30
+ * `/expensive` is limited; routes registered outside that scope are not.
31
+ * This is why the plugin does not implement its own per-route config —
32
+ * Fastify's encapsulation already provides it, and building a parallel
33
+ * mechanism would just be two ways to do the same thing.
34
+ */
35
+ const rateLimitPlugin = async (fastify, opts) => {
36
+ const windowMs = opts.windowMs ?? DEFAULT_WINDOW_MS;
37
+ const max = opts.max ?? DEFAULT_MAX;
38
+ const keyGenerator = opts.keyGenerator ?? defaultKeyGenerator;
39
+ const skip = opts.skip ?? neverSkip;
40
+ const onLimitExceeded = opts.onLimitExceeded;
41
+ // Swapping this for a Postgres- or Redis-backed RateLimitStore later is
42
+ // the only line that needs to change — the rest of the plugin only knows
43
+ // about the RateLimitStore interface.
44
+ const store = new MemoryRateLimitStore();
45
+ fastify.addHook("onClose", (_instance, done) => {
46
+ store.close?.();
47
+ done();
48
+ });
49
+ fastify.addHook("onRequest", async (request, reply) => {
50
+ if (skip(request)) {
51
+ return;
52
+ }
53
+ const key = keyGenerator(request);
54
+ const { count, resetAt } = store.increment(key, windowMs);
55
+ reply.header("RateLimit-Limit", max);
56
+ reply.header("RateLimit-Remaining", Math.max(0, max - count));
57
+ reply.header("RateLimit-Reset", Math.ceil((resetAt - Date.now()) / 1000));
58
+ if (count <= max) {
59
+ return;
60
+ }
61
+ if (onLimitExceeded) {
62
+ onLimitExceeded(request, reply);
63
+ return;
64
+ }
65
+ const retryAfterSeconds = Math.max(1, Math.ceil((resetAt - Date.now()) / 1000));
66
+ reply
67
+ .code(429)
68
+ .header("Retry-After", retryAfterSeconds)
69
+ .send({ error: "Too Many Requests", code: "rate_limited" });
70
+ });
71
+ };
72
+ export const rateLimit = fp(rateLimitPlugin, {
73
+ name: "@flytedesk/app-kit/rate-limit",
74
+ });
75
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/rate-limit/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAEhC,OAAO,EAAE,oBAAoB,EAAuB,MAAM,YAAY,CAAC;AAwBvE,MAAM,iBAAiB,GAAG,MAAM,CAAC;AACjC,MAAM,WAAW,GAAG,GAAG,CAAC;AAExB,SAAS,mBAAmB,CAAC,OAAuB;IAClD,OAAO,OAAO,CAAC,EAAE,CAAC;AACpB,CAAC;AAED,SAAS,SAAS;IAChB,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,eAAe,GAAyC,KAAK,EACjE,OAAO,EACP,IAAI,EACJ,EAAE;IACF,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,iBAAiB,CAAC;IACpD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,WAAW,CAAC;IACpC,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,mBAAmB,CAAC;IAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,SAAS,CAAC;IACpC,MAAM,eAAe,GAAG,IAAI,CAAC,eAAe,CAAC;IAE7C,wEAAwE;IACxE,yEAAyE;IACzE,sCAAsC;IACtC,MAAM,KAAK,GAAmB,IAAI,oBAAoB,EAAE,CAAC;IAEzD,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,SAAS,EAAE,IAAI,EAAE,EAAE;QAC7C,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;QAChB,IAAI,EAAE,CAAC;IACT,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QACpD,IAAI,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QAED,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;QAClC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAE1D,KAAK,CAAC,MAAM,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;QACrC,KAAK,CAAC,MAAM,CAAC,qBAAqB,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC;QAC9D,KAAK,CAAC,MAAM,CAAC,iBAAiB,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;QAE1E,IAAI,KAAK,IAAI,GAAG,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QAED,IAAI,eAAe,EAAE,CAAC;YACpB,eAAe,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YAChC,OAAO;QACT,CAAC;QAED,MAAM,iBAAiB,GAAG,IAAI,CAAC,GAAG,CAChC,CAAC,EACD,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CACzC,CAAC;QACF,KAAK;aACF,IAAI,CAAC,GAAG,CAAC;aACT,MAAM,CAAC,aAAa,EAAE,iBAAiB,CAAC;aACxC,IAAI,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC,eAAe,EAAE;IAC3C,IAAI,EAAE,+BAA+B;CACtC,CAAC,CAAC"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Storage backend for the rate limiter, kept behind a narrow interface so the
3
+ * default in-memory implementation can be swapped for a Postgres- or
4
+ * Redis-backed store later (per DEC-48: per-instance memory is a deliberate
5
+ * v1 choice, not a permanent ceiling) without touching the plugin's public
6
+ * API in ./plugin.ts.
7
+ */
8
+ export interface RateLimitStoreResult {
9
+ /** Number of requests recorded for this key in the current window, including this one. */
10
+ count: number;
11
+ /** Epoch ms at which the current window ends and the count resets. */
12
+ resetAt: number;
13
+ }
14
+ export interface RateLimitStore {
15
+ /**
16
+ * Record one request for `key` and return the up-to-date count for the
17
+ * window it falls in, opening a fresh window if none is active.
18
+ */
19
+ increment(key: string, windowMs: number): RateLimitStoreResult;
20
+ /** Release any resources held by the store (e.g. sweep timers). */
21
+ close?(): void;
22
+ }
23
+ /**
24
+ * Fixed-window counter store: each key gets a counter that resets `windowMs`
25
+ * after its first request in the window, rather than a true sliding window
26
+ * or token bucket. Chosen over those because:
27
+ * - the shape (count + resetAt per key) is trivial to reason about, test
28
+ * with fake timers, and eventually reimplement against Postgres/Redis
29
+ * with a single row per key (an `INCR`-style upsert);
30
+ * - a true sliding window needs a timestamp log (or a weighted two-window
31
+ * approximation) purely to smooth boundary bursts — at this platform's
32
+ * real traffic volume (~20 total users, bursty, idle overnight, per
33
+ * DEC-37) that precision buys nothing and isn't worth the extra state;
34
+ * - a token bucket adds a refill-rate concept for the same "protect
35
+ * against bursts" goal this traffic profile doesn't need.
36
+ *
37
+ * Expiry is primarily lazy: `increment` checks each key's `resetAt` on
38
+ * access and starts a fresh window once it has passed, so a key is never
39
+ * read with stale data. The periodic sweep below is only a backstop to
40
+ * reclaim memory for keys that stop being requested entirely (so they
41
+ * aren't touched by the lazy check again) — it is not required for
42
+ * correctness.
43
+ */
44
+ export declare class MemoryRateLimitStore implements RateLimitStore {
45
+ private readonly entries;
46
+ private readonly sweepTimer;
47
+ constructor(sweepIntervalMs?: number);
48
+ increment(key: string, windowMs: number): RateLimitStoreResult;
49
+ private sweep;
50
+ close(): void;
51
+ }
@@ -0,0 +1,59 @@
1
+ // Backstop sweep interval for reclaiming memory from keys that are never
2
+ // requested again (e.g. a one-off client IP). Kept coarse and infrequent
3
+ // since this platform's real traffic (~20 users, bursty, idle overnight —
4
+ // DEC-37) never produces enough distinct keys for the Map to matter between
5
+ // sweeps.
6
+ const DEFAULT_SWEEP_INTERVAL_MS = 5 * 60_000;
7
+ /**
8
+ * Fixed-window counter store: each key gets a counter that resets `windowMs`
9
+ * after its first request in the window, rather than a true sliding window
10
+ * or token bucket. Chosen over those because:
11
+ * - the shape (count + resetAt per key) is trivial to reason about, test
12
+ * with fake timers, and eventually reimplement against Postgres/Redis
13
+ * with a single row per key (an `INCR`-style upsert);
14
+ * - a true sliding window needs a timestamp log (or a weighted two-window
15
+ * approximation) purely to smooth boundary bursts — at this platform's
16
+ * real traffic volume (~20 total users, bursty, idle overnight, per
17
+ * DEC-37) that precision buys nothing and isn't worth the extra state;
18
+ * - a token bucket adds a refill-rate concept for the same "protect
19
+ * against bursts" goal this traffic profile doesn't need.
20
+ *
21
+ * Expiry is primarily lazy: `increment` checks each key's `resetAt` on
22
+ * access and starts a fresh window once it has passed, so a key is never
23
+ * read with stale data. The periodic sweep below is only a backstop to
24
+ * reclaim memory for keys that stop being requested entirely (so they
25
+ * aren't touched by the lazy check again) — it is not required for
26
+ * correctness.
27
+ */
28
+ export class MemoryRateLimitStore {
29
+ entries = new Map();
30
+ sweepTimer;
31
+ constructor(sweepIntervalMs = DEFAULT_SWEEP_INTERVAL_MS) {
32
+ this.sweepTimer = setInterval(() => this.sweep(), sweepIntervalMs);
33
+ // Never keep the process (or a test runner) alive just for the sweep.
34
+ this.sweepTimer.unref();
35
+ }
36
+ increment(key, windowMs) {
37
+ const now = Date.now();
38
+ const existing = this.entries.get(key);
39
+ if (existing && existing.resetAt > now) {
40
+ existing.count += 1;
41
+ return { count: existing.count, resetAt: existing.resetAt };
42
+ }
43
+ const entry = { count: 1, resetAt: now + windowMs };
44
+ this.entries.set(key, entry);
45
+ return { count: entry.count, resetAt: entry.resetAt };
46
+ }
47
+ sweep() {
48
+ const now = Date.now();
49
+ for (const [key, entry] of this.entries) {
50
+ if (entry.resetAt <= now) {
51
+ this.entries.delete(key);
52
+ }
53
+ }
54
+ }
55
+ close() {
56
+ clearInterval(this.sweepTimer);
57
+ }
58
+ }
59
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/rate-limit/store.ts"],"names":[],"mappings":"AA6BA,yEAAyE;AACzE,yEAAyE;AACzE,0EAA0E;AAC1E,4EAA4E;AAC5E,UAAU;AACV,MAAM,yBAAyB,GAAG,CAAC,GAAG,MAAM,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,OAAO,oBAAoB;IACd,OAAO,GAAG,IAAI,GAAG,EAAiB,CAAC;IACnC,UAAU,CAAiC;IAE5D,YAAY,kBAA0B,yBAAyB;QAC7D,IAAI,CAAC,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,eAAe,CAAC,CAAC;QACnE,sEAAsE;QACtE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;IAC1B,CAAC;IAED,SAAS,CAAC,GAAW,EAAE,QAAgB;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAEvC,IAAI,QAAQ,IAAI,QAAQ,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC;YACvC,QAAQ,CAAC,KAAK,IAAI,CAAC,CAAC;YACpB,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,CAAC;QAC9D,CAAC;QAED,MAAM,KAAK,GAAU,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,QAAQ,EAAE,CAAC;QAC3D,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC7B,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;IACxD,CAAC;IAEO,KAAK;QACX,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACxC,IAAI,KAAK,CAAC,OAAO,IAAI,GAAG,EAAE,CAAC;gBACzB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK;QACH,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACjC,CAAC;CACF"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * createTraceClient(prisma) — bundles the query helpers and captureError into one
3
+ * object bound to the consumer's PrismaClient (or any TracePrismaClient-shaped
4
+ * object), so call sites don't have to thread `prisma` through every call.
5
+ */
6
+ import { type CaptureErrorOptions } from "./errors.js";
7
+ import { type NewApiCallLogInput, type NewModelAuditInput, type NewTraceLinkInput, type NewTraceNodeInput } from "./queries.js";
8
+ import type { ApiCallLogRow, ModelAuditRow, TraceLinkRow, TraceNodeRow, TracePrismaClient } from "./types.js";
9
+ export interface TraceClient {
10
+ insertTraceNode(input: NewTraceNodeInput): Promise<TraceNodeRow>;
11
+ setTraceNodeRoot(id: bigint, rootId: bigint): Promise<TraceNodeRow>;
12
+ getTraceChainForRoot(rootId: bigint): Promise<TraceNodeRow[]>;
13
+ insertModelAudit(input: NewModelAuditInput): Promise<ModelAuditRow>;
14
+ getAuditHistoryForModel(modelType: string, modelId: string, limit?: number): Promise<ModelAuditRow[]>;
15
+ insertApiCallLog(input: NewApiCallLogInput): Promise<ApiCallLogRow>;
16
+ getRecentApiCallsForRoute(route: string, sinceMinutes?: number): Promise<ApiCallLogRow[]>;
17
+ insertTraceLink(input: NewTraceLinkInput): Promise<TraceLinkRow>;
18
+ captureError(options: CaptureErrorOptions): Promise<TraceNodeRow>;
19
+ }
20
+ export declare function createTraceClient(prisma: TracePrismaClient): TraceClient;