@igstack/app-catalog-backend-core 5.0.1-alpha-20260923024813 → 5.0.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.
|
@@ -3,7 +3,7 @@ import { TRPCError, initTRPC } from "@trpc/server";
|
|
|
3
3
|
//#region src/server/trpcSetup.ts
|
|
4
4
|
const t = initTRPC.context().create({ errorFormatter({ error, shape }) {
|
|
5
5
|
if (shape.data.httpStatus < 500) {
|
|
6
|
-
console.info(`[tRPC] ${error.code} ${shape.data.path ??
|
|
6
|
+
console.info(`[tRPC] ${error.code} ${JSON.stringify(shape.data.path ?? null)}: ${JSON.stringify(error.message)}`);
|
|
7
7
|
return shape;
|
|
8
8
|
}
|
|
9
9
|
console.error("[tRPC Error]", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trpcSetup.mjs","names":[],"sources":["../../src/server/trpcSetup.ts"],"sourcesContent":["import { TRPCError, initTRPC } from '@trpc/server'\nimport type { AcTrpcContext } from './acTrpcContext'\n\nexport const t = initTRPC.context<AcTrpcContext>().create({\n errorFormatter({ error, shape }) {\n // A 4xx is the caller's mistake (unknown path, bad input, no session). One\n // plain line on stdout is enough; a stack on stderr would make log collectors\n // file it as status:error next to real failures.\n if (shape.data.httpStatus < 500) {\n console.info(\n `[tRPC] ${error.code} ${shape.data.path ??
|
|
1
|
+
{"version":3,"file":"trpcSetup.mjs","names":[],"sources":["../../src/server/trpcSetup.ts"],"sourcesContent":["import { TRPCError, initTRPC } from '@trpc/server'\nimport type { AcTrpcContext } from './acTrpcContext'\n\nexport const t = initTRPC.context<AcTrpcContext>().create({\n errorFormatter({ error, shape }) {\n // A 4xx is the caller's mistake (unknown path, bad input, no session). One\n // plain line on stdout is enough; a stack on stderr would make log collectors\n // file it as status:error next to real failures. Path and message are\n // caller-controlled (the path is URL-decoded, a zod message spans lines), so\n // JSON-escape them: a newline would otherwise split the record or forge one.\n if (shape.data.httpStatus < 500) {\n console.info(\n `[tRPC] ${error.code} ${JSON.stringify(shape.data.path ?? null)}: ${JSON.stringify(error.message)}`,\n )\n return shape\n }\n console.error('[tRPC Error]', {\n path: shape.data.path,\n code: error.code,\n message: error.message,\n cause: error.cause,\n stack: error.stack,\n })\n return shape\n },\n})\n\n/**\n * Export reusable router and procedure helpers\n */\nexport const router = t.router\nexport const publicProcedure = t.procedure\n\n/**\n * Middleware to check if user is authenticated\n */\nconst isAuthenticated = t.middleware(({ ctx, next }) => {\n if (!ctx.user) {\n throw new TRPCError({\n code: 'UNAUTHORIZED',\n message: 'You must be logged in to access this resource',\n })\n }\n return next({\n ctx: {\n ...ctx,\n user: ctx.user,\n },\n })\n})\n\n/**\n * Middleware to check if user is an admin\n */\nconst isAdminMiddleware = t.middleware(({ ctx, next }) => {\n if (!ctx.user) {\n throw new TRPCError({\n code: 'UNAUTHORIZED',\n message: 'You must be logged in',\n })\n }\n if (!ctx.isAdmin) {\n throw new TRPCError({\n code: 'FORBIDDEN',\n message: 'Admin access required',\n })\n }\n return next({\n ctx: {\n ...ctx,\n user: ctx.user,\n },\n })\n})\n\n/**\n * Admin procedure that requires admin permissions\n */\nexport const adminProcedure = t.procedure.use(isAdminMiddleware)\n\n/**\n * Protected procedure that requires authentication (but not admin)\n */\nexport const protectedProcedure = t.procedure.use(isAuthenticated)\n"],"mappings":";;;AAGA,MAAa,IAAI,SAAS,SAAwB,CAAC,OAAO,EACxD,eAAe,EAAE,OAAO,SAAS;AAM/B,KAAI,MAAM,KAAK,aAAa,KAAK;AAC/B,UAAQ,KACN,UAAU,MAAM,KAAK,GAAG,KAAK,UAAU,MAAM,KAAK,QAAQ,KAAK,CAAC,IAAI,KAAK,UAAU,MAAM,QAAQ,GAClG;AACD,SAAO;;AAET,SAAQ,MAAM,gBAAgB;EAC5B,MAAM,MAAM,KAAK;EACjB,MAAM,MAAM;EACZ,SAAS,MAAM;EACf,OAAO,MAAM;EACb,OAAO,MAAM;EACd,CAAC;AACF,QAAO;GAEV,CAAC;;;;AAKF,MAAa,SAAS,EAAE;AACxB,MAAa,kBAAkB,EAAE;;;;AAKjC,MAAM,kBAAkB,EAAE,YAAY,EAAE,KAAK,WAAW;AACtD,KAAI,CAAC,IAAI,KACP,OAAM,IAAI,UAAU;EAClB,MAAM;EACN,SAAS;EACV,CAAC;AAEJ,QAAO,KAAK,EACV,KAAK;EACH,GAAG;EACH,MAAM,IAAI;EACX,EACF,CAAC;EACF;;;;AAKF,MAAM,oBAAoB,EAAE,YAAY,EAAE,KAAK,WAAW;AACxD,KAAI,CAAC,IAAI,KACP,OAAM,IAAI,UAAU;EAClB,MAAM;EACN,SAAS;EACV,CAAC;AAEJ,KAAI,CAAC,IAAI,QACP,OAAM,IAAI,UAAU;EAClB,MAAM;EACN,SAAS;EACV,CAAC;AAEJ,QAAO,KAAK,EACV,KAAK;EACH,GAAG;EACH,MAAM,IAAI;EACX,EACF,CAAC;EACF;;;;AAKF,MAAa,iBAAiB,EAAE,UAAU,IAAI,kBAAkB;;;;AAKhE,MAAa,qBAAqB,EAAE,UAAU,IAAI,gBAAgB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@igstack/app-catalog-backend-core",
|
|
3
|
-
"version": "5.0.1
|
|
3
|
+
"version": "5.0.1",
|
|
4
4
|
"description": "Backend core library for App Catalog",
|
|
5
5
|
"homepage": "https://github.com/lislon/app-catalog",
|
|
6
6
|
"repository": {
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"tsyringe": "^4.10.0",
|
|
46
46
|
"yaml": "^2.8.0",
|
|
47
47
|
"zod": "^4.3.5",
|
|
48
|
-
"@igstack/app-catalog-
|
|
49
|
-
"@igstack/app-catalog-
|
|
48
|
+
"@igstack/app-catalog-table-sync": "5.0.1",
|
|
49
|
+
"@igstack/app-catalog-shared-core": "5.0.1"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@tanstack/vite-config": "^0.4.3",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"engines": {
|
|
74
74
|
"node": ">=24"
|
|
75
75
|
},
|
|
76
|
-
"gitHead": "
|
|
76
|
+
"gitHead": "80adf209743ee4fdef02b31172c0eed9b56374b8",
|
|
77
77
|
"scripts": {
|
|
78
78
|
"build": "tsdown",
|
|
79
79
|
"build:lenient": "tsdown",
|
|
@@ -42,9 +42,21 @@ describe('tRPC error logging', () => {
|
|
|
42
42
|
expect(error).not.toHaveBeenCalled()
|
|
43
43
|
expect(info).toHaveBeenCalledTimes(1)
|
|
44
44
|
expect(info).toHaveBeenCalledWith(
|
|
45
|
-
expect.stringContaining('NOT_FOUND appCatalog.getVersionInfo'),
|
|
45
|
+
expect.stringContaining('NOT_FOUND "appCatalog.getVersionInfo"'),
|
|
46
46
|
)
|
|
47
|
-
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('keeps a caller-supplied newline out of the log line', async () => {
|
|
50
|
+
// The path is URL-decoded before it reaches the formatter, so without
|
|
51
|
+
// escaping a request could inject a second, forged record.
|
|
52
|
+
const res = await call('x%0A[tRPC Error] forged')
|
|
53
|
+
|
|
54
|
+
expect(res.status).toBe(404)
|
|
55
|
+
expect(error).not.toHaveBeenCalled()
|
|
56
|
+
expect(info).toHaveBeenCalledTimes(1)
|
|
57
|
+
const line: string = info.mock.calls[0]?.[0]
|
|
58
|
+
expect(line).not.toContain('\n')
|
|
59
|
+
expect(line).toContain('x\\n[tRPC Error] forged')
|
|
48
60
|
})
|
|
49
61
|
|
|
50
62
|
it('keeps a procedure failure at error level with its stack', async () => {
|
package/src/server/trpcSetup.ts
CHANGED
|
@@ -5,10 +5,12 @@ export const t = initTRPC.context<AcTrpcContext>().create({
|
|
|
5
5
|
errorFormatter({ error, shape }) {
|
|
6
6
|
// A 4xx is the caller's mistake (unknown path, bad input, no session). One
|
|
7
7
|
// plain line on stdout is enough; a stack on stderr would make log collectors
|
|
8
|
-
// file it as status:error next to real failures.
|
|
8
|
+
// file it as status:error next to real failures. Path and message are
|
|
9
|
+
// caller-controlled (the path is URL-decoded, a zod message spans lines), so
|
|
10
|
+
// JSON-escape them: a newline would otherwise split the record or forge one.
|
|
9
11
|
if (shape.data.httpStatus < 500) {
|
|
10
12
|
console.info(
|
|
11
|
-
`[tRPC] ${error.code} ${shape.data.path ??
|
|
13
|
+
`[tRPC] ${error.code} ${JSON.stringify(shape.data.path ?? null)}: ${JSON.stringify(error.message)}`,
|
|
12
14
|
)
|
|
13
15
|
return shape
|
|
14
16
|
}
|