diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 4bcc31b..7dec469 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -34,16 +34,22 @@ jobs:
- run: bun install --frozen-lockfile
+ # Emits dist/, which is what the reference host and the typecheck
+ # resolve @corbits/mailbox through — so everything downstream proves
+ # the published artifact, not just the sources.
+ - name: build
+ run: bun run build
+
+ # bun copies `file:` dependencies at install time, before dist/ exists;
+ # a forced reinstall refreshes the copy with the built output.
+ - name: refresh reference-host dependency copy
+ run: bun install --force
+
- name: typecheck
run: bun run typecheck
- name: unit + integration tests
- run: bun run --cwd packages/mailbox test:coverage
-
- # Emits dist/, which is also what the reference host resolves through —
- # so this proves the published artifact, not just the sources.
- - name: build
- run: bun run build
+ run: bun run test:coverage
- name: reference-host acceptance
run: bun test --cwd examples/reference-host
@@ -54,8 +60,7 @@ jobs:
- name: node consumer smoke test
run: |
set -euo pipefail
- TARBALL="$(cd packages/mailbox && npm pack --silent)"
- TARBALL="$PWD/packages/mailbox/$TARBALL"
+ TARBALL="$PWD/$(npm pack --silent)"
mkdir -p "$RUNNER_TEMP/consumer" && cd "$RUNNER_TEMP/consumer"
npm init -y >/dev/null && npm pkg set type=module >/dev/null
npm install "$TARBALL"
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index 073efac..7219477 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -2,7 +2,7 @@
How `@corbits/mailbox` is put together, and what it does and does not ask of
the host that mounts it. For install, the mount snippet, the route table and the
-response contracts, see the [package README](./packages/mailbox/README.md) —
+response contracts, see the [README](./README.md) —
this document is about structure and reasoning, and does not repeat them.
## The shape of the thing
diff --git a/README.md b/README.md
index 17d204a..357ddc4 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,30 @@
# corbits-mailbox
-Home of **[`@corbits/mailbox`](./packages/mailbox)** — a universal,
-principal-keyed inbox, mountable onto a Hono host backed by an Interchange-shaped
-Postgres. Its tables live in a dedicated `mailbox` schema in the host's database,
-foreign-keyed to the host's `tenant` and `principal` tables. Backend only; this
-package ships no UI.
+**[`@corbits/mailbox`](./package.json)** — a universal, principal-keyed inbox,
+mountable onto a Hono host backed by an Interchange-shaped Postgres. Its tables
+live in a dedicated `mailbox` schema in the host's database, foreign-keyed to
+the host's `tenant` and `principal` tables. Backend only; this package ships no
+UI.
-See the [package README](./packages/mailbox/README.md) for install, the mount
-snippet and the seams, and [ARCHITECTURE.md](./ARCHITECTURE.md) for the data model.
+Requires `@intx` 0.2.2 or newer.
+
+See [ARCHITECTURE.md](./ARCHITECTURE.md) for the data model.
+
+## Install
+
+```sh
+# from npm (ships prebuilt dist/)
+bun add @corbits/mailbox
+
+# from git (prepare hook builds dist/ on the way in)
+bun add github:corbitsdev/corbits-mailbox
+```
## Layout
| | |
| --- | --- |
-| `packages/mailbox` | The published package. Owns `principal_mail` (the message, immutable) and `mailbox` (the management layer, created eagerly with each message). |
+| `src/` | The published package. Owns `principal_mail` (the message, immutable) and `mailbox` (the management layer, created eagerly with each message). |
| `examples/reference-host` | Mounts it on a real `@intx/hub-api` app against a live Postgres and asserts the acceptance scenarios end to end. |
## Working on it
@@ -23,8 +34,8 @@ bun install
docker run -d --name mailbox-pg -p 5433:5432 \
-e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=mailbox_core postgres:16
-bun run --cwd packages/mailbox test # unit + integration
-bun run --cwd packages/mailbox build # dist/ (JS + .d.ts)
+bun run test # unit + integration
+bun run build # dist/ (JS + .d.ts)
bun test --cwd examples/reference-host # acceptance scenarios
```
diff --git a/bun.lock b/bun.lock
index fcd488b..785ac9b 100644
--- a/bun.lock
+++ b/bun.lock
@@ -3,32 +3,7 @@
"configVersion": 1,
"workspaces": {
"": {
- "name": "corbits-mailbox",
- "devDependencies": {
- "@types/bun": "1.1.14",
- "@types/node": "22.10.5",
- "typescript": "5.7.2",
- },
- },
- "examples/reference-host": {
- "name": "reference-host",
- "dependencies": {
- "@corbits/mailbox": "workspace:*",
- "@intx/db": "0.2.2",
- "@intx/hub-api": "0.2.2",
- "@intx/hub-sessions": "0.2.2",
- "drizzle-orm": "0.45.2",
- "hono": "4.12.32",
- "postgres": "3.4.9",
- },
- "devDependencies": {
- "@types/bun": "1.1.14",
- "typescript": "5.7.2",
- },
- },
- "packages/mailbox": {
"name": "@corbits/mailbox",
- "version": "0.1.0",
"dependencies": {
"@hono/standard-validator": "0.2.3",
"@standard-community/standard-json": "0.3.5",
@@ -58,6 +33,22 @@
"postgres": "^3.4.0",
},
},
+ "examples/reference-host": {
+ "name": "reference-host",
+ "dependencies": {
+ "@corbits/mailbox": "file:../..",
+ "@intx/db": "0.2.2",
+ "@intx/hub-api": "0.2.2",
+ "@intx/hub-sessions": "0.2.2",
+ "drizzle-orm": "0.45.2",
+ "hono": "4.12.32",
+ "postgres": "3.4.9",
+ },
+ "devDependencies": {
+ "@types/bun": "1.1.14",
+ "typescript": "5.7.2",
+ },
+ },
},
"overrides": {
"drizzle-orm": "0.45.2",
@@ -67,25 +58,25 @@
"@ark/util": ["@ark/util@0.56.0", "", {}, "sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA=="],
- "@better-auth/core": ["@better-auth/core@1.6.25", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.1", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.3.7", "jose": "^6.1.0", "kysely": "^0.28.5 || ^0.29.0", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types", "@opentelemetry/api"] }, "sha512-lMTlhtwyK4NpY9kPF+2rQCRKYpg136d3gM2xl8esxT1PjJx5Nh5YwZvxcYCIjDuO759sx6TCloJTuwcZGG6ZBw=="],
+ "@better-auth/core": ["@better-auth/core@1.6.26", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.1", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.3.7", "jose": "^6.1.0", "kysely": "^0.28.5 || ^0.29.0", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types", "@opentelemetry/api"] }, "sha512-Ud4FqnjIJDvmeo+3bN+OsuVVAiuvFjNERbW7G8/65ictSxCox62gNgTOsJ4YXmbwXzBzyyekStzrupg2qNGEkA=="],
- "@better-auth/drizzle-adapter": ["@better-auth/drizzle-adapter@1.6.25", "", { "peerDependencies": { "@better-auth/core": "^1.6.25", "@better-auth/utils": "0.4.2", "drizzle-orm": "^0.45.2" }, "optionalPeers": ["drizzle-orm"] }, "sha512-ru/DeKjFPQUVeKkxF/ScazmPqIY7lwfkAV5Yt4j24wmn1Y8vFwoiPRnHgXUeZqBs10+nubaRwEqLF39CP6EhRw=="],
+ "@better-auth/drizzle-adapter": ["@better-auth/drizzle-adapter@1.6.26", "", { "peerDependencies": { "@better-auth/core": "^1.6.26", "@better-auth/utils": "0.4.2", "drizzle-orm": "^0.45.2" }, "optionalPeers": ["drizzle-orm"] }, "sha512-SMvAeeUqEsz0BLtWVVp+HdStAVOwcxs4vPkL/AVmFJ0e9dDKItXl881xWeB/Ze4NgBW6NYmUId9qI4LrdQu3hA=="],
- "@better-auth/kysely-adapter": ["@better-auth/kysely-adapter@1.6.25", "", { "peerDependencies": { "@better-auth/core": "^1.6.25", "@better-auth/utils": "0.4.2", "kysely": "^0.28.17 || ^0.29.0" }, "optionalPeers": ["kysely"] }, "sha512-zxiePhtN1YClS1irKYPVwWfN6kYp+QoYlz1hdQUOj8hXyo2aE/ny4RNAb6v332b0+U6Vu88EhYITRPdmvCo6uA=="],
+ "@better-auth/kysely-adapter": ["@better-auth/kysely-adapter@1.6.26", "", { "peerDependencies": { "@better-auth/core": "^1.6.26", "@better-auth/utils": "0.4.2", "kysely": "^0.28.17 || ^0.29.0" }, "optionalPeers": ["kysely"] }, "sha512-Y0Kdqn8JQMR8dHMtUnbBNqq7Mk8mDf18DwTves2uhjtRfcOWByVTGxsFMDVUbedMuoM+Ab+v04bwCk94sAo8NA=="],
- "@better-auth/memory-adapter": ["@better-auth/memory-adapter@1.6.25", "", { "peerDependencies": { "@better-auth/core": "^1.6.25", "@better-auth/utils": "0.4.2" } }, "sha512-GhEzTumc8yfTz+OZ6pMg06BA49xob49x1bX+1mEl/FStDJoSF+6mTfI5M2ytFxaiN89336/aUjkW8u+qRyLexw=="],
+ "@better-auth/memory-adapter": ["@better-auth/memory-adapter@1.6.26", "", { "peerDependencies": { "@better-auth/core": "^1.6.26", "@better-auth/utils": "0.4.2" } }, "sha512-kb5ahphEp9jyMleXU4T8I/xgWPa8gjOLFKKsnaVqR/BU+h7xqTOsjJwTY3evY+PFYHvjNFUlrjZ1Km8A/w1p/w=="],
- "@better-auth/mongo-adapter": ["@better-auth/mongo-adapter@1.6.25", "", { "peerDependencies": { "@better-auth/core": "^1.6.25", "@better-auth/utils": "0.4.2", "mongodb": "^6.0.0 || ^7.0.0" }, "optionalPeers": ["mongodb"] }, "sha512-ZtMmjcOdXR2Ziqx5y8ptTOaNpe0snNfALbBUPXJsgeyeRkDJDYzyLZ8MpuvNBTNllNeIFDbiXWAK5k+pEBZrUQ=="],
+ "@better-auth/mongo-adapter": ["@better-auth/mongo-adapter@1.6.26", "", { "peerDependencies": { "@better-auth/core": "^1.6.26", "@better-auth/utils": "0.4.2", "mongodb": "^6.0.0 || ^7.0.0" }, "optionalPeers": ["mongodb"] }, "sha512-jYGhuIQqj48h69ROLPYdc8jgqDaMmRRLLFiRViZFYarNyBuYUV07DywJXvRuw8l9ADx+M1TB+BJZGnLE2nnmwQ=="],
- "@better-auth/prisma-adapter": ["@better-auth/prisma-adapter@1.6.25", "", { "peerDependencies": { "@better-auth/core": "^1.6.25", "@better-auth/utils": "0.4.2", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@prisma/client", "prisma"] }, "sha512-ym7B6Iqcry+/4aQnYpFwqP/GBIiXvjrm/5B6+0qmx8mkTY/apHFTpHuGzUYYNf4vPTtzF3eYY2+s2GOsomKaRg=="],
+ "@better-auth/prisma-adapter": ["@better-auth/prisma-adapter@1.6.26", "", { "peerDependencies": { "@better-auth/core": "^1.6.26", "@better-auth/utils": "0.4.2", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@prisma/client", "prisma"] }, "sha512-ILQoYmnoYyghDP4iN1h/Gkd9Jt7Xs/vxoNAt4AuuJJbqs73u27LlQM4P1YiP7nJ+bM1iVTBI1dDF73myIAtjzQ=="],
- "@better-auth/telemetry": ["@better-auth/telemetry@1.6.25", "", { "peerDependencies": { "@better-auth/core": "^1.6.25", "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.1" } }, "sha512-2ZfC9lp7tU6Jw/q2Lz/bKfQqGMdMwc/IQDTYdBhvtGi24qInYVnhp2ZCW57hHM9j+fq1ULOtxgg6M3T1LEaihw=="],
+ "@better-auth/telemetry": ["@better-auth/telemetry@1.6.26", "", { "peerDependencies": { "@better-auth/core": "^1.6.26", "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.1" } }, "sha512-pBs69RORUSJHRF9r5PdeY7pzLRga09YSWew8igFYrBvT2tLt7DktrbD5WjryLZpAZOuKAMZ+Xo0DlJbnvLWGpQ=="],
"@better-auth/utils": ["@better-auth/utils@0.4.2", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-AUxrvu+HaaODsUyzDxFgwd/8RZ1yZaYo42LXKSrU2oGgR38pS1ij8nqQKNgtTWoYGpNevNXtCfgTy6loHveW9A=="],
"@better-fetch/fetch": ["@better-fetch/fetch@1.3.1", "", {}, "sha512-ABkD1WhyfPZprKRQI3bhATjeiFuNWC9PXhfGWqL+sg/gKrM977oFrYkdb4msM3hgUGonr7KlOsOFT5TU2rht9g=="],
- "@corbits/mailbox": ["@corbits/mailbox@workspace:packages/mailbox"],
+ "@corbits/mailbox": ["@corbits/mailbox@root:", {}],
"@gar/promise-retry": ["@gar/promise-retry@1.0.3", "", {}, "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA=="],
@@ -125,13 +116,13 @@
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
- "@logtape/hono": ["@logtape/hono@2.2.4", "", { "peerDependencies": { "@logtape/logtape": "^2.2.4", "hono": "^4.0.0" } }, "sha512-y0iKPQ4vOS+s3C2sj40juY7DtyYGnylvdWi1s6J7+5hE3RyW5jBNDax+XiXPEJnVBMg5zU5MHR7dbqGvmPu1hg=="],
+ "@logtape/hono": ["@logtape/hono@2.3.0", "", { "peerDependencies": { "@logtape/logtape": "^2.3.0", "hono": "^4.0.0" } }, "sha512-+aYoPfhEeOVW1hiQwx7Tv2fbNSbOPQhx5EBZ/8JXAXt9j1w0h63X0M/MjyhjBh6Mp22gnOdNLTF6l5Tg2RIcJw=="],
- "@logtape/logtape": ["@logtape/logtape@2.2.4", "", {}, "sha512-2rALzv9m4ibE5FyB8/FMm5MPMMlK7ujgy3ufricVKIxj6e7SZbw4w6J16/fRAsXgdDlOXPUA0aa6XoEQvdlKxw=="],
+ "@logtape/logtape": ["@logtape/logtape@2.3.0", "", {}, "sha512-s/pxCgf9Gg75ypTV/bRUq355Dy/JP/zUJWfgJQPO5pTkXC23X2AXBcmBBgFiWH+bSi4yiX/G06qGV8CdAWD3SA=="],
- "@noble/ciphers": ["@noble/ciphers@2.2.0", "", {}, "sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA=="],
+ "@noble/ciphers": ["@noble/ciphers@2.3.0", "", {}, "sha512-Clu/xdfgVTf9o7ngLOURaxePwR0j8sjclKEtVij10/jGulwFsPWCvvRgG/XjUVf8Nei+jLG6uwyXzUTGY1DQrw=="],
- "@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="],
+ "@noble/hashes": ["@noble/hashes@2.3.0", "", {}, "sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ=="],
"@npmcli/agent": ["@npmcli/agent@4.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-EUEuWAxnL07Sp5/iC/1X6Xj+XThUvnbei9zfRWZdEXa7lss9RTHMhAHBeg+MZ5To9s/gGaSI+UwZTPdYMvKSeg=="],
@@ -171,11 +162,11 @@
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
- "better-auth": ["better-auth@1.6.25", "", { "dependencies": { "@better-auth/core": "1.6.25", "@better-auth/drizzle-adapter": "1.6.25", "@better-auth/kysely-adapter": "1.6.25", "@better-auth/memory-adapter": "1.6.25", "@better-auth/mongo-adapter": "1.6.25", "@better-auth/prisma-adapter": "1.6.25", "@better-auth/telemetry": "1.6.25", "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.1", "@noble/ciphers": "^2.1.1", "@noble/hashes": "^2.0.1", "better-call": "1.3.7", "defu": "^6.1.4", "jose": "^6.1.3", "kysely": "^0.28.17 || ^0.29.0", "nanostores": "^1.1.1", "zod": "^4.3.6" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4", "drizzle-orm": "^0.45.2", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-fvoq+oCO+FF5fpP3XfU7znRyGFpHB77UG2EyxsKNy+Cak7Q5pELu+auvvDveQbWQxcoKugZ7jYQQPFQLpUTGOw=="],
+ "better-auth": ["better-auth@1.6.26", "", { "dependencies": { "@better-auth/core": "1.6.26", "@better-auth/drizzle-adapter": "1.6.26", "@better-auth/kysely-adapter": "1.6.26", "@better-auth/memory-adapter": "1.6.26", "@better-auth/mongo-adapter": "1.6.26", "@better-auth/prisma-adapter": "1.6.26", "@better-auth/telemetry": "1.6.26", "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.1", "@noble/ciphers": "^2.1.1", "@noble/hashes": "^2.0.1", "better-call": "1.3.7", "defu": "^6.1.4", "jose": "^6.1.3", "kysely": "^0.28.17 || ^0.29.0", "nanostores": "^1.1.1", "zod": "^4.3.6" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4", "drizzle-orm": "^0.45.2", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-nhXWrDDj+EnZsHq1j0z1c6DowOMFZWZHe6LCaXbBfLIgHHZm6dyazBQcbRspM4spUIcUt250Mc1BFOSuP7eniQ=="],
"better-call": ["better-call@1.3.7", "", { "dependencies": { "@better-auth/utils": "^0.4.0", "@better-fetch/fetch": "^1.1.21", "rou3": "^0.7.12", "set-cookie-parser": "^3.0.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-Al51/hjp2SSp6CRTa3F2ptcx4yQVS1xWKoY6jcVXqNYOap6mHFP2jUBn5EwIL4iIed1/Sq4hlQ+Umm6EflZG+w=="],
- "brace-expansion": ["brace-expansion@5.0.8", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg=="],
+ "brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="],
"buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
@@ -195,7 +186,7 @@
"crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="],
- "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
@@ -261,7 +252,7 @@
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
- "ip-address": ["ip-address@10.3.1", "", {}, "sha512-1e9d3kb97NHJTIJDZW9rKqW2h6+dFa50Dy0fpPSMQp2ADje5gvKsXmdiK6dwY5t76TaTt5+P5N1Y/LoToIxP6g=="],
+ "ip-address": ["ip-address@10.4.0", "", {}, "sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ=="],
"is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="],
@@ -269,9 +260,9 @@
"isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
- "isomorphic-git": ["isomorphic-git@1.40.0", "", { "dependencies": { "async-lock": "^1.4.1", "clean-git-ref": "^2.0.1", "crc-32": "^1.2.0", "diff3": "0.0.3", "ignore": "^5.1.4", "minimisted": "^2.0.0", "pako": "^1.0.10", "pify": "^4.0.1", "readable-stream": "^4.0.0", "sha.js": "^2.4.12", "simple-get": "^4.0.1" }, "bin": { "isogit": "cli.cjs" } }, "sha512-/CbnxwZqIm17y3c/z0INbkgEKSvFerXtO/NGgaRxZ8nvL3eoMtbjuAS7f4Pj7lZzj8HaultvDD1ClJTBVDl89g=="],
+ "isomorphic-git": ["isomorphic-git@1.41.0", "", { "dependencies": { "async-lock": "^1.4.1", "clean-git-ref": "^2.0.1", "crc-32": "^1.2.0", "diff3": "0.0.3", "ignore": "^5.1.4", "minimisted": "^2.0.0", "pako": "^1.0.10", "pify": "^4.0.1", "readable-stream": "^4.0.0", "sha.js": "^2.4.12", "simple-get": "^4.0.1" }, "bin": { "isogit": "cli.cjs" } }, "sha512-YADpOKD/pLemtcyZ9jssNXnPVhfDObGl/BAKMtvmU17svgNzOKTT6AHX68DzFHpie5hAZHRtutC0Cka3lYdmBA=="],
- "jose": ["jose@6.2.4", "", {}, "sha512-N8acGzVsQy6M/fjFcxtysNc4Q379TcM5dM/qKkNtsHFji88yANnXTr7BLeP75iPnFwBfQzM/jg2BZ9+HZrHCZA=="],
+ "jose": ["jose@6.2.8", "", {}, "sha512-Bsdjwm3Qsd/P0jR+BHDe3LytDfY7WBq2HmCCLIwuVRHMuEC9ae7/R474GIUdF1NgCyZjzVo/A9DOiOBtXq8ZoQ=="],
"jsonparse": ["jsonparse@1.3.1", "", {}, "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="],
@@ -285,7 +276,7 @@
"mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
- "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
+ "minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="],
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
@@ -307,7 +298,7 @@
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
- "nanostores": ["nanostores@1.4.1", "", {}, "sha512-PGd3uPojJB9Z07d5NX3Db/SOSBbyy3wLMUGq0GpnEEJfVzY9mq7daPMAZ3jObV5D3Jn+YKND636eI5ULg7F80Q=="],
+ "nanostores": ["nanostores@1.4.2", "", {}, "sha512-Wxv8Roefr2nqtiRG0bnaFlpYqpIVtOEeJZHaH+4nGgOK1/7n6OHOuHCb/bhqrNQgZM8fyd0s1PqhdrJc9Ib44g=="],
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
diff --git a/packages/mailbox/bunfig.toml b/bunfig.toml
similarity index 100%
rename from packages/mailbox/bunfig.toml
rename to bunfig.toml
diff --git a/examples/reference-host/package.json b/examples/reference-host/package.json
index e803213..2d2f166 100644
--- a/examples/reference-host/package.json
+++ b/examples/reference-host/package.json
@@ -6,7 +6,7 @@
"test": "bun test"
},
"dependencies": {
- "@corbits/mailbox": "workspace:*",
+ "@corbits/mailbox": "file:../..",
"@intx/db": "0.2.2",
"@intx/hub-api": "0.2.2",
"@intx/hub-sessions": "0.2.2",
diff --git a/package.json b/package.json
index 67919f5..63d79c2 100644
--- a/package.json
+++ b/package.json
@@ -1,18 +1,89 @@
{
- "name": "corbits-mailbox",
- "private": true,
- "license": "LGPL-2.1-only",
+ "name": "@corbits/mailbox",
+ "version": "0.1.0",
"type": "module",
- "workspaces": ["packages/*", "examples/*"],
+ "license": "LGPL-2.1-only",
+ "description": "Universal principal-keyed inbox, mountable onto any Interchange host. Requires @intx 0.2.2 or newer.",
+ "keywords": [
+ "corbits",
+ "interchange",
+ "intx",
+ "mailbox",
+ "inbox",
+ "hono",
+ "drizzle",
+ "postgres"
+ ],
+ "author": "Corbits",
+ "homepage": "https://github.com/corbitsdev/corbits-mailbox#readme",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/corbitsdev/corbits-mailbox.git"
+ },
+ "bugs": {
+ "url": "https://github.com/corbitsdev/corbits-mailbox/issues"
+ },
+ "engines": {
+ "node": ">=22"
+ },
+ "corbits": {
+ "minimumIntxVersion": "0.2.2"
+ },
+ "workspaces": [
+ "examples/*"
+ ],
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "default": "./dist/index.js"
+ }
+ },
+ "files": [
+ "dist",
+ "src",
+ "!src/**/*.test.ts",
+ "!src/test-helpers.ts",
+ "LICENSE",
+ "README.md"
+ ],
+ "sideEffects": false,
"scripts": {
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
+ "prepack": "bun run build",
+ "prepare": "node scripts/prepare.mjs",
"typecheck": "tsc --noEmit",
- "build": "bun run --cwd packages/mailbox build",
- "test": "bun run --cwd packages/mailbox test",
- "test:acceptance": "bun run build && bun test --cwd examples/reference-host"
+ "test": "bun test src",
+ "test:coverage": "bun test src --coverage",
+ "test:acceptance": "bun run build && bun install --force && bun test --cwd examples/reference-host"
+ },
+ "dependencies": {
+ "@hono/standard-validator": "0.2.3",
+ "@standard-community/standard-json": "0.3.5",
+ "@standard-community/standard-openapi": "0.2.9",
+ "arktype": "2.1.29",
+ "hono-openapi": "1.3.1"
+ },
+ "peerDependencies": {
+ "@intx/log": "^0.2.2",
+ "@intx/mime": "^0.2.2",
+ "@intx/types": "^0.2.2",
+ "drizzle-orm": "^0.45.2",
+ "hono": "^4.12.0",
+ "postgres": "^3.4.0"
},
"devDependencies": {
+ "@intx/log": "0.2.2",
+ "@intx/mime": "0.2.2",
+ "@intx/types": "0.2.2",
"@types/bun": "1.1.14",
+ "@types/json-schema": "7.0.15",
"@types/node": "22.10.5",
+ "drizzle-orm": "0.45.2",
+ "hono": "4.12.32",
+ "openapi-types": "12.1.3",
+ "postgres": "3.4.9",
"typescript": "5.7.2"
},
"overrides": {
diff --git a/packages/mailbox/LICENSE b/packages/mailbox/LICENSE
deleted file mode 100644
index f6683e7..0000000
--- a/packages/mailbox/LICENSE
+++ /dev/null
@@ -1,501 +0,0 @@
- GNU LESSER GENERAL PUBLIC LICENSE
- Version 2.1, February 1999
-
- Copyright (C) 1991, 1999 Free Software Foundation, Inc.
-
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-[This is the first released version of the Lesser GPL. It also counts
- as the successor of the GNU Library Public License, version 2, hence
- the version number 2.1.]
-
- Preamble
-
- The licenses for most software are designed to take away your
-freedom to share and change it. By contrast, the GNU General Public
-Licenses are intended to guarantee your freedom to share and change
-free software--to make sure the software is free for all its users.
-
- This license, the Lesser General Public License, applies to some
-specially designated software packages--typically libraries--of the
-Free Software Foundation and other authors who decide to use it. You
-can use it too, but we suggest you first think carefully about whether
-this license or the ordinary General Public License is the better
-strategy to use in any particular case, based on the explanations below.
-
- When we speak of free software, we are referring to freedom of use,
-not price. Our General Public Licenses are designed to make sure that
-you have the freedom to distribute copies of free software (and charge
-for this service if you wish); that you receive source code or can get
-it if you want it; that you can change the software and use pieces of
-it in new free programs; and that you are informed that you can do
-these things.
-
- To protect your rights, we need to make restrictions that forbid
-distributors to deny you these rights or to ask you to surrender these
-rights. These restrictions translate to certain responsibilities for
-you if you distribute copies of the library or if you modify it.
-
- For example, if you distribute copies of the library, whether gratis
-or for a fee, you must give the recipients all the rights that we gave
-you. You must make sure that they, too, receive or can get the source
-code. If you link other code with the library, you must provide
-complete object files to the recipients, so that they can relink them
-with the library after making changes to the library and recompiling
-it. And you must show them these terms so they know their rights.
-
- We protect your rights with a two-step method: (1) we copyright the
-library, and (2) we offer you this license, which gives you legal
-permission to copy, distribute and/or modify the library.
-
- To protect each distributor, we want to make it very clear that
-there is no warranty for the free library. Also, if the library is
-modified by someone else and passed on, the recipients should know
-that what they have is not the original version, so that the original
-author's reputation will not be affected by problems that might be
-introduced by others.
-
- Finally, software patents pose a constant threat to the existence of
-any free program. We wish to make sure that a company cannot
-effectively restrict the users of a free program by obtaining a
-restrictive license from a patent holder. Therefore, we insist that
-any patent license obtained for a version of the library must be
-consistent with the full freedom of use specified in this license.
-
- Most GNU software, including some libraries, is covered by the
-ordinary GNU General Public License. This license, the GNU Lesser
-General Public License, applies to certain designated libraries, and
-is quite different from the ordinary General Public License. We use
-this license for certain libraries in order to permit linking those
-libraries into non-free programs.
-
- When a program is linked with a library, whether statically or using
-a shared library, the combination of the two is legally speaking a
-combined work, a derivative of the original library. The ordinary
-General Public License therefore permits such linking only if the
-entire combination fits its criteria of freedom. The Lesser General
-Public License permits more lax criteria for linking other code with
-the library.
-
- We call this license the "Lesser" General Public License because it
-does Less to protect the user's freedom than the ordinary General
-Public License. It also provides other free software developers Less
-of an advantage over competing non-free programs. These disadvantages
-are the reason we use the ordinary General Public License for many
-libraries. However, the Lesser license provides advantages in certain
-special circumstances.
-
- For example, on rare occasions, there may be a special need to
-encourage the widest possible use of a certain library, so that it becomes
-a de-facto standard. To achieve this, non-free programs must be
-allowed to use the library. A more frequent case is that a free
-library does the same job as widely used non-free libraries. In this
-case, there is little to gain by limiting the free library to free
-software only, so we use the Lesser General Public License.
-
- In other cases, permission to use a particular library in non-free
-programs enables a greater number of people to use a large body of
-free software. For example, permission to use the GNU C Library in
-non-free programs enables many more people to use the whole GNU
-operating system, as well as its variant, the GNU/Linux operating
-system.
-
- Although the Lesser General Public License is Less protective of the
-users' freedom, it does ensure that the user of a program that is
-linked with the Library has the freedom and the wherewithal to run
-that program using a modified version of the Library.
-
- The precise terms and conditions for copying, distribution and
-modification follow. Pay close attention to the difference between a
-"work based on the library" and a "work that uses the library". The
-former contains code derived from the library, whereas the latter must
-be combined with the library in order to run.
-
- GNU LESSER GENERAL PUBLIC LICENSE
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
- 0. This License Agreement applies to any software library or other
-program which contains a notice placed by the copyright holder or
-other authorized party saying it may be distributed under the terms of
-this Lesser General Public License (also called "this License").
-Each licensee is addressed as "you".
-
- A "library" means a collection of software functions and/or data
-prepared so as to be conveniently linked with application programs
-(which use some of those functions and data) to form executables.
-
- The "Library", below, refers to any such software library or work
-which has been distributed under these terms. A "work based on the
-Library" means either the Library or any derivative work under
-copyright law: that is to say, a work containing the Library or a
-portion of it, either verbatim or with modifications and/or translated
-straightforwardly into another language. (Hereinafter, translation is
-included without limitation in the term "modification".)
-
- "Source code" for a work means the preferred form of the work for
-making modifications to it. For a library, complete source code means
-all the source code for all modules it contains, plus any associated
-interface definition files, plus the scripts used to control compilation
-and installation of the library.
-
- Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope. The act of
-running a program using the Library is not restricted, and output from
-such a program is covered only if its contents constitute a work based
-on the Library (independent of the use of the Library in a tool for
-writing it). Whether that is true depends on what the Library does
-and what the program that uses the Library does.
-
- 1. You may copy and distribute verbatim copies of the Library's
-complete source code as you receive it, in any medium, provided that
-you conspicuously and appropriately publish on each copy an
-appropriate copyright notice and disclaimer of warranty; keep intact
-all the notices that refer to this License and to the absence of any
-warranty; and distribute a copy of this License along with the
-Library.
-
- You may charge a fee for the physical act of transferring a copy,
-and you may at your option offer warranty protection in exchange for a
-fee.
-
- 2. You may modify your copy or copies of the Library or any portion
-of it, thus forming a work based on the Library, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
- a) The modified work must itself be a software library.
-
- b) You must cause the files modified to carry prominent notices
- stating that you changed the files and the date of any change.
-
- c) You must cause the whole of the work to be licensed at no
- charge to all third parties under the terms of this License.
-
- d) If a facility in the modified Library refers to a function or a
- table of data to be supplied by an application program that uses
- the facility, other than as an argument passed when the facility
- is invoked, then you must make a good faith effort to ensure that,
- in the event an application does not supply such function or
- table, the facility still operates, and performs whatever part of
- its purpose remains meaningful.
-
- (For example, a function in a library to compute square roots has
- a purpose that is entirely well-defined independent of the
- application. Therefore, Subsection 2d requires that any
- application-supplied function or table used by this function must
- be optional: if the application does not supply it, the square
- root function must still compute square roots.)
-
-These requirements apply to the modified work as a whole. If
-identifiable sections of that work are not derived from the Library,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works. But when you
-distribute the same sections as part of a whole which is a work based
-on the Library, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote
-it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Library.
-
-In addition, mere aggregation of another work not based on the Library
-with the Library (or with a work based on the Library) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
- 3. You may opt to apply the terms of the ordinary GNU General Public
-License instead of this License to a given copy of the Library. To do
-this, you must alter all the notices that refer to this License, so
-that they refer to the ordinary GNU General Public License, version 2,
-instead of to this License. (If a newer version than version 2 of the
-ordinary GNU General Public License has appeared, then you can specify
-that version instead if you wish.) Do not make any other change in
-these notices.
-
- Once this change is made in a given copy, it is irreversible for
-that copy, so the ordinary GNU General Public License applies to all
-subsequent copies and derivative works made from that copy.
-
- This option is useful when you wish to copy part of the code of
-the Library into a program that is not a library.
-
- 4. You may copy and distribute the Library (or a portion or
-derivative of it, under Section 2) in object code or executable form
-under the terms of Sections 1 and 2 above provided that you accompany
-it with the complete corresponding machine-readable source code, which
-must be distributed under the terms of Sections 1 and 2 above on a
-medium customarily used for software interchange.
-
- If distribution of object code is made by offering access to copy
-from a designated place, then offering equivalent access to copy the
-source code from the same place satisfies the requirement to
-distribute the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
- 5. A program that contains no derivative of any portion of the
-Library, but is designed to work with the Library by being compiled or
-linked with it, is called a "work that uses the Library". Such a
-work, in isolation, is not a derivative work of the Library, and
-therefore falls outside the scope of this License.
-
- However, linking a "work that uses the Library" with the Library
-creates an executable that is a derivative of the Library (because it
-contains portions of the Library), rather than a "work that uses the
-library". The executable is therefore covered by this License.
-Section 6 states terms for distribution of such executables.
-
- When a "work that uses the Library" uses material from a header file
-that is part of the Library, the object code for the work may be a
-derivative work of the Library even though the source code is not.
-Whether this is true is especially significant if the work can be
-linked without the Library, or if the work is itself a library. The
-threshold for this to be true is not precisely defined by law.
-
- If such an object file uses only numerical parameters, data
-structure layouts and accessors, and small macros and small inline
-functions (ten lines or less in length), then the use of the object
-file is unrestricted, regardless of whether it is legally a derivative
-work. (Executables containing this object code plus portions of the
-Library will still fall under Section 6.)
-
- Otherwise, if the work is a derivative of the Library, you may
-distribute the object code for the work under the terms of Section 6.
-Any executables containing that work also fall under Section 6,
-whether or not they are linked directly with the Library itself.
-
- 6. As an exception to the Sections above, you may also combine or
-link a "work that uses the Library" with the Library to produce a
-work containing portions of the Library, and distribute that work
-under terms of your choice, provided that the terms permit
-modification of the work for the customer's own use and reverse
-engineering for debugging such modifications.
-
- You must give prominent notice with each copy of the work that the
-Library is used in it and that the Library and its use are covered by
-this License. You must supply a copy of this License. If the work
-during execution displays copyright notices, you must include the
-copyright notice for the Library among them, as well as a reference
-directing the user to the copy of this License. Also, you must do one
-of these things:
-
- a) Accompany the work with the complete corresponding
- machine-readable source code for the Library including whatever
- changes were used in the work (which must be distributed under
- Sections 1 and 2 above); and, if the work is an executable linked
- with the Library, with the complete machine-readable "work that
- uses the Library", as object code and/or source code, so that the
- user can modify the Library and then relink to produce a modified
- executable containing the modified Library. (It is understood
- that the user who changes the contents of definitions files in the
- Library will not necessarily be able to recompile the application
- to use the modified definitions.)
-
- b) Use a suitable shared library mechanism for linking with the
- Library. A suitable mechanism is one that (1) uses at run time a
- copy of the library already present on the user's computer system,
- rather than copying library functions into the executable, and (2)
- will operate properly with a modified version of the library, if
- the user installs one, as long as the modified version is
- interface-compatible with the version that the work was made with.
-
- c) Accompany the work with a written offer, valid for at
- least three years, to give the same user the materials
- specified in Subsection 6a, above, for a charge no more
- than the cost of performing this distribution.
-
- d) If distribution of the work is made by offering access to copy
- from a designated place, offer equivalent access to copy the above
- specified materials from the same place.
-
- e) Verify that the user has already received a copy of these
- materials or that you have already sent this user a copy.
-
- For an executable, the required form of the "work that uses the
-Library" must include any data and utility programs needed for
-reproducing the executable from it. However, as a special exception,
-the materials to be distributed need not include anything that is
-normally distributed (in either source or binary form) with the major
-components (compiler, kernel, and so on) of the operating system on
-which the executable runs, unless that component itself accompanies
-the executable.
-
- It may happen that this requirement contradicts the license
-restrictions of other proprietary libraries that do not normally
-accompany the operating system. Such a contradiction means you cannot
-use both them and the Library together in an executable that you
-distribute.
-
- 7. You may place library facilities that are a work based on the
-Library side-by-side in a single library together with other library
-facilities not covered by this License, and distribute such a combined
-library, provided that the separate distribution of the work based on
-the Library and of the other library facilities is otherwise
-permitted, and provided that you do these two things:
-
- a) Accompany the combined library with a copy of the same work
- based on the Library, uncombined with any other library
- facilities. This must be distributed under the terms of the
- Sections above.
-
- b) Give prominent notice with the combined library of the fact
- that part of it is a work based on the Library, and explaining
- where to find the accompanying uncombined form of the same work.
-
- 8. You may not copy, modify, sublicense, link with, or distribute
-the Library except as expressly provided under this License. Any
-attempt otherwise to copy, modify, sublicense, link with, or
-distribute the Library is void, and will automatically terminate your
-rights under this License. However, parties who have received copies,
-or rights, from you under this License will not have their licenses
-terminated so long as such parties remain in full compliance.
-
- 9. You are not required to accept this License, since you have not
-signed it. However, nothing else grants you permission to modify or
-distribute the Library or its derivative works. These actions are
-prohibited by law if you do not accept this License. Therefore, by
-modifying or distributing the Library (or any work based on the
-Library), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Library or works based on it.
-
- 10. Each time you redistribute the Library (or any work based on the
-Library), the recipient automatically receives a license from the
-original licensor to copy, distribute, link with or modify the Library
-subject to these terms and conditions. You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties with
-this License.
-
- 11. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Library at all. For example, if a patent
-license would not permit royalty-free redistribution of the Library by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Library.
-
-If any portion of this section is held invalid or unenforceable under any
-particular circumstance, the balance of the section is intended to apply,
-and the section as a whole is intended to apply in other circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system which is
-implemented by public license practices. Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
- 12. If the distribution and/or use of the Library is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Library under this License may add
-an explicit geographical distribution limitation excluding those countries,
-so that distribution is permitted only in or among countries not thus
-excluded. In such case, this License incorporates the limitation as if
-written in the body of this License.
-
- 13. The Free Software Foundation may publish revised and/or new
-versions of the Lesser General Public License from time to time.
-Such new versions will be similar in spirit to the present version,
-but may differ in detail to address new problems or concerns.
-
-Each version is given a distinguishing version number. If the Library
-specifies a version number of this License which applies to it and
-"any later version", you have the option of following the terms and
-conditions either of that version or of any later version published by
-the Free Software Foundation. If the Library does not specify a
-license version number, you may choose any version ever published by
-the Free Software Foundation.
-
- 14. If you wish to incorporate parts of the Library into other free
-programs whose distribution conditions are incompatible with these,
-write to the author to ask for permission. For software which is
-copyrighted by the Free Software Foundation, write to the Free
-Software Foundation; we sometimes make exceptions for this. Our
-decision will be guided by the two goals of preserving the free status
-of all derivatives of our free software and of promoting the sharing
-and reuse of software generally.
-
- NO WARRANTY
-
- 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
-WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
-EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
-OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
-KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
-LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
-THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
- 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
-WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
-AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
-FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
-CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
-LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
-RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
-FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
-SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
-DAMAGES.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Libraries
-
- If you develop a new library, and you want it to be of the greatest
-possible use to the public, we recommend making it free software that
-everyone can redistribute and change. You can do so by permitting
-redistribution under these terms (or, alternatively, under the terms of the
-ordinary General Public License).
-
- To apply these terms, attach the following notices to the library. It is
-safest to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least the
-"copyright" line and a pointer to where the full notice is found.
-
-
- Copyright (C)
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library; if not, see .
-
-Also add information on how to contact you by electronic and paper mail.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the library, if
-necessary. Here is a sample; alter the names:
-
- Yoyodyne, Inc., hereby disclaims all copyright interest in the
- library `Frob' (a library for tweaking knobs) written by James Random Hacker.
-
- , 1 April 1990
- Moe Ghoul, President of Vice
-
-That's all there is to it!
diff --git a/packages/mailbox/README.md b/packages/mailbox/README.md
deleted file mode 100644
index 7543abf..0000000
--- a/packages/mailbox/README.md
+++ /dev/null
@@ -1,340 +0,0 @@
-# @corbits/mailbox
-
-A universal, principal-keyed inbox you mount onto an existing [Hono](https://hono.dev)
-app. Backend only — this package ships no UI.
-
-It owns two tables and its own migration ledger — all in a dedicated `mailbox`
-Postgres schema in the host's database — and the `/api/me/inbox*` routes:
-
-- **`principal_mail`** — the message as delivered, immutable. Reads 1-1 with
- Interchange's `session_mail`.
-- **`mailbox`** — the mutable management layer keyed by mail id (read/archive/trash
- plus triage columns), created **eagerly with its message in one transaction**. An
- all-NULL row means delivered-and-untouched.
-
-**Detail is frame-authoritative; list is not.** `GET …/inbox/:id` re-derives subject,
-sender, recipients, date, Message-ID and snippet from the stored RFC 2822 frame
-(cached columns as fallback), so a malformed frame degrades to a partial message rather
-than a failed request. **List** never selects `raw` and never decodes: it projects
-`subject`/`from` from the write-time cached columns, omits `snippet`, sets
-`date` from `created_at`, `messageId` to the row id, and `to` to `[address]`.
-
-See [ARCHITECTURE.md](../../ARCHITECTURE.md) for the data model and the design
-rationale behind it.
-
-## Requirements
-
-| | |
-| --- | --- |
-| Runtime | Node 22+ or Bun 1.1+ |
-| Postgres | 13+ |
-| Control plane | Interchange-shaped `public.tenant` and `public.principal` tables in the **same database**, created before `runMailboxMigrations` — the mailbox tables carry hard foreign keys to both |
-| Minimum `@intx/*` | **0.2.2** — 0.1.x does not compile (no `SidecarAuthenticator`, four-verb `SessionService`) |
-
-`hono`, `drizzle-orm`, `postgres`, `@intx/log`, `@intx/mime` and `@intx/types` are
-**peer dependencies**. Each breaks quietly with a second copy in the tree —
-`@intx/log` configures LogTape's module-global registry, so two copies means your
-sinks silently miss this package's logs. Install one copy of each at the top level.
-
-## Install
-
-```sh
-npm install @corbits/mailbox
-```
-
-> **Not on npm yet.** Until the first release, consume it from a git checkout or an
-> `npm pack` tarball. The `@intx/*` packages *are* published, at `0.2.2`.
-
-## Mount
-
-```ts
-import { Hono } from "hono";
-import {
- mountMailbox,
- runMailboxMigrations,
- createInMemoryMailboxEventBus,
-} from "@corbits/mailbox";
-
-// Your own drizzle handle. The mailbox never opens a second pool.
-await runMailboxMigrations(db);
-
-const api = new Hono();
-mountMailbox(api, {
- db,
- bus: createInMemoryMailboxEventBus(),
- resolvePrincipal: (ctx) => {
- const user = (ctx as Context).get("user");
- return user ? { tenantId: user.tenantId, principalId: user.id } : null;
- },
- // Your taxonomy, not ours. `priorities` is ORDERED, most urgent first.
- vocabulary: {
- priorities: ["urgent", "high", "normal", "low"],
- statuses: ["needs-action", "done"],
- },
-});
-
-const app = new Hono();
-app.route("/api", api);
-```
-
-Routes are registered root-relative and served under `/api` — the prefix Interchange
-serves its own routes under. No `/v1`, no vendor prefix.
-
-`examples/reference-host` in this repository is a complete `@intx/hub-api` host with
-this package mounted and the acceptance suite pointed at it. Start there if you need
-the whole `createApp` wiring.
-
-No handle of your own? `createMailboxDb(url)` opens one and returns `{ db, close }`.
-
-## The seams
-
-| Seam | Contract |
-| --- | --- |
-| `db` | Your drizzle handle, pointed at the HOST's database — the one your control plane lives in. `MailboxDb` is schema-agnostic in the drizzle sense (every query names its table explicitly, nothing uses `db.query.*`), so a handle bound to your schema is assignable. There is no separate-database mode: the control-plane foreign keys can only hold when both planes share one database. |
-| `resolvePrincipal(ctx)` | Takes the host context as `unknown`, returns `{ tenantId, principalId } \| null`. The only authentication seam — this package has no opinion about sessions, cookies or tokens. |
-| `bus` | SSE fan-out, keyed by the `(tenantId, principalId)` pair (`MailboxEventScope`) — a principal id is only unique within its tenant, so a principal-only key would leak one tenant's events to another's same-named principal. The in-memory default suits a single process; supply a broker-backed `MailboxEventBus` to fan out across replicas. |
-| `vocabulary` | **Required, no default.** Priority ranking, query validation and OpenAPI enums are all generated from your lists. |
-| `resolveSenderDisplays` | Optional. Batched per read; turns sender addresses into human labels. Best-effort — a resolver that throws costs the page its labels, never the page. |
-| `runMailboxMigrations` | Idempotent, advisory-locked, own ledger (`"mailbox"."corbits_mailbox_migrations"`). Call on every boot from every replica — after the host's control plane exists, since the DDL's foreign keys reference `public.tenant` and `public.principal`. Migrations are checksummed — editing one that already shipped fails the next boot loudly. |
-
-## Routes
-
-| Method | Path | |
-| --- | --- | --- |
-| `GET` | `/api/me/inbox` | List. `view=all\|unread\|archived\|trash`, `limit`, `cursor`, `sort=date\|priority`, plus `priority`/`classification`/`status`/`assignee` filters. |
-| `GET` | `/api/me/inbox/unread-count` | Unread, non-archived, non-trashed count. |
-| `GET` | `/api/me/inbox/events` | SSE stream, `: heartbeat` every 25s. See the client contract below. |
-| `GET` | `/api/me/inbox/:id` | One message with its full body. |
-| `POST` | `/api/me/inbox/:id/{read,unread,trash,archive,restore}` | Single-message mutations. |
-| `POST` | `/api/me/inbox/:id/enrich` | Stamp triage `priority`/`classification`/`status`. |
-| `POST` | `/api/me/inbox/:id/assign` | Delegate to another principal (`null` un-assigns). |
-| `POST` | `/api/me/inbox/bulk` | One action across up to 50 ids, partial success. |
-
-All routes carry `describeRoute`, so they appear in the host's OpenAPI document.
-
-### The SSE client contract
-
-**Events are non-durable nudges, not data.** Each event carries an id to
-refetch and, when the publisher knows it, `op` — which operation fired
-(`create`, `mark_read`, `mark_unread`, `trash`, `archive`, `restore`,
-`enrich`, `assign`). `op` is **additive**: it is optional on the wire, so a
-client that only reads `id` (the original shape) keeps working unchanged,
-and a client that wants to react to a specific kind of change (e.g. badge a
-new arrival differently from a read receipt) can switch on it instead of
-re-fetching and diffing every message. Postgres remains the source of truth
-either way — `op` narrows what changed, it does not replace a refetch when
-you need the new state. The server queues at most `MAX_PENDING_SSE_EVENTS`
-(100) events per connection — a consumer that stops reading is
-**disconnected**, not buffered for. So the contract for any client:
-
-- On **any** disconnect — network drop, server restart, or an overflow close —
- reconnect and **refetch from the API**: the list and the unread count. Never
- assume the stream told you everything that happened while you were away.
-- Do not treat the stream as a change log. It may drop events (publish is
- best-effort after commit) and the server may close a stream whose consumer
- stops draining it.
-- **Events can be missed, duplicated, or arrive out of order** — this is a
- best-effort nudge channel, not a durable log:
- - *Missed*: publish failures are logged and swallowed, never retried; an
- overflowing connection is disconnected, not buffered for.
- - *Duplicated*: redelivery is deduped by default — an inbox item with a
- stable external identifier inserts once, on conflict-do-nothing. Only a
- redelivered item with no such identifier inserts a second row and
- publishes a second `create`; a broker-backed bus a host supplies for
- multi-replica fan-out may also redeliver on its own. Treat a repeat of
- an already-applied `op` for the same `id` as a no-op.
- - *Out of order*: the default in-memory bus preserves publish order within
- one process for one mailbox, but a broker-backed bus, or multiple
- replicas publishing concurrently, gives no such guarantee. Never infer
- "later event = later state" from arrival order.
-
-Four more behaviors to know before wiring a UI:
-
-- **No-identity asymmetry** (the cross-core rule, not a mailbox quirk). With no
- resolvable principal, collection reads answer `200` with an empty result — "show me
- my inbox" has a truthful answer for someone with no inbox. Everything else names or
- streams a *specific* identity and answers `403`.
-- **Cursors are bound to the result set that minted them.** Replaying one under a
- different view, sort or filter set is a `400`; paging a `priority=high` cursor into
- an unfiltered list would silently skip messages. Malformed cursors are also always
- a `400`, never a `500`: the timestamp must be exactly the microsecond shape this
- package mints and the priority rank a safe integer, so a crafted cursor never
- reaches Postgres.
-- **`bulk` with `ids: []` is a `200`** (`{updated: 0, results: []}`), not a `400` — the
- partial-success contract applied to zero ids.
-- **`limit` is rejected, never clamped.** Default 50, ceiling `MAX_MAILBOX_PAGE_LIMIT`
- (200). A caller handed 200 after asking for 500 advances its paging by 500 and skips
- the difference.
-- **Frame size and transport recipient count are hard caps.** Direct write
- (`writeMailboxMessage` / `deliverInboxItems`) throws `RangeError` when a built
- frame would exceed `MAX_MAILBOX_FRAME_BYTES` (1 MiB). The transport dual-write
- path applies the same frame-byte cap and a hard ceiling of
- `MAX_MAILBOX_RECIPIENTS` (50) on the **raw** recipient address list (before
- domain filter / principal resolve — never clamps to the first 50). Over-cap
- refusal there is logged and swallowed (dual-write independence) so
- `createMailboxPersist` still returns upstream success; hosts that need larger
- fan-out must chunk across calls.
-
-## Writing into a mailbox
-
-```ts
-const delivered = await deliverInboxItems(db, items, { bus });
-// each result is { messageKey, id }; `id === null` means already delivered
-```
-
-`deliverInboxItems` is the ingress-adapter seam, deduping on
-`mailboxKey.inbox(source, externalId)` — a versioned length-prefixed encoding
-(`inbox2:::`) so pairs that contain `:` cannot
-collide (NUL-join is injective too, but Postgres text rejects U+0000). The
-`inbox2:` prefix keeps the space disjoint from pre-upgrade
-`inbox::` keys: length-prefix under `inbox:` alone would
-false-collide when a historical source was pure decimal. Pre-upgrade rows will
-not dedupe against the new encoding and cannot false-collide with it; no
-migration is performed.
-
-After blank-scope prevalidation, **the entire batch commits in one transaction**
-(or none): a mid-batch FK / driver failure rolls back every new row from that
-call. Deduped keys (`id: null`) are no-ops inside the transaction. Bus publish
-and the optional host `enqueue` hook run only after commit, and only for newly
-inserted ids. `enqueue` is best-effort — a throw is logged with the message id
-and never rejects the delivery (same posture as bus publish). A host whose hook
-permanently fails on the first try must triage independently; retries of the
-same items will dedupe and skip enqueue.
-
-`writeMailboxMessage` inserts a single row. Idempotency keys are namespaced
-(`mailboxKey.inbox`/`.gate`/`.run`).
-
-Every delivery writes the mail row **and its management row in one transaction** —
-the management row is eager, so every mutation and the unread count are plain
-operations on `mailbox`. `InboxItem` carries optional triage fields, so an adapter
-that already knows an item's verdict stamps them at delivery instead of writing then
-updating; a crash between two separate writes could otherwise strand a delivered
-message behind the dedupe with no management row.
-
-Only inbound rows are written, by design — the read path filters `direction = 'inbound'`,
-and the sender's outbound record belongs to whatever transport actually sent the frame.
-
-## Triage enriches the mail row
-
-Mail is the single work surface: triage does not spawn a task object.
-
-```ts
-await enrichMailboxMessage(db, { tenantId, principalId, id }, {
- priority: "urgent", // from YOUR vocabulary
- classification: "deal-risk",
- status: "needs-action",
-});
-```
-
-Each field applies independently — an omitted key leaves the stored value alone, an
-explicit `null` clears it, and an enrichment that sets nothing is refused. Read it back
-with `listUserMailbox`'s `filter` (ANDed) and `sort=priority`, where untriaged mail
-ranks last. `filter.priority`/`status` are checked against your vocabulary (unknown
-value → `400`); `classification`/`assignee` are open strings.
-
-Delegation is an `assignee` ref on the `mailbox` row, not a forwarded copy: the item
-stays in the delegator's mailbox, so `?assignee=user-2` answers "what have I handed to
-user-2". It does not put the item into user-2's inbox — write a second row for that.
-
-Validate untrusted bodies with `MailboxEnrichmentSchema` / `MailboxAssignmentSchema`.
-
-## Hard foreign keys — and what they buy you
-
-Both tables reference the host's control plane: `tenant_id → public.tenant(id)` and
-`principal_id → public.principal(id)`, both `ON DELETE CASCADE`. That is the same
-posture as Interchange's own `session_mail.tenant_id REFERENCES tenant ON DELETE
-CASCADE`, extended to the principal, and it does two jobs the database is better at
-than application code:
-
-**Writes to a scope the control plane does not know are refused by the database.** A
-tenant or principal id that matches no control-plane row is a foreign-key violation,
-not a stored orphan. Blank or whitespace-only ids are still refused earlier, with a
-`RangeError` at the write boundary — the FK would catch them too, but only as a driver
-error deep in the insert, long after the caller who typed `""` lost its stack.
-Identifiers are never trimmed for you. `assertMailboxScope` and `MailboxScopeIdsSchema`
-are exported for your own boundary.
-
-**Offboarding is a cascade.** Deleting a tenant or principal row in your control plane
-carries every one of its mailbox rows out with it — no window in which orphaned mail
-exists. The explicit purges remain for hosts that soft-delete or archive control-plane
-rows instead, where no cascade ever fires but the mail data must still go:
-
-```ts
-await purgeTenantMailbox(db, tenantId); // the whole tenant
-await purgePrincipalMailbox(db, { tenantId, principalId }); // one person
-```
-
-Each purge is a **single `DELETE` on `principal_mail`** — the management rows follow
-through the `mailbox.id → principal_mail.id` cascade, so it is atomic by construction.
-Both return the message count and take the handle you pass, so a transaction runs the
-purge inside your own offboarding unit of work. Both are irreversible; `raw` is the
-only copy of the frame held here.
-
-## Message schemas
-
-`MailboxMessageSchema`, `MailboxMessageDetailSchema`, `MailboxListResponseSchema` and
-`MailboxRefSchema` are real arktype schemas, so a consumer decoding this package's JSON
-validates rather than casts.
-
-- **`from` is always present** — on detail the header → cached column → default chain
- ends in `""`; on list only the cached column → default chain applies. Either way a
- client never branches on its absence. `subject` has no such default: an empty subject
- is distinct from no subject.
-- **Body and snippet come from the frame on detail only, multipart included.** Detail
- walks MIME path `1` (or `1.1` when part 1 is itself multipart) via `@intx/mime`,
- rather than returning the MIME envelope verbatim. An unwalkable frame yields an empty
- body, never a 500. List omits `snippet` entirely and never loads `raw`.
-- **Message-ID fallback is `hub.invalid`** (`MESSAGE_ID_FALLBACK_DOMAIN`) — RFC 2606
- reserved, guaranteed never to resolve, so a malformed sender cannot mint a routable-
- looking id. `generateMailboxMessageId` is exported.
-
-## The transport dual-write seam
-
-If your host already has a mail transport that persists frames, `createMailboxPersist`
-wraps it so every addressed principal also gets a durable inbound row:
-
-```ts
-const persistMail = createMailboxPersist(db, {
- upstream: transport.persistMail,
- authorizeSender: async (senderAddress) => {
- const instance = await findActiveInstance(senderAddress);
- return instance ? { tenantId: instance.tenantId, domain: instance.domain } : null;
- },
- bus,
-});
-```
-
-Sender authorization is the host's — whether an address belongs to a live agent
-instance is the host's call, not a schema fact. The enforcement is ours: an
-unauthorized sender gets no mailbox row while the frame is still delegated upstream,
-and recipients outside the authorized `domain` are skipped, so cross-tenant delivery is
-impossible by construction. Recipient local parts are sender-controlled, so addresses
-that resolve to no known principal in the tenant are skipped with a warning rather than
-minting a phantom mailbox row — and one typo'd address never costs the real recipients
-on the same frame their durable copy.
-
-Dual-write independence holds both ways: `upstream` throwing still attempts the mailbox
-write then re-throws unchanged, and a mailbox-write failure is logged loudly but never
-rejects a persist that already succeeded upstream.
-
-Mailbox inserts are idempotent under transport retry: each row is stamped with a
-package-owned `messageKey` (`transport:mid::` when the
-frame carries a Message-ID, otherwise `transport:raw::`) and
-inserted with `onConflictDoNothing` on the existing partial unique index. Management
-rows and bus announcements only follow rows actually returned by `RETURNING`, so a
-retried frame does not fail on unique-violation and does not re-announce duplicates.
-
-## Development
-
-```sh
-bun run test # unit + integration (needs Postgres)
-bun run test:coverage
-bun run build # dist/ — JS + .d.ts, consumable from Node
-```
-
-The tarball ships `src/` alongside `dist/`, so the emitted `.js.map` and `.d.ts.map`
-resolve: go-to-definition and debugger steps land on real TypeScript.
-
-## License
-
-LGPL-2.1-only. See [LICENSE](./LICENSE).
diff --git a/packages/mailbox/package.json b/packages/mailbox/package.json
deleted file mode 100644
index e0216a4..0000000
--- a/packages/mailbox/package.json
+++ /dev/null
@@ -1,84 +0,0 @@
-{
- "name": "@corbits/mailbox",
- "version": "0.1.0",
- "type": "module",
- "license": "LGPL-2.1-only",
- "description": "Universal principal-keyed inbox, mountable onto any Interchange host. Requires @intx 0.2.2 or newer.",
- "keywords": [
- "corbits",
- "interchange",
- "intx",
- "mailbox",
- "inbox",
- "hono",
- "drizzle",
- "postgres"
- ],
- "author": "Corbits",
- "homepage": "https://github.com/corbitsdev/corbits-mailbox#readme",
- "repository": {
- "type": "git",
- "url": "git+https://github.com/corbitsdev/corbits-mailbox.git",
- "directory": "packages/mailbox"
- },
- "bugs": {
- "url": "https://github.com/corbitsdev/corbits-mailbox/issues"
- },
- "engines": {
- "node": ">=22"
- },
- "corbits": {
- "minimumIntxVersion": "0.2.2"
- },
- "main": "./dist/index.js",
- "types": "./dist/index.d.ts",
- "exports": {
- ".": {
- "types": "./dist/index.d.ts",
- "default": "./dist/index.js"
- }
- },
- "files": [
- "dist",
- "src",
- "!src/**/*.test.ts",
- "!src/test-helpers.ts",
- "LICENSE",
- "README.md"
- ],
- "sideEffects": false,
- "scripts": {
- "build": "rm -rf dist && tsc -p tsconfig.build.json",
- "prepack": "bun run build",
- "test": "bun test",
- "test:coverage": "bun test --coverage"
- },
- "dependencies": {
- "@hono/standard-validator": "0.2.3",
- "@standard-community/standard-json": "0.3.5",
- "@standard-community/standard-openapi": "0.2.9",
- "arktype": "2.1.29",
- "hono-openapi": "1.3.1"
- },
- "peerDependencies": {
- "@intx/log": "^0.2.2",
- "@intx/mime": "^0.2.2",
- "@intx/types": "^0.2.2",
- "drizzle-orm": "^0.45.2",
- "hono": "^4.12.0",
- "postgres": "^3.4.0"
- },
- "devDependencies": {
- "@intx/log": "0.2.2",
- "@intx/mime": "0.2.2",
- "@intx/types": "0.2.2",
- "@types/bun": "1.1.14",
- "@types/json-schema": "7.0.15",
- "@types/node": "22.10.5",
- "drizzle-orm": "0.45.2",
- "hono": "4.12.32",
- "openapi-types": "12.1.3",
- "postgres": "3.4.9",
- "typescript": "5.7.2"
- }
-}
diff --git a/packages/mailbox/tsconfig.json b/packages/mailbox/tsconfig.json
deleted file mode 100644
index 2e69040..0000000
--- a/packages/mailbox/tsconfig.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "extends": "../../tsconfig.json",
- "include": [
- "src"
- ]
-}
diff --git a/scripts/prepare.mjs b/scripts/prepare.mjs
new file mode 100644
index 0000000..bbd9af4
--- /dev/null
+++ b/scripts/prepare.mjs
@@ -0,0 +1,93 @@
+/**
+ * `prepare` hook — builds `dist/` when the package is installed from git.
+ *
+ * The package publishes `dist/` (and `src`) via `files`, and the `exports` map
+ * points at `dist/`. A registry tarball arrives already built, via `prepack`.
+ * A git install does not: it gets the repository, so something has to build on
+ * the way in, and `prepare` is the one lifecycle hook package managers run at
+ * that moment. Without it every `@corbits/mailbox` import in the consumer
+ * resolves to a file that was never generated, and the failure lands in the
+ * consumer's tree, where they cannot fix it.
+ *
+ * Three things make this more than a one-line script:
+ *
+ * 1. `prepare` also runs for the *root* project on a plain `bun install` here
+ * in the repository. Building then would be wasted work at best, and
+ * recursive at worst (see 2). Being installed as a dependency is what
+ * distinguishes the two cases, and the honest signal for it is that the
+ * package directory sits inside a `node_modules` tree.
+ * 2. bun does not install a git dependency's devDependencies, and the whole
+ * toolchain (tsc) is a devDependency — as it should be, a consumer must not
+ * inherit it as a runtime dep. So the git-install path installs them itself,
+ * immediately before the build that needs them.
+ * 3. That build cannot run inside the consumer's `node_modules`. Declaration
+ * emit has to name the host's types, and installing the toolchain in place
+ * puts them under paths TypeScript refuses to write into a `.d.ts`
+ * (TS2742, "cannot be named without a reference to ..."). Building in a
+ * scratch directory outside any `node_modules` tree keeps those paths
+ * nameable; it is the same layout the build already runs in during
+ * development and on CI.
+ *
+ * Deliberately a no-op when `dist/` is already present: repeat installs of the
+ * same commit should not pay for a rebuild.
+ */
+import { execFileSync } from "node:child_process";
+import { cpSync, existsSync, mkdtempSync, rmSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { dirname, join, sep } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const packageDir = dirname(dirname(fileURLToPath(import.meta.url)));
+
+if (existsSync(join(packageDir, "dist", "index.js"))) {
+ process.exit(0);
+}
+
+const installedAsDependency = packageDir.split(sep).includes("node_modules");
+if (!installedAsDependency) {
+ // Developing the repository itself. `bun run build` is a documented step of
+ // getting set up (README.md) and stays an explicit one — a `prepare` that
+ // silently ran the whole gate on every `bun install` would be a surprise,
+ // and the recursive `bun install` below would be a worse one.
+ process.exit(0);
+}
+
+// The toolchain install below is itself a `bun install`. It no longer runs in
+// this directory, but the scratch copy carries this same script, and bun runs
+// `prepare` for the root project too — so without a marker the copy would
+// start the whole dance again. This breaks that cycle.
+const RECURSION_GUARD = "CORBITS_MAILBOX_PREPARE";
+if (process.env[RECURSION_GUARD] === "1") {
+ process.exit(0);
+}
+
+function run(command, args, cwd) {
+ execFileSync(command, args, {
+ cwd,
+ stdio: "inherit",
+ env: { ...process.env, [RECURSION_GUARD]: "1" },
+ });
+}
+
+console.log("@corbits/mailbox: git install — building dist/");
+
+const buildDir = mkdtempSync(join(tmpdir(), "corbits-mailbox-"));
+try {
+ cpSync(packageDir, buildDir, {
+ recursive: true,
+ // `node_modules` is what carries the unnameable layout, so copying it
+ // would defeat the point; `dist` is the thing being built.
+ filter: (source) => {
+ if (source === packageDir) return true;
+ const [top] = source.slice(packageDir.length + 1).split(sep);
+ return top !== "node_modules" && top !== "dist";
+ },
+ });
+
+ run("bun", ["install"], buildDir);
+ run("bun", ["run", "build"], buildDir);
+
+ cpSync(join(buildDir, "dist"), join(packageDir, "dist"), { recursive: true });
+} finally {
+ rmSync(buildDir, { recursive: true, force: true });
+}
diff --git a/packages/mailbox/src/bus-unsubscribe.test.ts b/src/bus-unsubscribe.test.ts
similarity index 100%
rename from packages/mailbox/src/bus-unsubscribe.test.ts
rename to src/bus-unsubscribe.test.ts
diff --git a/packages/mailbox/src/bus.test.ts b/src/bus.test.ts
similarity index 100%
rename from packages/mailbox/src/bus.test.ts
rename to src/bus.test.ts
diff --git a/packages/mailbox/src/bus.ts b/src/bus.ts
similarity index 100%
rename from packages/mailbox/src/bus.ts
rename to src/bus.ts
diff --git a/packages/mailbox/src/concurrency.test.ts b/src/concurrency.test.ts
similarity index 100%
rename from packages/mailbox/src/concurrency.test.ts
rename to src/concurrency.test.ts
diff --git a/packages/mailbox/src/db.ts b/src/db.ts
similarity index 100%
rename from packages/mailbox/src/db.ts
rename to src/db.ts
diff --git a/packages/mailbox/src/enrichment.test.ts b/src/enrichment.test.ts
similarity index 100%
rename from packages/mailbox/src/enrichment.test.ts
rename to src/enrichment.test.ts
diff --git a/packages/mailbox/src/filter-sort-assign.test.ts b/src/filter-sort-assign.test.ts
similarity index 100%
rename from packages/mailbox/src/filter-sort-assign.test.ts
rename to src/filter-sort-assign.test.ts
diff --git a/packages/mailbox/src/fk-cascade.test.ts b/src/fk-cascade.test.ts
similarity index 100%
rename from packages/mailbox/src/fk-cascade.test.ts
rename to src/fk-cascade.test.ts
diff --git a/packages/mailbox/src/frame-multipart.test.ts b/src/frame-multipart.test.ts
similarity index 100%
rename from packages/mailbox/src/frame-multipart.test.ts
rename to src/frame-multipart.test.ts
diff --git a/packages/mailbox/src/frame.test.ts b/src/frame.test.ts
similarity index 100%
rename from packages/mailbox/src/frame.test.ts
rename to src/frame.test.ts
diff --git a/packages/mailbox/src/frame.ts b/src/frame.ts
similarity index 100%
rename from packages/mailbox/src/frame.ts
rename to src/frame.ts
diff --git a/packages/mailbox/src/index.ts b/src/index.ts
similarity index 100%
rename from packages/mailbox/src/index.ts
rename to src/index.ts
diff --git a/packages/mailbox/src/log-warnings.test.ts b/src/log-warnings.test.ts
similarity index 100%
rename from packages/mailbox/src/log-warnings.test.ts
rename to src/log-warnings.test.ts
diff --git a/packages/mailbox/src/message-schemas.test.ts b/src/message-schemas.test.ts
similarity index 100%
rename from packages/mailbox/src/message-schemas.test.ts
rename to src/message-schemas.test.ts
diff --git a/packages/mailbox/src/migrations.test.ts b/src/migrations.test.ts
similarity index 100%
rename from packages/mailbox/src/migrations.test.ts
rename to src/migrations.test.ts
diff --git a/packages/mailbox/src/migrations.ts b/src/migrations.ts
similarity index 100%
rename from packages/mailbox/src/migrations.ts
rename to src/migrations.ts
diff --git a/packages/mailbox/src/mount-bus-failure.test.ts b/src/mount-bus-failure.test.ts
similarity index 100%
rename from packages/mailbox/src/mount-bus-failure.test.ts
rename to src/mount-bus-failure.test.ts
diff --git a/packages/mailbox/src/mount-event-op.test.ts b/src/mount-event-op.test.ts
similarity index 100%
rename from packages/mailbox/src/mount-event-op.test.ts
rename to src/mount-event-op.test.ts
diff --git a/packages/mailbox/src/mount.test.ts b/src/mount.test.ts
similarity index 100%
rename from packages/mailbox/src/mount.test.ts
rename to src/mount.test.ts
diff --git a/packages/mailbox/src/mount.ts b/src/mount.ts
similarity index 100%
rename from packages/mailbox/src/mount.ts
rename to src/mount.ts
diff --git a/packages/mailbox/src/mutations.test.ts b/src/mutations.test.ts
similarity index 100%
rename from packages/mailbox/src/mutations.test.ts
rename to src/mutations.test.ts
diff --git a/packages/mailbox/src/mutations.ts b/src/mutations.ts
similarity index 100%
rename from packages/mailbox/src/mutations.ts
rename to src/mutations.ts
diff --git a/packages/mailbox/src/openapi.test.ts b/src/openapi.test.ts
similarity index 100%
rename from packages/mailbox/src/openapi.test.ts
rename to src/openapi.test.ts
diff --git a/packages/mailbox/src/persist.test.ts b/src/persist.test.ts
similarity index 100%
rename from packages/mailbox/src/persist.test.ts
rename to src/persist.test.ts
diff --git a/packages/mailbox/src/persist.ts b/src/persist.ts
similarity index 100%
rename from packages/mailbox/src/persist.ts
rename to src/persist.ts
diff --git a/packages/mailbox/src/purge.test.ts b/src/purge.test.ts
similarity index 100%
rename from packages/mailbox/src/purge.test.ts
rename to src/purge.test.ts
diff --git a/packages/mailbox/src/purge.ts b/src/purge.ts
similarity index 100%
rename from packages/mailbox/src/purge.ts
rename to src/purge.ts
diff --git a/packages/mailbox/src/read-direction-and-date.test.ts b/src/read-direction-and-date.test.ts
similarity index 100%
rename from packages/mailbox/src/read-direction-and-date.test.ts
rename to src/read-direction-and-date.test.ts
diff --git a/packages/mailbox/src/read-fallbacks.test.ts b/src/read-fallbacks.test.ts
similarity index 100%
rename from packages/mailbox/src/read-fallbacks.test.ts
rename to src/read-fallbacks.test.ts
diff --git a/packages/mailbox/src/read-microsecond-cursor.test.ts b/src/read-microsecond-cursor.test.ts
similarity index 100%
rename from packages/mailbox/src/read-microsecond-cursor.test.ts
rename to src/read-microsecond-cursor.test.ts
diff --git a/packages/mailbox/src/read-multipart.test.ts b/src/read-multipart.test.ts
similarity index 100%
rename from packages/mailbox/src/read-multipart.test.ts
rename to src/read-multipart.test.ts
diff --git a/packages/mailbox/src/read-non-utc-session.test.ts b/src/read-non-utc-session.test.ts
similarity index 100%
rename from packages/mailbox/src/read-non-utc-session.test.ts
rename to src/read-non-utc-session.test.ts
diff --git a/packages/mailbox/src/read-page-boundary.test.ts b/src/read-page-boundary.test.ts
similarity index 100%
rename from packages/mailbox/src/read-page-boundary.test.ts
rename to src/read-page-boundary.test.ts
diff --git a/packages/mailbox/src/read.test.ts b/src/read.test.ts
similarity index 100%
rename from packages/mailbox/src/read.test.ts
rename to src/read.test.ts
diff --git a/packages/mailbox/src/read.ts b/src/read.ts
similarity index 100%
rename from packages/mailbox/src/read.ts
rename to src/read.ts
diff --git a/packages/mailbox/src/recipients.test.ts b/src/recipients.test.ts
similarity index 100%
rename from packages/mailbox/src/recipients.test.ts
rename to src/recipients.test.ts
diff --git a/packages/mailbox/src/recipients.ts b/src/recipients.ts
similarity index 100%
rename from packages/mailbox/src/recipients.ts
rename to src/recipients.ts
diff --git a/packages/mailbox/src/schema-check.test.ts b/src/schema-check.test.ts
similarity index 100%
rename from packages/mailbox/src/schema-check.test.ts
rename to src/schema-check.test.ts
diff --git a/packages/mailbox/src/schema-check.ts b/src/schema-check.ts
similarity index 100%
rename from packages/mailbox/src/schema-check.ts
rename to src/schema-check.ts
diff --git a/packages/mailbox/src/schema-ddl-parity.test.ts b/src/schema-ddl-parity.test.ts
similarity index 100%
rename from packages/mailbox/src/schema-ddl-parity.test.ts
rename to src/schema-ddl-parity.test.ts
diff --git a/packages/mailbox/src/schema.ts b/src/schema.ts
similarity index 100%
rename from packages/mailbox/src/schema.ts
rename to src/schema.ts
diff --git a/packages/mailbox/src/scope-validation.test.ts b/src/scope-validation.test.ts
similarity index 100%
rename from packages/mailbox/src/scope-validation.test.ts
rename to src/scope-validation.test.ts
diff --git a/packages/mailbox/src/sender-display.test.ts b/src/sender-display.test.ts
similarity index 100%
rename from packages/mailbox/src/sender-display.test.ts
rename to src/sender-display.test.ts
diff --git a/packages/mailbox/src/sse-heartbeat.test.ts b/src/sse-heartbeat.test.ts
similarity index 100%
rename from packages/mailbox/src/sse-heartbeat.test.ts
rename to src/sse-heartbeat.test.ts
diff --git a/packages/mailbox/src/sse-stream.test.ts b/src/sse-stream.test.ts
similarity index 100%
rename from packages/mailbox/src/sse-stream.test.ts
rename to src/sse-stream.test.ts
diff --git a/packages/mailbox/src/test-helpers.ts b/src/test-helpers.ts
similarity index 100%
rename from packages/mailbox/src/test-helpers.ts
rename to src/test-helpers.ts
diff --git a/packages/mailbox/src/vocabulary.test.ts b/src/vocabulary.test.ts
similarity index 100%
rename from packages/mailbox/src/vocabulary.test.ts
rename to src/vocabulary.test.ts
diff --git a/packages/mailbox/src/vocabulary.ts b/src/vocabulary.ts
similarity index 100%
rename from packages/mailbox/src/vocabulary.ts
rename to src/vocabulary.ts
diff --git a/packages/mailbox/src/write.test.ts b/src/write.test.ts
similarity index 100%
rename from packages/mailbox/src/write.test.ts
rename to src/write.test.ts
diff --git a/packages/mailbox/src/write.ts b/src/write.ts
similarity index 100%
rename from packages/mailbox/src/write.ts
rename to src/write.ts
diff --git a/packages/mailbox/tsconfig.build.json b/tsconfig.build.json
similarity index 89%
rename from packages/mailbox/tsconfig.build.json
rename to tsconfig.build.json
index 962e292..c94dafa 100644
--- a/packages/mailbox/tsconfig.build.json
+++ b/tsconfig.build.json
@@ -1,5 +1,5 @@
{
- "extends": "../../tsconfig.json",
+ "extends": "./tsconfig.json",
"compilerOptions": {
"types": [
"node"