@human-synthesis/norns 0.0.15 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@human-synthesis/norns",
3
- "version": "0.0.15",
3
+ "version": "0.0.16",
4
4
  "description": "Norns — SvelteKit with Civet, Pug, and the .n / .civet / .c file extensions",
5
5
  "license": "MIT",
6
6
  "author": "Daniel Teodoroiu (https://humansynthesis.ai)",
@@ -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} */ (
@@ -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';
@@ -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();