@human-synthesis/norns 0.0.13 → 0.0.16
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/package.json +1 -1
- package/src/server/boot.js +7 -1
- package/src/server/index.js +1 -1
- package/src/server/route.js +46 -2
- package/src/vite.js +13 -6
package/package.json
CHANGED
package/src/server/boot.js
CHANGED
|
@@ -2,6 +2,7 @@ import { sequence } from '@sveltejs/kit/hooks';
|
|
|
2
2
|
import { Container } from './container.js';
|
|
3
3
|
import { contextHandle } from './handle/context.js';
|
|
4
4
|
import { errorHandle } from './handle/error.js';
|
|
5
|
+
import { setSerializer } from './route.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Create a fresh root container with no features registered. Useful for tests
|
|
@@ -36,7 +37,8 @@ export function createApp() {
|
|
|
36
37
|
* @param {{
|
|
37
38
|
* features?: Record<string, FeatureModule>,
|
|
38
39
|
* extraHandle?: import('@sveltejs/kit').Handle | import('@sveltejs/kit').Handle[],
|
|
39
|
-
* handleError?: import('@sveltejs/kit').HandleServerError
|
|
40
|
+
* handleError?: import('@sveltejs/kit').HandleServerError,
|
|
41
|
+
* serializer?: import('./route.js').Serializer | null
|
|
40
42
|
* }} [opts]
|
|
41
43
|
* @returns {Promise<{
|
|
42
44
|
* container: Container,
|
|
@@ -47,6 +49,10 @@ export function createApp() {
|
|
|
47
49
|
export async function boot(opts = {}) {
|
|
48
50
|
const container = createApp();
|
|
49
51
|
|
|
52
|
+
// App-wide route() response serializer (e.g. tronSerializer() from
|
|
53
|
+
// @human-synthesis/norns-tron/server). Omit to keep plain JSON.
|
|
54
|
+
if (opts.serializer !== undefined) setSerializer(opts.serializer);
|
|
55
|
+
|
|
50
56
|
if (opts.features) {
|
|
51
57
|
for (const [path, mod] of Object.entries(opts.features)) {
|
|
52
58
|
const register = /** @type {ModuleRegister | undefined} */ (
|
package/src/server/index.js
CHANGED
|
@@ -3,7 +3,7 @@ export { withScope, getScope, getContainer } from './scope.js';
|
|
|
3
3
|
export { boot, createApp } from './boot.js';
|
|
4
4
|
export { contextHandle } from './handle/context.js';
|
|
5
5
|
export { errorHandle } from './handle/error.js';
|
|
6
|
-
export { route } from './route.js';
|
|
6
|
+
export { route, setSerializer, getSerializer } from './route.js';
|
|
7
7
|
export { page } from './page.js';
|
|
8
8
|
export { validate, ValidationError } from './validate.js';
|
|
9
9
|
export { betterSqlite, d1, libsql, postgres, withTransaction } from './db.js';
|
package/src/server/route.js
CHANGED
|
@@ -13,13 +13,44 @@ import { validate, ValidationError } from './validate.js';
|
|
|
13
13
|
* @property {any} user shortcut for `event.locals.user`
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {Object} Serializer
|
|
18
|
+
* @property {(result: any, event: RequestEvent) => Response | null} serialize
|
|
19
|
+
* turn the handler's return value into a Response, or return null to fall
|
|
20
|
+
* through to the default JSON serialization
|
|
21
|
+
* @property {(request: Request, contentType: string) => Promise<any> | undefined} [parseBody]
|
|
22
|
+
* read a request body for a content type route() doesn't handle natively;
|
|
23
|
+
* return undefined to fall through to the built-in JSON/form readers
|
|
24
|
+
*/
|
|
25
|
+
|
|
16
26
|
/**
|
|
17
27
|
* @typedef {Object} RouteOptions
|
|
18
28
|
* @property {any} [input] body schema (Standard Schema or function)
|
|
19
29
|
* @property {any} [query] query schema (Standard Schema or function)
|
|
30
|
+
* @property {Serializer | null} [serializer] per-route serializer; null forces
|
|
31
|
+
* plain JSON even when an app-wide serializer is set
|
|
20
32
|
* @property {(ctx: RouteContext) => any | Promise<any>} handler
|
|
21
33
|
*/
|
|
22
34
|
|
|
35
|
+
/** @type {Serializer | null} */
|
|
36
|
+
let defaultSerializer = null;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Set the app-wide response serializer used by every route() that doesn't
|
|
40
|
+
* declare its own (e.g. tronSerializer() from @human-synthesis/norns-tron).
|
|
41
|
+
* Pass null to go back to plain JSON. Usually wired via boot({ serializer }).
|
|
42
|
+
*
|
|
43
|
+
* @param {Serializer | null} serializer
|
|
44
|
+
*/
|
|
45
|
+
export function setSerializer(serializer) {
|
|
46
|
+
defaultSerializer = serializer ?? null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** @returns {Serializer | null} */
|
|
50
|
+
export function getSerializer() {
|
|
51
|
+
return defaultSerializer;
|
|
52
|
+
}
|
|
53
|
+
|
|
23
54
|
/**
|
|
24
55
|
* Wrap a `+server.c` handler. Bakes in:
|
|
25
56
|
* 1. body parsing (JSON / urlencoded / multipart) + validation
|
|
@@ -39,13 +70,17 @@ export function route(opts) {
|
|
|
39
70
|
if (typeof handler !== 'function') {
|
|
40
71
|
throw new Error('route(): `handler` is required');
|
|
41
72
|
}
|
|
73
|
+
const hasOwnSerializer = 'serializer' in opts;
|
|
42
74
|
|
|
43
75
|
return async (event) => {
|
|
44
76
|
const container = event.locals.container;
|
|
77
|
+
// Resolved per request so boot({ serializer }) applies regardless of
|
|
78
|
+
// module evaluation order.
|
|
79
|
+
const serializer = hasOwnSerializer ? opts.serializer : defaultSerializer;
|
|
45
80
|
|
|
46
81
|
let input;
|
|
47
82
|
if (inputSchema !== undefined) {
|
|
48
|
-
const raw = await readBody(event.request);
|
|
83
|
+
const raw = await readBody(event.request, serializer);
|
|
49
84
|
try {
|
|
50
85
|
input = validate(inputSchema, raw);
|
|
51
86
|
} catch (e) {
|
|
@@ -78,6 +113,10 @@ export function route(opts) {
|
|
|
78
113
|
});
|
|
79
114
|
|
|
80
115
|
if (result instanceof Response) return result;
|
|
116
|
+
if (serializer?.serialize) {
|
|
117
|
+
const response = serializer.serialize(result ?? null, event);
|
|
118
|
+
if (response instanceof Response) return response;
|
|
119
|
+
}
|
|
81
120
|
return json(result ?? null);
|
|
82
121
|
};
|
|
83
122
|
}
|
|
@@ -88,10 +127,15 @@ export function route(opts) {
|
|
|
88
127
|
* (or accept `null`).
|
|
89
128
|
*
|
|
90
129
|
* @param {Request} request
|
|
130
|
+
* @param {Serializer | null} [serializer]
|
|
91
131
|
* @returns {Promise<any>}
|
|
92
132
|
*/
|
|
93
|
-
async function readBody(request) {
|
|
133
|
+
async function readBody(request, serializer) {
|
|
94
134
|
const contentType = request.headers.get('content-type')?.split(';', 1)[0]?.trim() ?? '';
|
|
135
|
+
if (serializer?.parseBody) {
|
|
136
|
+
const parsed = serializer.parseBody(request, contentType);
|
|
137
|
+
if (parsed !== undefined) return await parsed;
|
|
138
|
+
}
|
|
95
139
|
if (contentType === 'application/json') {
|
|
96
140
|
try {
|
|
97
141
|
return await request.json();
|
package/src/vite.js
CHANGED
|
@@ -198,17 +198,21 @@ async function walkNFiles(dir, ext, out = []) {
|
|
|
198
198
|
* @param {object} [options]
|
|
199
199
|
* @param {string} [options.root] Directory to scan (default `src`).
|
|
200
200
|
* @param {string} [options.ext] File extension (default `.n`).
|
|
201
|
-
* @param {string} [options.outFile] Sidecar path relative to
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
201
|
+
* @param {string} [options.outFile] Sidecar path relative to the project
|
|
202
|
+
* root. The path is taken verbatim. Defaults
|
|
203
|
+
* to `node_modules/.cache/norns/tailwind-pug-classes.html`
|
|
204
|
+
* — node_modules is gitignored everywhere
|
|
205
|
+
* and `.cache/` is the conventional spot
|
|
206
|
+
* for build artifacts. Reference it from
|
|
207
|
+
* your CSS via `@source` with a path
|
|
208
|
+
* relative to the importing CSS file.
|
|
205
209
|
*
|
|
206
210
|
* @returns {import('vite').Plugin}
|
|
207
211
|
*/
|
|
208
212
|
export function pugTailwindExtract({
|
|
209
213
|
root = 'src',
|
|
210
214
|
ext = '.n',
|
|
211
|
-
outFile = '
|
|
215
|
+
outFile = 'node_modules/.cache/norns/tailwind-pug-classes.html'
|
|
212
216
|
} = {}) {
|
|
213
217
|
let projectRoot = process.cwd();
|
|
214
218
|
const fileClasses = new Map(); // absolute path -> Set<string>
|
|
@@ -222,7 +226,10 @@ export function pugTailwindExtract({
|
|
|
222
226
|
const html =
|
|
223
227
|
'<!-- AUTO-GENERATED by @human-synthesis/norns/vite pugTailwindExtract. Do not edit. -->\n' +
|
|
224
228
|
`<div class="${sorted.join(' ')}"></div>\n`;
|
|
225
|
-
|
|
229
|
+
// `outFile` is resolved from `projectRoot` directly; it doesn't get
|
|
230
|
+
// joined with `root` (the scan dir). Lets the sidecar live anywhere —
|
|
231
|
+
// inside src/, in a cache dir like `.norns/`, wherever.
|
|
232
|
+
const out = join(projectRoot, outFile);
|
|
226
233
|
await mkdir(dirname(out), { recursive: true });
|
|
227
234
|
await writeFile(out, html, 'utf8');
|
|
228
235
|
}
|