cursedbelt-server 4.19.0 โ†’ 4.19.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/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # cursedbelt-server
2
+
3
+ The server tier of the cursedbelt split: Hono on Bun, the D1 seam a Worker crosses, the
4
+ binary-server client, the guard, and the fleet standards (retention, activity, notifications,
5
+ engagement). `cursedbelt-core` is below it, the React design system `cursedbelt` above it.
6
+
7
+ ```sh
8
+ bun add cursedbelt-server
9
+ ```
10
+
11
+ ## ๐Ÿ”ด The root export is a BARREL โ€” import the leaf
12
+
13
+ `import โ€ฆ from "cursedbelt-server"` re-exports everything, so it reaches every optional peer and
14
+ `bun:sqlite` at once (the table below). An app that wants one function pays for all of them, and
15
+ on a Worker `wrangler deploy` fails to bundle it. Import the subpath that owns the symbol โ€”
16
+ `cursedbelt-server/login-throttle`, `cursedbelt-server/binary-store`, `cursedbelt-server/d1` โ€”
17
+ never the root. `package.json` `exports` is the list of subpaths.
18
+
19
+ Why this stays prose: which symbol an app WANTS is decided in the app, and a repo's gate proves
20
+ that repo โ€” so the barrel-importer grep belongs to the generation's tools, not to this package.
21
+
22
+ ## What you install
23
+
24
+ Most subpaths need nothing beyond `hono` (and `zod` where they validate). These are the only
25
+ ones that statically reach an OPTIONAL peer, or a bun builtin a Worker does not have. The table
26
+ is checked: `src/readmeInstallTable.spec.ts` fails when it disagrees with `src/subpathReach.ts`,
27
+ and `src/barrelsReachNoOptionalPeer.spec.ts` fails when that disagrees with what a bundler
28
+ actually keeps.
29
+
30
+ | subpath | optional peers it imports | bun builtins it needs |
31
+ |---|---|---|
32
+ | `.` | `kysely`, `kysely-bun-sqlite`, `otplib`, `plainjob` | `bun:sqlite` |
33
+ | `./jobs` | `plainjob` | โ€” |
34
+ | `./d1/backup-local` | โ€” | `bun:sqlite` |
35
+ | `./guard` | โ€” | `bun:sqlite` |
36
+ | `./guard/revocations` | โ€” | `bun:sqlite` |
37
+ | `./sqlite` | โ€” | `bun:sqlite` |
38
+ | `./engagement` | โ€” | `bun:sqlite` |
39
+
40
+ `sharp` and `@node-rs/argon2` are loaded lazily (`await import()`), so a missing one breaks only
41
+ the feature that asks for it, not the build.
42
+
43
+ ## ๐Ÿ”ด `Bun.serve`'s `websocket` selects an OVERLOAD
44
+
45
+ It is not an optional field. An options object whose `websocket` may be `undefined` โ€” a
46
+ conditional value or a conditional spread โ€” matches no overload, and TypeScript reports an error
47
+ about the whole options object rather than the one field. Use `serveBun(app, { websocket })`,
48
+ which takes it as a genuinely optional field and makes the two concrete calls itself.
49
+ `src/server/serveBunOverload.spec.ts` runs `tsc` on the trap and goes red when bun's types change.
50
+
51
+ ## Standards
52
+
53
+ `docs/retention.md`, `docs/activity.md`, `docs/notifications.md`, `docs/engagement.md` โ€” the
54
+ contracts every app that uses those modules is agreeing to. Cite them from an app with the
55
+ package prefix (`cursedbelt-server/docs/retention.md`).
56
+
57
+ ## Verifying
58
+
59
+ ```sh
60
+ cd "$FORGE/libs/cursedbelt-server" && bun run verify
61
+ ```
@@ -0,0 +1,32 @@
1
+ /**
2
+ * What each public subpath of this package REACHES that a consumer may not have โ€” the optional
3
+ * peers it imports statically, and the bun builtins it needs.
4
+ *
5
+ * Two specs read these tables and neither may drift from the other:
6
+ * ยท `barrelsReachNoOptionalPeer.spec.ts` bundles every subpath and fails when one reaches
7
+ * something its row does not list, or a row lists something it no longer reaches;
8
+ * ยท `readmeInstallTable.spec.ts` fails when README.md's "What you install" table disagrees
9
+ * with these rows โ€” the table an importing app actually reads (task 104).
10
+ * Not exported from package.json: this is the check's data, not an API.
11
+ */
12
+ /**
13
+ * The subpaths whose whole PURPOSE is the optional peer they pull, mapped to what they are
14
+ * allowed to pull and why. An entry here is a promise that the peer is the point of the
15
+ * subpath โ€” not a place to park a new drag.
16
+ *
17
+ * ๐Ÿ”ด Measured 2026-09-18: these three are the ONLY subpaths in the map that reach an
18
+ * optional peer. Everything else is clean, which is what makes this list an allowlist
19
+ * rather than a baseline โ€” it can only shrink.
20
+ */
21
+ export declare const MAY_DRAG: Record<string, readonly string[]>;
22
+ /**
23
+ * The subpaths whose whole PURPOSE is a bun-specific runtime, mapped to the builtins they
24
+ * are allowed to reach. Everything NOT listed here must be importable on `workerd`, which
25
+ * is the property the D1 seam exists to deliver.
26
+ *
27
+ * ๐Ÿ”ด Measured 2026-09-18 after the `./d1/backup-local` split: these five are the ONLY
28
+ * subpaths in the map that reach a bun builtin at all. Like {@link MAY_DRAG} it is an
29
+ * allowlist, not a baseline โ€” it can only shrink, and an entry that stops being true is a
30
+ * failure rather than a tidy-up.
31
+ */
32
+ export declare const MAY_NEED_BUN: Record<string, readonly string[]>;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * What each public subpath of this package REACHES that a consumer may not have โ€” the optional
3
+ * peers it imports statically, and the bun builtins it needs.
4
+ *
5
+ * Two specs read these tables and neither may drift from the other:
6
+ * ยท `barrelsReachNoOptionalPeer.spec.ts` bundles every subpath and fails when one reaches
7
+ * something its row does not list, or a row lists something it no longer reaches;
8
+ * ยท `readmeInstallTable.spec.ts` fails when README.md's "What you install" table disagrees
9
+ * with these rows โ€” the table an importing app actually reads (task 104).
10
+ * Not exported from package.json: this is the check's data, not an API.
11
+ */
12
+ /**
13
+ * The subpaths whose whole PURPOSE is the optional peer they pull, mapped to what they are
14
+ * allowed to pull and why. An entry here is a promise that the peer is the point of the
15
+ * subpath โ€” not a place to park a new drag.
16
+ *
17
+ * ๐Ÿ”ด Measured 2026-09-18: these three are the ONLY subpaths in the map that reach an
18
+ * optional peer. Everything else is clean, which is what makes this list an allowlist
19
+ * rather than a baseline โ€” it can only shrink.
20
+ */
21
+ export const MAY_DRAG = {
22
+ // The whole-package barrel. It exists to re-export everything, so it necessarily reaches
23
+ // what the specific subpaths reach. An app importing `cursedbelt-server` whole is asking
24
+ // for that; an app importing `cursedbelt-server/d1` is not, and that is the distinction
25
+ // this spec protects.
26
+ '.': ['kysely', 'kysely-bun-sqlite', 'otplib', 'plainjob'],
27
+ // `./d1/kysely` (`createD1Kysely`) was here until 4.19.0, when it was removed: no file in
28
+ // the generation had ever imported it (task 472's census), so the allowance went with it.
29
+ // The job queue is plainjob. Nothing else here is.
30
+ './jobs': ['plainjob'],
31
+ };
32
+ /**
33
+ * The subpaths whose whole PURPOSE is a bun-specific runtime, mapped to the builtins they
34
+ * are allowed to reach. Everything NOT listed here must be importable on `workerd`, which
35
+ * is the property the D1 seam exists to deliver.
36
+ *
37
+ * ๐Ÿ”ด Measured 2026-09-18 after the `./d1/backup-local` split: these five are the ONLY
38
+ * subpaths in the map that reach a bun builtin at all. Like {@link MAY_DRAG} it is an
39
+ * allowlist, not a baseline โ€” it can only shrink, and an entry that stops being true is a
40
+ * failure rather than a tidy-up.
41
+ */
42
+ export const MAY_NEED_BUN = {
43
+ // The whole-package barrel โ€” it re-exports everything, including the Mac-hosted halves.
44
+ // An app importing `cursedbelt-server` whole is a Bun server by construction; an app
45
+ // importing `cursedbelt-server/d1` is not, and that is the distinction this protects.
46
+ '.': ['bun:sqlite'],
47
+ // The local backup IS `VACUUM INTO` against a live `bun:sqlite` handle. Split out of
48
+ // `./d1` on 2026-09-18 precisely so the seam itself stops paying for it โ€” see the header.
49
+ './d1/backup-local': ['bun:sqlite'],
50
+ // The guard's revocation store is a `bun:sqlite` table, and `./guard` mounts it.
51
+ './guard': ['bun:sqlite'],
52
+ './guard/revocations': ['bun:sqlite'],
53
+ // `./sqlite` is the bun:sqlite tier. Naming it is the point of the subpath.
54
+ './sqlite': ['bun:sqlite'],
55
+ // The engagement store IS `engagement.sqlite` on the Mac (4.19.0, task 280) โ€” the recorder
56
+ // runs where the app's own database is. A Worker app records nothing until it has a D1 store.
57
+ './engagement': ['bun:sqlite'],
58
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursedbelt-server",
3
- "version": "4.19.0",
3
+ "version": "4.19.1",
4
4
  "license": "ISC",
5
5
  "type": "module",
6
6
  "description": "The app-facing Bun/Hono server tier of the cursedbelt split โ€” storage, sharing, activity, guard, sync. React-free; cursedbelt-core below it.",
@@ -29,7 +29,9 @@
29
29
  "files": [
30
30
  "dist",
31
31
  "src",
32
- "docs"
32
+ "docs",
33
+ "README.md",
34
+ "!src/server/typetests"
33
35
  ],
34
36
  "exports": {
35
37
  ".": {
@@ -287,7 +289,7 @@
287
289
  },
288
290
  "dependencies": {
289
291
  "cursedbelt-core": "^2.1.1",
290
- "cursedops": "^0.4.0",
292
+ "cursedops": "^0.5.2",
291
293
  "cwip": "^4.6.0",
292
294
  "jose": "^6.2.3"
293
295
  },
@@ -2,6 +2,7 @@ import { describe, expect, it } from 'bun:test';
2
2
  import { readFileSync } from 'node:fs';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import pkg from '../package.json';
5
+ import { MAY_DRAG, MAY_NEED_BUN } from './subpathReach.js';
5
6
 
6
7
  /**
7
8
  * A public subpath must not STATICALLY drag anything its consumers cannot have โ€” an
@@ -99,55 +100,6 @@ import pkg from '../package.json';
99
100
 
100
101
  const REPO = fileURLToPath(new URL('..', import.meta.url));
101
102
 
102
- /**
103
- * The subpaths whose whole PURPOSE is the optional peer they pull, mapped to what they are
104
- * allowed to pull and why. An entry here is a promise that the peer is the point of the
105
- * subpath โ€” not a place to park a new drag.
106
- *
107
- * ๐Ÿ”ด Measured 2026-09-18: these three are the ONLY subpaths in the map that reach an
108
- * optional peer. Everything else is clean, which is what makes this list an allowlist
109
- * rather than a baseline โ€” it can only shrink.
110
- */
111
- const MAY_DRAG: Record<string, readonly string[]> = {
112
- // The whole-package barrel. It exists to re-export everything, so it necessarily reaches
113
- // what the specific subpaths reach. An app importing `cursedbelt-server` whole is asking
114
- // for that; an app importing `cursedbelt-server/d1` is not, and that is the distinction
115
- // this spec protects.
116
- '.': ['kysely', 'kysely-bun-sqlite', 'otplib', 'plainjob'],
117
- // `./d1/kysely` (`createD1Kysely`) was here until 4.19.0, when it was removed: no file in
118
- // the generation had ever imported it (task 472's census), so the allowance went with it.
119
- // The job queue is plainjob. Nothing else here is.
120
- './jobs': ['plainjob'],
121
- };
122
-
123
- /**
124
- * The subpaths whose whole PURPOSE is a bun-specific runtime, mapped to the builtins they
125
- * are allowed to reach. Everything NOT listed here must be importable on `workerd`, which
126
- * is the property the D1 seam exists to deliver.
127
- *
128
- * ๐Ÿ”ด Measured 2026-09-18 after the `./d1/backup-local` split: these five are the ONLY
129
- * subpaths in the map that reach a bun builtin at all. Like {@link MAY_DRAG} it is an
130
- * allowlist, not a baseline โ€” it can only shrink, and an entry that stops being true is a
131
- * failure rather than a tidy-up.
132
- */
133
- const MAY_NEED_BUN: Record<string, readonly string[]> = {
134
- // The whole-package barrel โ€” it re-exports everything, including the Mac-hosted halves.
135
- // An app importing `cursedbelt-server` whole is a Bun server by construction; an app
136
- // importing `cursedbelt-server/d1` is not, and that is the distinction this protects.
137
- '.': ['bun:sqlite'],
138
- // The local backup IS `VACUUM INTO` against a live `bun:sqlite` handle. Split out of
139
- // `./d1` on 2026-09-18 precisely so the seam itself stops paying for it โ€” see the header.
140
- './d1/backup-local': ['bun:sqlite'],
141
- // The guard's revocation store is a `bun:sqlite` table, and `./guard` mounts it.
142
- './guard': ['bun:sqlite'],
143
- './guard/revocations': ['bun:sqlite'],
144
- // `./sqlite` is the bun:sqlite tier. Naming it is the point of the subpath.
145
- './sqlite': ['bun:sqlite'],
146
- // The engagement store IS `engagement.sqlite` on the Mac (4.19.0, task 280) โ€” the recorder
147
- // runs where the app's own database is. A Worker app records nothing until it has a D1 store.
148
- './engagement': ['bun:sqlite'],
149
- };
150
-
151
103
  const OPTIONAL_PEERS = new Set(
152
104
  Object.entries(
153
105
  (pkg as { peerDependenciesMeta?: Record<string, { optional?: boolean }> }).peerDependenciesMeta ?? {},
@@ -0,0 +1,48 @@
1
+ /**
2
+ * README.md's "What you install" table is what an importing app reads (task 104) โ€” so it is held
3
+ * to the same data the bundler check proves (`subpathReach.ts`), row for row, in both directions.
4
+ */
5
+ import { describe, expect, it } from 'bun:test';
6
+ import { readFileSync } from 'node:fs';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { MAY_DRAG, MAY_NEED_BUN } from './subpathReach.js';
9
+
10
+ const README = readFileSync(fileURLToPath(new URL('../README.md', import.meta.url)), 'utf8');
11
+
12
+ /** `| \`./x\` | \`a\`, \`b\` | โ€” |` โ†’ { './x': { peers: [a, b], bun: [] } } */
13
+ function tableRows(): Map<string, { peers: string[]; bun: string[] }> {
14
+ const section = README.split('## What you install')[1]?.split('\n## ')[0] ?? '';
15
+ const rows = new Map<string, { peers: string[]; bun: string[] }>();
16
+ for (const line of section.split('\n')) {
17
+ const cells = line.split('|').map((c) => c.trim());
18
+ const subpath = /^`(\.[^`]*)`$/.exec(cells[1] ?? '')?.[1];
19
+ if (!subpath) continue;
20
+ const named = (cell: string | undefined) => [...(cell ?? '').matchAll(/`([^`]+)`/g)].map((m) => m[1] as string).sort();
21
+ rows.set(subpath, { peers: named(cells[2]), bun: named(cells[3]) });
22
+ }
23
+ return rows;
24
+ }
25
+
26
+ describe('README install table', () => {
27
+ const rows = tableRows();
28
+ const expected = new Set([...Object.keys(MAY_DRAG), ...Object.keys(MAY_NEED_BUN)]);
29
+
30
+ it('has a row for every subpath that reaches an optional peer or a bun builtin โ€” and no other', () => {
31
+ expect(rows.size).toBeGreaterThan(0);
32
+ expect([...rows.keys()].sort()).toEqual([...expected].sort());
33
+ });
34
+
35
+ it('names exactly what each subpath reaches', () => {
36
+ for (const subpath of expected) {
37
+ expect({ subpath, ...rows.get(subpath) }).toEqual({
38
+ subpath,
39
+ peers: [...(MAY_DRAG[subpath] ?? [])].sort(),
40
+ bun: [...(MAY_NEED_BUN[subpath] ?? [])].sort(),
41
+ });
42
+ }
43
+ });
44
+
45
+ it('warns that the root export is a barrel', () => {
46
+ expect(README).toContain('The root export is a BARREL');
47
+ });
48
+ });
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Task 104, trap 3 โ€” `Bun.serve`'s `websocket` selects an OVERLOAD; it is not an optional field.
3
+ *
4
+ * A compiler fact, so its honest carrier is the compiler: `typetests/serveBunOverload.ts` holds the
5
+ * trap under `@ts-expect-error` and the seam (`serveBun`) without one, and this spec runs `tsc` on
6
+ * it. Two ways it goes red, both meaningful:
7
+ *
8
+ * ยท bun's types stop rejecting a maybe-undefined `websocket` โ†’ the `@ts-expect-error` is UNUSED
9
+ * and tsc fails: the trap is gone, so the note in README.md and the fixture can go too;
10
+ * ยท `serveBun` stops accepting an optional `websocket` โ†’ the seam line fails: the one thing an
11
+ * app is told to use instead has itself grown the trap.
12
+ *
13
+ * The second half proves the first can fire: the same fixture with the directives stripped must
14
+ * fail with TS2345 on exactly the two trap lines.
15
+ */
16
+ import { describe, expect, it } from 'bun:test';
17
+ import { readFileSync, rmSync, writeFileSync } from 'node:fs';
18
+ import { fileURLToPath } from 'node:url';
19
+
20
+ const DIR = fileURLToPath(new URL('./typetests/', import.meta.url));
21
+ const TSC = fileURLToPath(new URL('../../node_modules/typescript/bin/tsc', import.meta.url));
22
+
23
+ const tsc = (project: string) => {
24
+ const run = Bun.spawnSync(['bun', TSC, '-p', project], { stdout: 'pipe', stderr: 'pipe' });
25
+ return { code: run.exitCode, out: `${run.stdout.toString()}${run.stderr.toString()}` };
26
+ };
27
+
28
+ describe("Bun.serve's websocket overload", () => {
29
+ it('the trap is still live in bunโ€™s types, and serveBun is the seam that never meets it', () => {
30
+ const run = tsc(`${DIR}tsconfig.json`);
31
+ expect(run.out).toBe('');
32
+ expect(run.code).toBe(0);
33
+ }, 60_000);
34
+
35
+ it('๐Ÿ”ด without the directives the SAME lines fail โ€” so the check above can go red', () => {
36
+ const source = readFileSync(`${DIR}serveBunOverload.ts`, 'utf8').replace(/^\s*\/\/ @ts-expect-error.*$/gm, '');
37
+ const file = `${DIR}serveBunOverload.stripped.ts`;
38
+ const project = `${DIR}tsconfig.stripped.json`;
39
+ writeFileSync(file, source);
40
+ writeFileSync(project, JSON.stringify({ extends: './tsconfig.json', include: ['serveBunOverload.stripped.ts'] }));
41
+ try {
42
+ const run = tsc(project);
43
+ expect(run.code).not.toBe(0);
44
+ const errors = run.out.split('\n').filter((line) => line.includes('error TS'));
45
+ expect(errors).toHaveLength(2);
46
+ for (const line of errors) expect(line).toContain('TS2345');
47
+ } finally {
48
+ rmSync(file, { force: true });
49
+ rmSync(project, { force: true });
50
+ }
51
+ }, 60_000);
52
+ });
@@ -0,0 +1,61 @@
1
+ /**
2
+ * What each public subpath of this package REACHES that a consumer may not have โ€” the optional
3
+ * peers it imports statically, and the bun builtins it needs.
4
+ *
5
+ * Two specs read these tables and neither may drift from the other:
6
+ * ยท `barrelsReachNoOptionalPeer.spec.ts` bundles every subpath and fails when one reaches
7
+ * something its row does not list, or a row lists something it no longer reaches;
8
+ * ยท `readmeInstallTable.spec.ts` fails when README.md's "What you install" table disagrees
9
+ * with these rows โ€” the table an importing app actually reads (task 104).
10
+ * Not exported from package.json: this is the check's data, not an API.
11
+ */
12
+
13
+ /**
14
+ * The subpaths whose whole PURPOSE is the optional peer they pull, mapped to what they are
15
+ * allowed to pull and why. An entry here is a promise that the peer is the point of the
16
+ * subpath โ€” not a place to park a new drag.
17
+ *
18
+ * ๐Ÿ”ด Measured 2026-09-18: these three are the ONLY subpaths in the map that reach an
19
+ * optional peer. Everything else is clean, which is what makes this list an allowlist
20
+ * rather than a baseline โ€” it can only shrink.
21
+ */
22
+ export const MAY_DRAG: Record<string, readonly string[]> = {
23
+ // The whole-package barrel. It exists to re-export everything, so it necessarily reaches
24
+ // what the specific subpaths reach. An app importing `cursedbelt-server` whole is asking
25
+ // for that; an app importing `cursedbelt-server/d1` is not, and that is the distinction
26
+ // this spec protects.
27
+ '.': ['kysely', 'kysely-bun-sqlite', 'otplib', 'plainjob'],
28
+ // `./d1/kysely` (`createD1Kysely`) was here until 4.19.0, when it was removed: no file in
29
+ // the generation had ever imported it (task 472's census), so the allowance went with it.
30
+ // The job queue is plainjob. Nothing else here is.
31
+ './jobs': ['plainjob'],
32
+ };
33
+
34
+ /**
35
+ * The subpaths whose whole PURPOSE is a bun-specific runtime, mapped to the builtins they
36
+ * are allowed to reach. Everything NOT listed here must be importable on `workerd`, which
37
+ * is the property the D1 seam exists to deliver.
38
+ *
39
+ * ๐Ÿ”ด Measured 2026-09-18 after the `./d1/backup-local` split: these five are the ONLY
40
+ * subpaths in the map that reach a bun builtin at all. Like {@link MAY_DRAG} it is an
41
+ * allowlist, not a baseline โ€” it can only shrink, and an entry that stops being true is a
42
+ * failure rather than a tidy-up.
43
+ */
44
+ export const MAY_NEED_BUN: Record<string, readonly string[]> = {
45
+ // The whole-package barrel โ€” it re-exports everything, including the Mac-hosted halves.
46
+ // An app importing `cursedbelt-server` whole is a Bun server by construction; an app
47
+ // importing `cursedbelt-server/d1` is not, and that is the distinction this protects.
48
+ '.': ['bun:sqlite'],
49
+ // The local backup IS `VACUUM INTO` against a live `bun:sqlite` handle. Split out of
50
+ // `./d1` on 2026-09-18 precisely so the seam itself stops paying for it โ€” see the header.
51
+ './d1/backup-local': ['bun:sqlite'],
52
+ // The guard's revocation store is a `bun:sqlite` table, and `./guard` mounts it.
53
+ './guard': ['bun:sqlite'],
54
+ './guard/revocations': ['bun:sqlite'],
55
+ // `./sqlite` is the bun:sqlite tier. Naming it is the point of the subpath.
56
+ './sqlite': ['bun:sqlite'],
57
+ // The engagement store IS `engagement.sqlite` on the Mac (4.19.0, task 280) โ€” the recorder
58
+ // runs where the app's own database is. A Worker app records nothing until it has a D1 store.
59
+ './engagement': ['bun:sqlite'],
60
+ };
61
+