@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.
- package/dist/auth/guards.d.ts +5 -0
- package/dist/auth/guards.js +28 -0
- package/dist/auth/guards.js.map +1 -0
- package/dist/auth/index.d.ts +33 -5
- package/dist/auth/index.js +31 -5
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/oidc-client.d.ts +47 -0
- package/dist/auth/oidc-client.js +162 -0
- package/dist/auth/oidc-client.js.map +1 -0
- package/dist/auth/pkce.d.ts +8 -0
- package/dist/auth/pkce.js +12 -0
- package/dist/auth/pkce.js.map +1 -0
- package/dist/auth/plugin.d.ts +8 -0
- package/dist/auth/plugin.js +362 -0
- package/dist/auth/plugin.js.map +1 -0
- package/dist/auth/tokens.d.ts +24 -0
- package/dist/auth/tokens.js +35 -0
- package/dist/auth/tokens.js.map +1 -0
- package/dist/auth/types.d.ts +156 -0
- package/dist/auth/types.js +2 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/cli/trace-sync.d.ts +2 -0
- package/dist/cli/trace-sync.js +362 -0
- package/dist/cli/trace-sync.js.map +1 -0
- package/dist/rate-limit/index.d.ts +6 -0
- package/dist/rate-limit/index.js +7 -0
- package/dist/rate-limit/index.js.map +1 -0
- package/dist/rate-limit/plugin.d.ts +23 -0
- package/dist/rate-limit/plugin.js +75 -0
- package/dist/rate-limit/plugin.js.map +1 -0
- package/dist/rate-limit/store.d.ts +51 -0
- package/dist/rate-limit/store.js +59 -0
- package/dist/rate-limit/store.js.map +1 -0
- package/dist/trace/client.d.ts +20 -0
- package/dist/trace/client.js +21 -0
- package/dist/trace/client.js.map +1 -0
- package/dist/trace/errors.d.ts +31 -0
- package/dist/trace/errors.js +37 -0
- package/dist/trace/errors.js.map +1 -0
- package/dist/trace/index.d.ts +15 -4
- package/dist/trace/index.js +3 -6
- package/dist/trace/index.js.map +1 -1
- package/dist/trace/queries.d.ts +59 -0
- package/dist/trace/queries.js +147 -0
- package/dist/trace/queries.js.map +1 -0
- package/dist/trace/types.d.ts +72 -0
- package/dist/trace/types.js +14 -0
- package/dist/trace/types.js.map +1 -0
- package/package.json +54 -8
- package/prisma/fragments/trace.meta.json +4 -0
- package/prisma/fragments/trace.prisma +87 -0
- package/prisma/migrations/0001_app_kit_trace_init/migration.sql +91 -0
- 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;
|