@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,21 @@
|
|
|
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 { captureError } from "./errors.js";
|
|
7
|
+
import { getAuditHistoryForModel, getRecentApiCallsForRoute, getTraceChainForRoot, insertApiCallLog, insertModelAudit, insertTraceLink, insertTraceNode, setTraceNodeRoot, } from "./queries.js";
|
|
8
|
+
export function createTraceClient(prisma) {
|
|
9
|
+
return {
|
|
10
|
+
insertTraceNode: (input) => insertTraceNode(prisma, input),
|
|
11
|
+
setTraceNodeRoot: (id, rootId) => setTraceNodeRoot(prisma, id, rootId),
|
|
12
|
+
getTraceChainForRoot: (rootId) => getTraceChainForRoot(prisma, rootId),
|
|
13
|
+
insertModelAudit: (input) => insertModelAudit(prisma, input),
|
|
14
|
+
getAuditHistoryForModel: (modelType, modelId, limit) => getAuditHistoryForModel(prisma, modelType, modelId, limit),
|
|
15
|
+
insertApiCallLog: (input) => insertApiCallLog(prisma, input),
|
|
16
|
+
getRecentApiCallsForRoute: (route, sinceMinutes) => getRecentApiCallsForRoute(prisma, route, sinceMinutes),
|
|
17
|
+
insertTraceLink: (input) => insertTraceLink(prisma, input),
|
|
18
|
+
captureError: (options) => captureError(prisma, options),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/trace/client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,YAAY,EAA4B,MAAM,aAAa,CAAC;AACrE,OAAO,EACL,uBAAuB,EACvB,yBAAyB,EACzB,oBAAoB,EACpB,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,gBAAgB,GAKjB,MAAM,cAAc,CAAC;AA4BtB,MAAM,UAAU,iBAAiB,CAAC,MAAyB;IACzD,OAAO;QACL,eAAe,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,eAAe,CAAC,MAAM,EAAE,KAAK,CAAC;QAC1D,gBAAgB,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,gBAAgB,CAAC,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC;QACtE,oBAAoB,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC;QACtE,gBAAgB,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,gBAAgB,CAAC,MAAM,EAAE,KAAK,CAAC;QAC5D,uBAAuB,EAAE,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CACrD,uBAAuB,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC;QAC5D,gBAAgB,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,gBAAgB,CAAC,MAAM,EAAE,KAAK,CAAC;QAC5D,yBAAyB,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,EAAE,CACjD,yBAAyB,CAAC,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC;QACxD,eAAe,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,eAAe,CAAC,MAAM,EAAE,KAAK,CAAC;QAC1D,YAAY,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC;KACzD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { TraceNodeRow, TracePrismaClient } from "./types.js";
|
|
2
|
+
export interface CapturedErrorPayload {
|
|
3
|
+
message: string;
|
|
4
|
+
stack: string | null;
|
|
5
|
+
context: unknown;
|
|
6
|
+
}
|
|
7
|
+
export interface TraceErrorAlertPayload {
|
|
8
|
+
traceNode: TraceNodeRow;
|
|
9
|
+
error: Error;
|
|
10
|
+
actorId?: string | undefined;
|
|
11
|
+
route?: string | undefined;
|
|
12
|
+
}
|
|
13
|
+
export type TraceErrorAlertHook = (payload: TraceErrorAlertPayload) => Promise<void>;
|
|
14
|
+
export declare const defaultTraceErrorAlertHook: TraceErrorAlertHook;
|
|
15
|
+
export interface CaptureErrorOptions {
|
|
16
|
+
error: Error;
|
|
17
|
+
actorId?: string;
|
|
18
|
+
route?: string;
|
|
19
|
+
/** Existing trace root to hang this error node off of. Omit to make the error
|
|
20
|
+
* node self-rooted (`root_id = id`) — e.g. an uncaught exception with no
|
|
21
|
+
* in-flight trace context. */
|
|
22
|
+
rootId?: bigint;
|
|
23
|
+
/** Arbitrary structured context to store alongside the error (request body,
|
|
24
|
+
* job payload, ids, ...). Stored as-is in `request_data.context`. */
|
|
25
|
+
context?: unknown;
|
|
26
|
+
/** Defaults to logging to console.error. The real Slack-webhook (or other
|
|
27
|
+
* alerting) wiring is consumer-specific and lives outside this module —
|
|
28
|
+
* this is just the injectable, typed seam for it. */
|
|
29
|
+
alertHook?: TraceErrorAlertHook;
|
|
30
|
+
}
|
|
31
|
+
export declare function captureError(prisma: TracePrismaClient, options: CaptureErrorOptions): Promise<TraceNodeRow>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error capture (DEC-49): folded into trace rather than shipped as a separate
|
|
3
|
+
* module. An `ERROR`-kind TraceNode is the record; the alert hook is the seam for
|
|
4
|
+
* wiring in wherever the consumer wants notifications (Slack, PagerDuty, email,
|
|
5
|
+
* ...) — that wiring is consumer-specific and out of scope for this module, so the
|
|
6
|
+
* default hook just logs, and consumers pass their own `alertHook`.
|
|
7
|
+
*/
|
|
8
|
+
import { insertTraceNode, setTraceNodeRoot } from "./queries.js";
|
|
9
|
+
export const defaultTraceErrorAlertHook = async (payload) => {
|
|
10
|
+
console.error(`[app-kit/trace] captured error${payload.route ? ` on ${payload.route}` : ""}:`, payload.error);
|
|
11
|
+
};
|
|
12
|
+
export async function captureError(prisma, options) {
|
|
13
|
+
const { error, actorId, route, context, alertHook = defaultTraceErrorAlertHook, } = options;
|
|
14
|
+
const requestData = {
|
|
15
|
+
message: error.message,
|
|
16
|
+
stack: error.stack ?? null,
|
|
17
|
+
context: context ?? null,
|
|
18
|
+
};
|
|
19
|
+
let node = await insertTraceNode(prisma, {
|
|
20
|
+
// rootId is NOT NULL on TraceNode; when the caller has no root to attach to,
|
|
21
|
+
// insert self-referentially rooted below once the id is known.
|
|
22
|
+
rootId: options.rootId ?? 0n,
|
|
23
|
+
parentId: null,
|
|
24
|
+
kind: "ERROR",
|
|
25
|
+
actorId: actorId ?? null,
|
|
26
|
+
route: route ?? null,
|
|
27
|
+
requestData,
|
|
28
|
+
responseData: null,
|
|
29
|
+
statusCode: null,
|
|
30
|
+
});
|
|
31
|
+
if (options.rootId === undefined) {
|
|
32
|
+
node = await setTraceNodeRoot(prisma, node.id, node.id);
|
|
33
|
+
}
|
|
34
|
+
await alertHook({ traceNode: node, error, actorId, route });
|
|
35
|
+
return node;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/trace/errors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAoBjE,MAAM,CAAC,MAAM,0BAA0B,GAAwB,KAAK,EAClE,OAAO,EACP,EAAE;IACF,OAAO,CAAC,KAAK,CACX,iCAAiC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAC/E,OAAO,CAAC,KAAK,CACd,CAAC;AACJ,CAAC,CAAC;AAmBF,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,MAAyB,EACzB,OAA4B;IAE5B,MAAM,EACJ,KAAK,EACL,OAAO,EACP,KAAK,EACL,OAAO,EACP,SAAS,GAAG,0BAA0B,GACvC,GAAG,OAAO,CAAC;IAEZ,MAAM,WAAW,GAAyB;QACxC,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,IAAI;QAC1B,OAAO,EAAE,OAAO,IAAI,IAAI;KACzB,CAAC;IAEF,IAAI,IAAI,GAAG,MAAM,eAAe,CAAC,MAAM,EAAE;QACvC,6EAA6E;QAC7E,+DAA+D;QAC/D,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,EAAE;QAC5B,QAAQ,EAAE,IAAI;QACd,IAAI,EAAE,OAAO;QACb,OAAO,EAAE,OAAO,IAAI,IAAI;QACxB,KAAK,EAAE,KAAK,IAAI,IAAI;QACpB,WAAW;QACX,YAAY,EAAE,IAAI;QAClB,UAAU,EAAE,IAAI;KACjB,CAAC,CAAC;IAEH,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QACjC,IAAI,GAAG,MAAM,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,SAAS,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IAE5D,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/trace/index.d.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Postgres-native trace/audit layer
|
|
3
|
-
* DEC-
|
|
4
|
-
* not a generated Prisma Client — see
|
|
2
|
+
* Postgres-native trace/audit layer (DEC-16, DEC-22, DEC-26, DEC-31, DEC-39,
|
|
3
|
+
* DEC-49). Ships Prisma schema fragments + raw SQL migrations + a `trace-sync`
|
|
4
|
+
* CLI, not a generated Prisma Client — see prisma/fragments/trace.prisma and
|
|
5
|
+
* src/cli/trace-sync.ts for why and how.
|
|
6
|
+
*
|
|
7
|
+
* Consumers bring their own generated PrismaClient (after running
|
|
8
|
+
* `trace-sync init`) and get a bound query surface from createTraceClient, or use
|
|
9
|
+
* the individual query helpers directly.
|
|
5
10
|
*/
|
|
6
|
-
export
|
|
11
|
+
export type { TracePrismaClient, TraceNodeKind, TraceNodeRow, ModelAuditRow, ApiCallLogRow, TraceLinkRow, } from "./types.js";
|
|
12
|
+
export { insertTraceNode, setTraceNodeRoot, getTraceChainForRoot, insertModelAudit, getAuditHistoryForModel, insertApiCallLog, getRecentApiCallsForRoute, insertTraceLink, } from "./queries.js";
|
|
13
|
+
export type { NewTraceNodeInput, NewModelAuditInput, NewApiCallLogInput, NewTraceLinkInput, } from "./queries.js";
|
|
14
|
+
export { captureError, defaultTraceErrorAlertHook } from "./errors.js";
|
|
15
|
+
export type { CaptureErrorOptions, CapturedErrorPayload, TraceErrorAlertHook, TraceErrorAlertPayload, } from "./errors.js";
|
|
16
|
+
export { createTraceClient } from "./client.js";
|
|
17
|
+
export type { TraceClient } from "./client.js";
|
package/dist/trace/index.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* not a generated Prisma Client — see DEC-31 for why.
|
|
5
|
-
*/
|
|
6
|
-
export const APP_KIT_TRACE_PLACEHOLDER = true;
|
|
1
|
+
export { insertTraceNode, setTraceNodeRoot, getTraceChainForRoot, insertModelAudit, getAuditHistoryForModel, insertApiCallLog, getRecentApiCallsForRoute, insertTraceLink, } from "./queries.js";
|
|
2
|
+
export { captureError, defaultTraceErrorAlertHook } from "./errors.js";
|
|
3
|
+
export { createTraceClient } from "./client.js";
|
|
7
4
|
//# sourceMappingURL=index.js.map
|
package/dist/trace/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/trace/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/trace/index.ts"],"names":[],"mappings":"AAmBA,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,gBAAgB,EAChB,uBAAuB,EACvB,gBAAgB,EAChB,yBAAyB,EACzB,eAAe,GAChB,MAAM,cAAc,CAAC;AAQtB,OAAO,EAAE,YAAY,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAQvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { ApiCallLogRow, ModelAuditRow, TraceLinkRow, TraceNodeRow, TracePrismaClient } from "./types.js";
|
|
2
|
+
export interface NewTraceNodeInput {
|
|
3
|
+
externalId?: string;
|
|
4
|
+
parentId?: bigint | null;
|
|
5
|
+
rootId: bigint;
|
|
6
|
+
kind: string;
|
|
7
|
+
actorId?: string | null;
|
|
8
|
+
route?: string | null;
|
|
9
|
+
requestData?: unknown;
|
|
10
|
+
responseData?: unknown;
|
|
11
|
+
statusCode?: number | null;
|
|
12
|
+
startedAt?: Date;
|
|
13
|
+
endedAt?: Date | null;
|
|
14
|
+
durationMs?: number | null;
|
|
15
|
+
}
|
|
16
|
+
export declare function insertTraceNode(prisma: TracePrismaClient, input: NewTraceNodeInput): Promise<TraceNodeRow>;
|
|
17
|
+
/**
|
|
18
|
+
* Point an already-inserted TraceNode's `root_id` at a (possibly different) id.
|
|
19
|
+
* Used by src/trace/errors.ts to make a standalone captured error self-rooted
|
|
20
|
+
* (`root_id = id`) when the caller has no existing trace root to hang it off of —
|
|
21
|
+
* `TraceNode.rootId` is `NOT NULL`, so an orphan error always needs *some* root.
|
|
22
|
+
*/
|
|
23
|
+
export declare function setTraceNodeRoot(prisma: TracePrismaClient, id: bigint, rootId: bigint): Promise<TraceNodeRow>;
|
|
24
|
+
export declare function getTraceChainForRoot(prisma: TracePrismaClient, rootId: bigint): Promise<TraceNodeRow[]>;
|
|
25
|
+
export interface NewModelAuditInput {
|
|
26
|
+
traceNodeId?: bigint | null;
|
|
27
|
+
modelType: string;
|
|
28
|
+
modelId: string;
|
|
29
|
+
event: string;
|
|
30
|
+
actorId?: string | null;
|
|
31
|
+
oldValues?: unknown;
|
|
32
|
+
newValues?: unknown;
|
|
33
|
+
createdAt?: Date;
|
|
34
|
+
}
|
|
35
|
+
export declare function insertModelAudit(prisma: TracePrismaClient, input: NewModelAuditInput): Promise<ModelAuditRow>;
|
|
36
|
+
export declare function getAuditHistoryForModel(prisma: TracePrismaClient, modelType: string, modelId: string, limit?: number): Promise<ModelAuditRow[]>;
|
|
37
|
+
export interface NewApiCallLogInput {
|
|
38
|
+
traceNodeId?: bigint | null;
|
|
39
|
+
route: string;
|
|
40
|
+
method: string;
|
|
41
|
+
statusCode?: number | null;
|
|
42
|
+
actorId?: string | null;
|
|
43
|
+
durationMs?: number | null;
|
|
44
|
+
createdAt?: Date;
|
|
45
|
+
}
|
|
46
|
+
export declare function insertApiCallLog(prisma: TracePrismaClient, input: NewApiCallLogInput): Promise<ApiCallLogRow>;
|
|
47
|
+
export declare function getRecentApiCallsForRoute(prisma: TracePrismaClient, route: string, sinceMinutes?: number): Promise<ApiCallLogRow[]>;
|
|
48
|
+
export interface NewTraceLinkInput {
|
|
49
|
+
fromId: bigint;
|
|
50
|
+
toId: bigint;
|
|
51
|
+
kind: string;
|
|
52
|
+
createdAt?: Date;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Upsert on the (fromId, toId, kind) unique constraint: calling this twice for
|
|
56
|
+
* the same triple updates the one row in place (a no-op `SET kind = EXCLUDED.kind`)
|
|
57
|
+
* instead of raising a unique-violation or silently inserting a duplicate.
|
|
58
|
+
*/
|
|
59
|
+
export declare function insertTraceLink(prisma: TracePrismaClient, input: NewTraceLinkInput): Promise<TraceLinkRow>;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed $queryRaw/$executeRaw helpers for @flytedesk/app-kit/trace.
|
|
3
|
+
*
|
|
4
|
+
* Every helper writes/reads the tables created by
|
|
5
|
+
* prisma/migrations/0001_app_kit_trace_init/migration.sql directly. Column lists
|
|
6
|
+
* are written out literally in each query's tagged template (rather than shared
|
|
7
|
+
* via a string constant interpolated into the template) because a `${...}`
|
|
8
|
+
* interpolation inside a $queryRaw tagged template becomes a bound *parameter*,
|
|
9
|
+
* not raw SQL text — the TracePrismaClient structural interface has no
|
|
10
|
+
* `Prisma.raw`/`$queryRawUnsafe` escape hatch, deliberately, so there is no way to
|
|
11
|
+
* splice SQL fragments in from a variable. That's fine: the column lists are
|
|
12
|
+
* static.
|
|
13
|
+
*/
|
|
14
|
+
import { randomUUID } from "node:crypto";
|
|
15
|
+
function toJsonParam(value) {
|
|
16
|
+
return value === undefined || value === null ? null : JSON.stringify(value);
|
|
17
|
+
}
|
|
18
|
+
function requireOne(rows, what) {
|
|
19
|
+
const row = rows[0];
|
|
20
|
+
if (!row) {
|
|
21
|
+
throw new Error(`${what}: expected a returned row but got none`);
|
|
22
|
+
}
|
|
23
|
+
return row;
|
|
24
|
+
}
|
|
25
|
+
export async function insertTraceNode(prisma, input) {
|
|
26
|
+
const externalId = input.externalId ?? randomUUID();
|
|
27
|
+
const startedAt = input.startedAt ?? new Date();
|
|
28
|
+
const rows = await prisma.$queryRaw `
|
|
29
|
+
INSERT INTO app_kit_trace_nodes (
|
|
30
|
+
external_id, parent_id, root_id, kind, actor_id, route,
|
|
31
|
+
request_data, response_data, status_code, started_at, ended_at, duration_ms
|
|
32
|
+
) VALUES (
|
|
33
|
+
${externalId}::uuid, ${input.parentId ?? null}, ${input.rootId}, ${input.kind},
|
|
34
|
+
${input.actorId ?? null}, ${input.route ?? null},
|
|
35
|
+
${toJsonParam(input.requestData)}::jsonb, ${toJsonParam(input.responseData)}::jsonb,
|
|
36
|
+
${input.statusCode ?? null}, ${startedAt}, ${input.endedAt ?? null}, ${input.durationMs ?? null}
|
|
37
|
+
)
|
|
38
|
+
RETURNING
|
|
39
|
+
id, external_id AS "externalId", parent_id AS "parentId", root_id AS "rootId", kind,
|
|
40
|
+
actor_id AS "actorId", route, request_data AS "requestData", response_data AS "responseData",
|
|
41
|
+
status_code AS "statusCode", started_at AS "startedAt", ended_at AS "endedAt",
|
|
42
|
+
duration_ms AS "durationMs"
|
|
43
|
+
`;
|
|
44
|
+
return requireOne(rows, "insertTraceNode");
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Point an already-inserted TraceNode's `root_id` at a (possibly different) id.
|
|
48
|
+
* Used by src/trace/errors.ts to make a standalone captured error self-rooted
|
|
49
|
+
* (`root_id = id`) when the caller has no existing trace root to hang it off of —
|
|
50
|
+
* `TraceNode.rootId` is `NOT NULL`, so an orphan error always needs *some* root.
|
|
51
|
+
*/
|
|
52
|
+
export async function setTraceNodeRoot(prisma, id, rootId) {
|
|
53
|
+
const rows = await prisma.$queryRaw `
|
|
54
|
+
UPDATE app_kit_trace_nodes
|
|
55
|
+
SET root_id = ${rootId}
|
|
56
|
+
WHERE id = ${id}
|
|
57
|
+
RETURNING
|
|
58
|
+
id, external_id AS "externalId", parent_id AS "parentId", root_id AS "rootId", kind,
|
|
59
|
+
actor_id AS "actorId", route, request_data AS "requestData", response_data AS "responseData",
|
|
60
|
+
status_code AS "statusCode", started_at AS "startedAt", ended_at AS "endedAt",
|
|
61
|
+
duration_ms AS "durationMs"
|
|
62
|
+
`;
|
|
63
|
+
return requireOne(rows, "setTraceNodeRoot");
|
|
64
|
+
}
|
|
65
|
+
export async function getTraceChainForRoot(prisma, rootId) {
|
|
66
|
+
return prisma.$queryRaw `
|
|
67
|
+
SELECT
|
|
68
|
+
id, external_id AS "externalId", parent_id AS "parentId", root_id AS "rootId", kind,
|
|
69
|
+
actor_id AS "actorId", route, request_data AS "requestData", response_data AS "responseData",
|
|
70
|
+
status_code AS "statusCode", started_at AS "startedAt", ended_at AS "endedAt",
|
|
71
|
+
duration_ms AS "durationMs"
|
|
72
|
+
FROM app_kit_trace_nodes
|
|
73
|
+
WHERE root_id = ${rootId}
|
|
74
|
+
ORDER BY started_at ASC
|
|
75
|
+
`;
|
|
76
|
+
}
|
|
77
|
+
export async function insertModelAudit(prisma, input) {
|
|
78
|
+
const createdAt = input.createdAt ?? new Date();
|
|
79
|
+
const rows = await prisma.$queryRaw `
|
|
80
|
+
INSERT INTO app_kit_trace_model_audits (
|
|
81
|
+
trace_node_id, model_type, model_id, event, actor_id, old_values, new_values, created_at
|
|
82
|
+
) VALUES (
|
|
83
|
+
${input.traceNodeId ?? null}, ${input.modelType}, ${input.modelId}, ${input.event},
|
|
84
|
+
${input.actorId ?? null}, ${toJsonParam(input.oldValues)}::jsonb,
|
|
85
|
+
${toJsonParam(input.newValues)}::jsonb, ${createdAt}
|
|
86
|
+
)
|
|
87
|
+
RETURNING
|
|
88
|
+
id, trace_node_id AS "traceNodeId", model_type AS "modelType", model_id AS "modelId",
|
|
89
|
+
event, actor_id AS "actorId", old_values AS "oldValues", new_values AS "newValues",
|
|
90
|
+
created_at AS "createdAt"
|
|
91
|
+
`;
|
|
92
|
+
return requireOne(rows, "insertModelAudit");
|
|
93
|
+
}
|
|
94
|
+
export async function getAuditHistoryForModel(prisma, modelType, modelId, limit = 50) {
|
|
95
|
+
return prisma.$queryRaw `
|
|
96
|
+
SELECT
|
|
97
|
+
id, trace_node_id AS "traceNodeId", model_type AS "modelType", model_id AS "modelId",
|
|
98
|
+
event, actor_id AS "actorId", old_values AS "oldValues", new_values AS "newValues",
|
|
99
|
+
created_at AS "createdAt"
|
|
100
|
+
FROM app_kit_trace_model_audits
|
|
101
|
+
WHERE model_type = ${modelType} AND model_id = ${modelId}
|
|
102
|
+
ORDER BY created_at DESC
|
|
103
|
+
LIMIT ${limit}
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
export async function insertApiCallLog(prisma, input) {
|
|
107
|
+
const createdAt = input.createdAt ?? new Date();
|
|
108
|
+
const rows = await prisma.$queryRaw `
|
|
109
|
+
INSERT INTO app_kit_trace_api_call_logs (
|
|
110
|
+
trace_node_id, route, method, status_code, actor_id, duration_ms, created_at
|
|
111
|
+
) VALUES (
|
|
112
|
+
${input.traceNodeId ?? null}, ${input.route}, ${input.method}, ${input.statusCode ?? null},
|
|
113
|
+
${input.actorId ?? null}, ${input.durationMs ?? null}, ${createdAt}
|
|
114
|
+
)
|
|
115
|
+
RETURNING
|
|
116
|
+
id, trace_node_id AS "traceNodeId", route, method, status_code AS "statusCode",
|
|
117
|
+
actor_id AS "actorId", duration_ms AS "durationMs", created_at AS "createdAt"
|
|
118
|
+
`;
|
|
119
|
+
return requireOne(rows, "insertApiCallLog");
|
|
120
|
+
}
|
|
121
|
+
export async function getRecentApiCallsForRoute(prisma, route, sinceMinutes = 30) {
|
|
122
|
+
const since = new Date(Date.now() - sinceMinutes * 60_000);
|
|
123
|
+
return prisma.$queryRaw `
|
|
124
|
+
SELECT
|
|
125
|
+
id, trace_node_id AS "traceNodeId", route, method, status_code AS "statusCode",
|
|
126
|
+
actor_id AS "actorId", duration_ms AS "durationMs", created_at AS "createdAt"
|
|
127
|
+
FROM app_kit_trace_api_call_logs
|
|
128
|
+
WHERE route = ${route} AND created_at >= ${since}
|
|
129
|
+
ORDER BY created_at DESC
|
|
130
|
+
`;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Upsert on the (fromId, toId, kind) unique constraint: calling this twice for
|
|
134
|
+
* the same triple updates the one row in place (a no-op `SET kind = EXCLUDED.kind`)
|
|
135
|
+
* instead of raising a unique-violation or silently inserting a duplicate.
|
|
136
|
+
*/
|
|
137
|
+
export async function insertTraceLink(prisma, input) {
|
|
138
|
+
const createdAt = input.createdAt ?? new Date();
|
|
139
|
+
const rows = await prisma.$queryRaw `
|
|
140
|
+
INSERT INTO app_kit_trace_links (from_id, to_id, kind, created_at)
|
|
141
|
+
VALUES (${input.fromId}, ${input.toId}, ${input.kind}, ${createdAt})
|
|
142
|
+
ON CONFLICT (from_id, to_id, kind) DO UPDATE SET kind = EXCLUDED.kind
|
|
143
|
+
RETURNING id, from_id AS "fromId", to_id AS "toId", kind, created_at AS "createdAt"
|
|
144
|
+
`;
|
|
145
|
+
return requireOne(rows, "insertTraceLink");
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=queries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/trace/queries.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-written row/client types for @flytedesk/app-kit/trace.
|
|
3
|
+
*
|
|
4
|
+
* These are written by hand, not generated by Prisma, because this package never
|
|
5
|
+
* builds its own PrismaClient (DEC-39): the consumer generates their own client
|
|
6
|
+
* from their own schema plus the fragment copied in by `trace-sync init`, and this
|
|
7
|
+
* package only ever talks to that client through the minimal structural interface
|
|
8
|
+
* below, via raw SQL. Field names here mirror the field names in
|
|
9
|
+
* prisma/fragments/trace.prisma; query helpers (src/trace/queries.ts) are
|
|
10
|
+
* responsible for aliasing the snake_case SQL columns back onto these camelCase
|
|
11
|
+
* shapes with `AS "camelCaseName"`.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* The structural subset of a generated PrismaClient this package actually needs.
|
|
15
|
+
* Deliberately minimal and decoupled from `@prisma/client`'s own types: any object
|
|
16
|
+
* exposing tagged-template `$queryRaw`/`$executeRaw` methods satisfies this,
|
|
17
|
+
* including a real PrismaClient instance, a lightly-wrapped one (e.g. request
|
|
18
|
+
* logging middleware), or a hand-rolled test double.
|
|
19
|
+
*/
|
|
20
|
+
export interface TracePrismaClient {
|
|
21
|
+
$queryRaw<T = unknown>(query: TemplateStringsArray, ...values: unknown[]): Promise<T>;
|
|
22
|
+
$executeRaw(query: TemplateStringsArray, ...values: unknown[]): Promise<number>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Valid `TraceNode.kind` values this package knows how to write.
|
|
26
|
+
* The column itself is a plain `String` (see trace.prisma for why), so consumers
|
|
27
|
+
* remain free to write their own kinds; this union is what src/trace helpers use.
|
|
28
|
+
*/
|
|
29
|
+
export type TraceNodeKind = "HTTP_REQUEST" | "JOB" | "EVENT" | "SYSTEM" | "ERROR";
|
|
30
|
+
export interface TraceNodeRow {
|
|
31
|
+
id: bigint;
|
|
32
|
+
externalId: string;
|
|
33
|
+
parentId: bigint | null;
|
|
34
|
+
rootId: bigint;
|
|
35
|
+
kind: string;
|
|
36
|
+
actorId: string | null;
|
|
37
|
+
route: string | null;
|
|
38
|
+
requestData: unknown;
|
|
39
|
+
responseData: unknown;
|
|
40
|
+
statusCode: number | null;
|
|
41
|
+
startedAt: Date;
|
|
42
|
+
endedAt: Date | null;
|
|
43
|
+
durationMs: number | null;
|
|
44
|
+
}
|
|
45
|
+
export interface ModelAuditRow {
|
|
46
|
+
id: bigint;
|
|
47
|
+
traceNodeId: bigint | null;
|
|
48
|
+
modelType: string;
|
|
49
|
+
modelId: string;
|
|
50
|
+
event: string;
|
|
51
|
+
actorId: string | null;
|
|
52
|
+
oldValues: unknown;
|
|
53
|
+
newValues: unknown;
|
|
54
|
+
createdAt: Date;
|
|
55
|
+
}
|
|
56
|
+
export interface ApiCallLogRow {
|
|
57
|
+
id: bigint;
|
|
58
|
+
traceNodeId: bigint | null;
|
|
59
|
+
route: string;
|
|
60
|
+
method: string;
|
|
61
|
+
statusCode: number | null;
|
|
62
|
+
actorId: string | null;
|
|
63
|
+
durationMs: number | null;
|
|
64
|
+
createdAt: Date;
|
|
65
|
+
}
|
|
66
|
+
export interface TraceLinkRow {
|
|
67
|
+
id: bigint;
|
|
68
|
+
fromId: bigint;
|
|
69
|
+
toId: bigint;
|
|
70
|
+
kind: string;
|
|
71
|
+
createdAt: Date;
|
|
72
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-written row/client types for @flytedesk/app-kit/trace.
|
|
3
|
+
*
|
|
4
|
+
* These are written by hand, not generated by Prisma, because this package never
|
|
5
|
+
* builds its own PrismaClient (DEC-39): the consumer generates their own client
|
|
6
|
+
* from their own schema plus the fragment copied in by `trace-sync init`, and this
|
|
7
|
+
* package only ever talks to that client through the minimal structural interface
|
|
8
|
+
* below, via raw SQL. Field names here mirror the field names in
|
|
9
|
+
* prisma/fragments/trace.prisma; query helpers (src/trace/queries.ts) are
|
|
10
|
+
* responsible for aliasing the snake_case SQL columns back onto these camelCase
|
|
11
|
+
* shapes with `AS "camelCaseName"`.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/trace/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flytedesk/app-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Shared platform kit for flytedesk apps: flytedesk-id auth (BFF/OIDC client) and a Postgres-native trace/audit layer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -19,12 +19,16 @@
|
|
|
19
19
|
"pnpm": ">=12.0.0"
|
|
20
20
|
},
|
|
21
21
|
"publishConfig": {
|
|
22
|
-
"access": "
|
|
22
|
+
"access": "public",
|
|
23
23
|
"registry": "https://registry.npmjs.org/"
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
|
-
"dist"
|
|
26
|
+
"dist",
|
|
27
|
+
"prisma"
|
|
27
28
|
],
|
|
29
|
+
"bin": {
|
|
30
|
+
"trace-sync": "./dist/cli/trace-sync.js"
|
|
31
|
+
},
|
|
28
32
|
"exports": {
|
|
29
33
|
"./auth": {
|
|
30
34
|
"types": "./dist/auth/index.d.ts",
|
|
@@ -33,26 +37,68 @@
|
|
|
33
37
|
"./trace": {
|
|
34
38
|
"types": "./dist/trace/index.d.ts",
|
|
35
39
|
"import": "./dist/trace/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./rate-limit": {
|
|
42
|
+
"types": "./dist/rate-limit/index.d.ts",
|
|
43
|
+
"import": "./dist/rate-limit/index.js"
|
|
36
44
|
}
|
|
37
45
|
},
|
|
38
46
|
"scripts": {
|
|
39
|
-
"build": "tsc -p tsconfig.json",
|
|
47
|
+
"build": "tsc -p tsconfig.build.json",
|
|
40
48
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
41
|
-
"prepublishOnly": "npm run build"
|
|
49
|
+
"prepublishOnly": "npm run build",
|
|
50
|
+
"prepare": "node scripts/postinstall-eslint-ts6-shim.cjs",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
53
|
+
"lint": "eslint .",
|
|
54
|
+
"format": "prettier --check .",
|
|
55
|
+
"check:exports": "publint && attw --pack . --profile esm-only",
|
|
56
|
+
"changeset": "changeset",
|
|
57
|
+
"version-packages": "changeset version",
|
|
58
|
+
"release": "npm run build && npm run check:exports && changeset publish"
|
|
42
59
|
},
|
|
43
60
|
"peerDependencies": {
|
|
44
|
-
"
|
|
45
|
-
"
|
|
61
|
+
"@prisma/client": "^7",
|
|
62
|
+
"fastify": "^4 || ^5"
|
|
46
63
|
},
|
|
47
64
|
"peerDependenciesMeta": {
|
|
48
65
|
"@prisma/client": {
|
|
49
66
|
"optional": true
|
|
50
67
|
}
|
|
51
68
|
},
|
|
69
|
+
"overrides": {
|
|
70
|
+
"typescript-eslint": {
|
|
71
|
+
"typescript": "$typescript"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
52
74
|
"devDependencies": {
|
|
75
|
+
"@arethetypeswrong/cli": "^0.18.5",
|
|
76
|
+
"@changesets/changelog-github": "^1.0.0",
|
|
77
|
+
"@changesets/cli": "^3.0.1",
|
|
53
78
|
"@prisma/client": "^7.10.0",
|
|
79
|
+
"@testcontainers/postgresql": "^12.1.0",
|
|
80
|
+
"@types/cross-spawn": "^6.0.6",
|
|
54
81
|
"@types/node": "^24.0.0",
|
|
82
|
+
"@types/pg": "^8.23.1",
|
|
83
|
+
"@typescript/typescript6": "^6.0.2",
|
|
84
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
85
|
+
"eslint": "^10.9.1",
|
|
86
|
+
"eslint-config-prettier": "^10.1.8",
|
|
87
|
+
"eslint-plugin-n": "^18.3.0",
|
|
55
88
|
"fastify": "^5.12.0",
|
|
56
|
-
"
|
|
89
|
+
"globals": "^17.12.0",
|
|
90
|
+
"pg": "^8.23.0",
|
|
91
|
+
"prettier": "^3.9.6",
|
|
92
|
+
"publint": "^0.3.24",
|
|
93
|
+
"testcontainers": "^12.1.0",
|
|
94
|
+
"typescript": "^7.0.2",
|
|
95
|
+
"typescript-eslint": "^8.69.0",
|
|
96
|
+
"vitest": "^4.1.11"
|
|
97
|
+
},
|
|
98
|
+
"dependencies": {
|
|
99
|
+
"@fastify/cookie": "^11.0.1",
|
|
100
|
+
"cross-spawn": "^7.0.6",
|
|
101
|
+
"fastify-plugin": "^6.0.0",
|
|
102
|
+
"jose": "^6.2.10"
|
|
57
103
|
}
|
|
58
104
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// @flytedesk/app-kit/trace — canonical Prisma model fragment (DEC-39).
|
|
2
|
+
//
|
|
3
|
+
// This fragment is NOT built into a Prisma Client by this package. It is copied
|
|
4
|
+
// verbatim into the consumer's own multi-file Prisma schema folder by `trace-sync
|
|
5
|
+
// init` (see src/cli/trace-sync.ts), and the consumer generates their own client
|
|
6
|
+
// against their own schema + this fragment merged in.
|
|
7
|
+
//
|
|
8
|
+
// Design constraints (deliberate, see DEC-39):
|
|
9
|
+
// - No `@relation`/FK attributes anywhere. These are plain scalar columns. A
|
|
10
|
+
// package that ships models into a consumer's schema cannot see the consumer's
|
|
11
|
+
// other models, and dropping relation attributes avoids lock contention on the
|
|
12
|
+
// hottest insert path (trace nodes are written on every request/job).
|
|
13
|
+
// - No Prisma `enum`. Enums are schema-global in Prisma and would collide across
|
|
14
|
+
// packages that both want to declare, say, an `EventKind`. `kind`/`event` are
|
|
15
|
+
// plain `String` columns; the TypeScript layer (src/trace/types.ts) provides a
|
|
16
|
+
// union type for the values this package writes.
|
|
17
|
+
// - `BigInt @id @default(autoincrement())` primary keys, not UUID, for insert
|
|
18
|
+
// locality on a high-write append log.
|
|
19
|
+
//
|
|
20
|
+
// Table names are prefixed `app_kit_trace_` via `@@map` so they can't collide with
|
|
21
|
+
// consumer tables. Column names are snake_case via `@map`, matching the raw SQL in
|
|
22
|
+
// prisma/migrations/0001_app_kit_trace_init/migration.sql exactly.
|
|
23
|
+
|
|
24
|
+
model TraceNode {
|
|
25
|
+
id BigInt @id @default(autoincrement())
|
|
26
|
+
externalId String @default(uuid()) @db.Uuid @map("external_id")
|
|
27
|
+
parentId BigInt? @map("parent_id")
|
|
28
|
+
rootId BigInt @map("root_id")
|
|
29
|
+
kind String
|
|
30
|
+
actorId String? @map("actor_id")
|
|
31
|
+
route String?
|
|
32
|
+
requestData Json? @map("request_data")
|
|
33
|
+
responseData Json? @map("response_data")
|
|
34
|
+
statusCode Int? @map("status_code")
|
|
35
|
+
startedAt DateTime @default(now()) @map("started_at")
|
|
36
|
+
endedAt DateTime? @map("ended_at")
|
|
37
|
+
durationMs Int? @map("duration_ms")
|
|
38
|
+
|
|
39
|
+
@@index([rootId])
|
|
40
|
+
@@index([parentId])
|
|
41
|
+
@@index([route, startedAt(sort: Desc)])
|
|
42
|
+
@@index([actorId, startedAt(sort: Desc)])
|
|
43
|
+
@@map("app_kit_trace_nodes")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
model ModelAudit {
|
|
47
|
+
id BigInt @id @default(autoincrement())
|
|
48
|
+
traceNodeId BigInt? @map("trace_node_id")
|
|
49
|
+
modelType String @map("model_type")
|
|
50
|
+
modelId String @map("model_id")
|
|
51
|
+
event String
|
|
52
|
+
actorId String? @map("actor_id")
|
|
53
|
+
oldValues Json? @map("old_values")
|
|
54
|
+
newValues Json? @map("new_values")
|
|
55
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
56
|
+
|
|
57
|
+
@@index([modelType, modelId, createdAt(sort: Desc)])
|
|
58
|
+
@@index([traceNodeId])
|
|
59
|
+
@@map("app_kit_trace_model_audits")
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
model ApiCallLog {
|
|
63
|
+
id BigInt @id @default(autoincrement())
|
|
64
|
+
traceNodeId BigInt? @map("trace_node_id")
|
|
65
|
+
route String
|
|
66
|
+
method String
|
|
67
|
+
statusCode Int? @map("status_code")
|
|
68
|
+
actorId String? @map("actor_id")
|
|
69
|
+
durationMs Int? @map("duration_ms")
|
|
70
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
71
|
+
|
|
72
|
+
@@index([route, createdAt(sort: Desc)])
|
|
73
|
+
@@index([actorId, createdAt(sort: Desc)])
|
|
74
|
+
@@map("app_kit_trace_api_call_logs")
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
model TraceLink {
|
|
78
|
+
id BigInt @id @default(autoincrement())
|
|
79
|
+
fromId BigInt @map("from_id")
|
|
80
|
+
toId BigInt @map("to_id")
|
|
81
|
+
kind String
|
|
82
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
83
|
+
|
|
84
|
+
@@unique([fromId, toId, kind])
|
|
85
|
+
@@index([toId])
|
|
86
|
+
@@map("app_kit_trace_links")
|
|
87
|
+
}
|