@tollwyn/baukasten 1.3.1 → 1.3.3
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/server/api.d.ts +4 -1
- package/dist/server/api.js +23 -4
- package/dist/server/hook.d.ts +4 -1
- package/dist/server/messages.d.ts +1 -1
- package/dist/server/messages.js +2 -0
- package/dist/server/openapi.js +2 -0
- package/dist/testing/app.d.ts +1 -0
- package/dist/testing/app.js +12 -11
- package/dist/testing/connection.js +1 -1
- package/dist/testing/shopify-connection.js +1 -1
- package/package.json +16 -16
package/dist/server/api.d.ts
CHANGED
|
@@ -14,7 +14,10 @@ export declare class ApiError extends Error {
|
|
|
14
14
|
}
|
|
15
15
|
/** Runs a handler of the API: an ApiError becomes its answer, anything else a logged 500. */
|
|
16
16
|
export declare function respond(log: Log, messages: Messages, handler: () => Promise<Response>): Promise<Response>;
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* The JSON body of a request, parsed with `schema`: 400 when it is no JSON, 413 when it is over the
|
|
19
|
+
* limit of the server, 422 when it does not fit.
|
|
20
|
+
*/
|
|
18
21
|
export declare function readBody<S extends z.ZodType>(request: Request, schema: S, messages: Messages): Promise<z.output<S>>;
|
|
19
22
|
/** The key in `Authorization: Bearer <prefix>_<secret>`, or null. */
|
|
20
23
|
export declare function bearerKey(headers: Headers, prefix: string): string | null;
|
package/dist/server/api.js
CHANGED
|
@@ -30,14 +30,28 @@ export async function respond(log, messages, handler) {
|
|
|
30
30
|
return errorResponse(new ApiError(500, 'internal', messages.api.internal));
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* SvelteKit errors the body of a request over BODY_SIZE_LIMIT with an error of its own that carries
|
|
35
|
+
* the status 413, and does so only while the body is read.
|
|
36
|
+
*/
|
|
37
|
+
const overLimit = (error) => error instanceof Error && 'status' in error && error.status === 413;
|
|
38
|
+
/**
|
|
39
|
+
* The JSON body of a request, parsed with `schema`: 400 when it is no JSON, 413 when it is over the
|
|
40
|
+
* limit of the server, 422 when it does not fit.
|
|
41
|
+
*/
|
|
34
42
|
export async function readBody(request, schema, messages) {
|
|
35
43
|
let body;
|
|
36
44
|
try {
|
|
37
45
|
body = await request.json();
|
|
38
46
|
}
|
|
39
|
-
catch {
|
|
40
|
-
|
|
47
|
+
catch (caught) {
|
|
48
|
+
if (caught instanceof SyntaxError) {
|
|
49
|
+
throw new ApiError(400, 'invalid_json', messages.api.invalidJson);
|
|
50
|
+
}
|
|
51
|
+
if (overLimit(caught)) {
|
|
52
|
+
throw new ApiError(413, 'payload_too_large', messages.api.payloadTooLarge);
|
|
53
|
+
}
|
|
54
|
+
throw caught;
|
|
41
55
|
}
|
|
42
56
|
const result = schema.safeParse(body);
|
|
43
57
|
if (!result.success) {
|
|
@@ -80,10 +94,15 @@ export async function asApiError(response, messages) {
|
|
|
80
94
|
if (response.status < 400)
|
|
81
95
|
return response;
|
|
82
96
|
if (response.headers.get('content-type')?.startsWith('application/json') === true) {
|
|
97
|
+
// A body that claims to be JSON and is none gets the shape; a failure to read it goes on.
|
|
83
98
|
const body = await response
|
|
84
99
|
.clone()
|
|
85
100
|
.json()
|
|
86
|
-
.catch(() =>
|
|
101
|
+
.catch((caught) => {
|
|
102
|
+
if (caught instanceof SyntaxError)
|
|
103
|
+
return null;
|
|
104
|
+
throw caught;
|
|
105
|
+
});
|
|
87
106
|
if (isShaped(body))
|
|
88
107
|
return response;
|
|
89
108
|
}
|
package/dist/server/hook.d.ts
CHANGED
|
@@ -71,7 +71,10 @@ export interface Kit<E extends ProductEnv = ProductEnv> {
|
|
|
71
71
|
api: {
|
|
72
72
|
/** Runs a handler: an ApiError becomes its answer, anything else a logged 500. */
|
|
73
73
|
respond(handler: () => Promise<Response>): Promise<Response>;
|
|
74
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* The JSON body of a request, parsed with `schema`: else 400, 413 over the limit of the server,
|
|
76
|
+
* or 422 with each problem.
|
|
77
|
+
*/
|
|
75
78
|
body<S extends z.ZodType>(request: Request, schema: S): Promise<z.output<S>>;
|
|
76
79
|
/** The account of the bearer key: else 401, and 429 past the limit of the key. */
|
|
77
80
|
account(event: RequestEvent): Promise<Account>;
|
|
@@ -99,7 +99,7 @@ export interface Messages {
|
|
|
99
99
|
url: string;
|
|
100
100
|
price: string;
|
|
101
101
|
quantity: string;
|
|
102
|
-
errors: Record<'invalidJson' | 'invalidRequest' | 'emailTaken' | 'unauthorized' | 'rateLimited' | 'forbidden' | 'subscribed' | 'noCustomer' | 'notConfigured', string>;
|
|
102
|
+
errors: Record<'invalidJson' | 'payloadTooLarge' | 'invalidRequest' | 'emailTaken' | 'unauthorized' | 'rateLimited' | 'forbidden' | 'subscribed' | 'noCustomer' | 'notConfigured', string>;
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
105
|
export declare const MESSAGES: Record<Language, Messages>;
|
package/dist/server/messages.js
CHANGED
|
@@ -103,6 +103,7 @@ const en = {
|
|
|
103
103
|
quantity: 'For a subscription that counts a quantity, like seats; 1 without.',
|
|
104
104
|
errors: {
|
|
105
105
|
invalidJson: 'The body is not JSON (invalid_json).',
|
|
106
|
+
payloadTooLarge: 'The body is over the limit of the server (payload_too_large).',
|
|
106
107
|
invalidRequest: 'The body is not valid (invalid_request); details name each problem.',
|
|
107
108
|
emailTaken: 'An account with this email address exists (email_taken).',
|
|
108
109
|
unauthorized: 'No valid API key (unauthorized).',
|
|
@@ -220,6 +221,7 @@ const de = {
|
|
|
220
221
|
quantity: 'Für ein Abo mit einer Menge, etwa Plätzen; ohne 1.',
|
|
221
222
|
errors: {
|
|
222
223
|
invalidJson: 'Der Inhalt ist kein JSON (invalid_json).',
|
|
224
|
+
payloadTooLarge: 'Der Inhalt ist grösser, als der Server annimmt (payload_too_large).',
|
|
223
225
|
invalidRequest: 'Der Inhalt ist nicht gültig (invalid_request); details nennt jedes Problem.',
|
|
224
226
|
emailTaken: 'Zu dieser E-Mail-Adresse besteht ein Konto (email_taken).',
|
|
225
227
|
unauthorized: 'Kein gültiger API-Schlüssel (unauthorized).',
|
package/dist/server/openapi.js
CHANGED
|
@@ -49,6 +49,7 @@ export function openApiPart(origin, messages) {
|
|
|
49
49
|
'400': error(o.errors.invalidJson),
|
|
50
50
|
'403': error(o.errors.forbidden),
|
|
51
51
|
'409': error(o.errors.emailTaken),
|
|
52
|
+
'413': error(o.errors.payloadTooLarge),
|
|
52
53
|
'422': error(o.errors.invalidRequest),
|
|
53
54
|
'429': error(o.errors.rateLimited)
|
|
54
55
|
}
|
|
@@ -88,6 +89,7 @@ export function openApiPart(origin, messages) {
|
|
|
88
89
|
'400': error(o.errors.invalidJson),
|
|
89
90
|
'401': error(o.errors.unauthorized),
|
|
90
91
|
'409': error(o.errors.subscribed),
|
|
92
|
+
'413': error(o.errors.payloadTooLarge),
|
|
91
93
|
'422': error(o.errors.invalidRequest),
|
|
92
94
|
'429': error(o.errors.rateLimited),
|
|
93
95
|
'503': error(o.errors.notConfigured)
|
package/dist/testing/app.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SmtpDouble } from './smtp.js';
|
|
2
2
|
import type { StripeDouble } from './stripe.js';
|
|
3
|
+
/** A line of stdout as JSON object; a line that is none is { notJson } or, as JSON, { notAnObject }. */
|
|
3
4
|
export type LogLine = Record<string, unknown>;
|
|
4
5
|
export interface AppProcess {
|
|
5
6
|
logs: LogLine[];
|
package/dist/testing/app.js
CHANGED
|
@@ -38,15 +38,8 @@ export function spawnNode(args, env, options = {}) {
|
|
|
38
38
|
buffer += chunk.toString();
|
|
39
39
|
let end;
|
|
40
40
|
while ((end = buffer.indexOf('\n')) >= 0) {
|
|
41
|
-
const
|
|
41
|
+
const line = asLine(buffer.slice(0, end));
|
|
42
42
|
buffer = buffer.slice(end + 1);
|
|
43
|
-
let line;
|
|
44
|
-
try {
|
|
45
|
-
line = asLine(JSON.parse(text));
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
line = { notJson: text };
|
|
49
|
-
}
|
|
50
43
|
logs.push(line);
|
|
51
44
|
events.emit('line', line);
|
|
52
45
|
}
|
|
@@ -101,10 +94,18 @@ export function spawnNode(args, env, options = {}) {
|
|
|
101
94
|
}
|
|
102
95
|
};
|
|
103
96
|
}
|
|
104
|
-
/** A log line is a JSON object; anything else is kept as text. */
|
|
105
|
-
function asLine(
|
|
97
|
+
/** A log line is a JSON object; anything else is kept as text, under notJson or notAnObject. */
|
|
98
|
+
function asLine(text) {
|
|
99
|
+
let value;
|
|
100
|
+
try {
|
|
101
|
+
value = JSON.parse(text);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// JSON.parse of a string fails only with a SyntaxError.
|
|
105
|
+
return { notJson: text };
|
|
106
|
+
}
|
|
106
107
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
107
|
-
|
|
108
|
+
return { notAnObject: text };
|
|
108
109
|
}
|
|
109
110
|
return Object.fromEntries(Object.entries(value));
|
|
110
111
|
}
|
|
@@ -294,7 +294,7 @@ export function connectionTest(product, playwright) {
|
|
|
294
294
|
});
|
|
295
295
|
test('the log of the product is JSON lines without the address, and stderr stays empty, to its end', async () => {
|
|
296
296
|
await app.stop();
|
|
297
|
-
expect(app.logs.filter((line) => 'notJson' in line)).toEqual([]);
|
|
297
|
+
expect(app.logs.filter((line) => 'notJson' in line || 'notAnObject' in line)).toEqual([]);
|
|
298
298
|
expect(JSON.stringify(app.logs)).not.toContain(EMAIL);
|
|
299
299
|
expect(app.stderr()).toBe('');
|
|
300
300
|
});
|
|
@@ -296,7 +296,7 @@ export function shopifyConnectionTest(app, playwright) {
|
|
|
296
296
|
});
|
|
297
297
|
test('it writes JSON lines only, and no token into its log', () => {
|
|
298
298
|
expect(running.stderr()).toBe('');
|
|
299
|
-
expect(running.logs.filter((line) => 'notJson' in line)).toEqual([]);
|
|
299
|
+
expect(running.logs.filter((line) => 'notJson' in line || 'notAnObject' in line)).toEqual([]);
|
|
300
300
|
// Every ID token starts with the same header, so any of them in the log would show it.
|
|
301
301
|
const [header = ''] = shopify.idToken(SHOP).split('.');
|
|
302
302
|
expect(header).not.toBe('');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tollwyn/baukasten",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"description": "What every product built on this kit shares: configuration, log and server, database with two-layer migrations, health, accounts with sessions and API keys, limits, mail, payment through Stripe, the numbers for sales, the frame and pages, the stubs that hang them into a product and the check of a product, and the tools to test one, its connection test among them; and for a Shopify app its installation with tokens, the frame of the admin, its webhooks, its plan through the Partner API and its privacy text.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -60,35 +60,35 @@
|
|
|
60
60
|
"svelte": "^5.57.0"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"nodemailer": "10.
|
|
63
|
+
"nodemailer": "10.1.1",
|
|
64
64
|
"pg": "8.23.1",
|
|
65
|
-
"smol-toml": "1.9.
|
|
65
|
+
"smol-toml": "1.9.1",
|
|
66
66
|
"stripe": "22.6.2",
|
|
67
|
-
"zod": "4.6.
|
|
67
|
+
"zod": "4.6.7"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@eslint-community/eslint-plugin-eslint-comments": "4.
|
|
70
|
+
"@eslint-community/eslint-plugin-eslint-comments": "4.8.1",
|
|
71
71
|
"@eslint/js": "10.0.1",
|
|
72
|
-
"@playwright/test": "1.
|
|
72
|
+
"@playwright/test": "1.64.0",
|
|
73
73
|
"@sveltejs/kit": "2.70.3",
|
|
74
74
|
"@sveltejs/package": "2.5.8",
|
|
75
|
-
"@sveltejs/vite-plugin-svelte": "7.3.
|
|
76
|
-
"@types/node": "24.19.
|
|
75
|
+
"@sveltejs/vite-plugin-svelte": "7.3.1",
|
|
76
|
+
"@types/node": "24.19.2",
|
|
77
77
|
"@types/pg": "8.23.1",
|
|
78
78
|
"@types/smtp-server": "3.5.13",
|
|
79
|
-
"eslint": "10.
|
|
79
|
+
"eslint": "10.12.0",
|
|
80
80
|
"eslint-config-prettier": "10.1.8",
|
|
81
|
-
"eslint-plugin-svelte": "3.23.
|
|
82
|
-
"globals": "17.
|
|
83
|
-
"prettier": "3.9.
|
|
81
|
+
"eslint-plugin-svelte": "3.23.1",
|
|
82
|
+
"globals": "17.13.0",
|
|
83
|
+
"prettier": "3.9.10",
|
|
84
84
|
"prettier-plugin-svelte": "4.1.1",
|
|
85
|
-
"smtp-server": "3.19.
|
|
85
|
+
"smtp-server": "3.19.22",
|
|
86
86
|
"svelte": "5.57.2",
|
|
87
87
|
"svelte-check": "4.7.6",
|
|
88
88
|
"typescript": "6.0.3",
|
|
89
|
-
"typescript-eslint": "8.
|
|
90
|
-
"vite": "8.
|
|
91
|
-
"vitest": "5.0.
|
|
89
|
+
"typescript-eslint": "8.71.2",
|
|
90
|
+
"vite": "8.3.4",
|
|
91
|
+
"vitest": "5.0.4"
|
|
92
92
|
},
|
|
93
93
|
"overrides": {
|
|
94
94
|
"cookie": "0.7.0"
|