Skip to content

Commit 9d63cb9

Browse files
committed
docs: refresh root README and add sub-package READMEs
1 parent 2d72013 commit 9d63cb9

10 files changed

Lines changed: 368 additions & 18 deletions

File tree

README.md

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,12 @@ MX Space Core is a headless CMS server built with **NestJS**, **PostgreSQL**, an
3333
| **Real-time** | WebSocket via Socket.IO with Redis adapter for multi-instance broadcast |
3434
| **Distribution** | RSS/Atom feeds, sitemap, local search, aggregate API |
3535
| **Auth** | Better Auth sessions, passkeys, OAuth, API keys (`x-api-key` header) |
36+
| **Mobile & Push** | Native iOS client (Space) with privacy-preserving APNs push via a self-hosted Push Relay |
3637
| **Deployment** | Docker (multi-arch), PM2, standalone binary |
3738

3839
## Tech Stack
3940

40-
- **Runtime**: Node.js >= 22 + TypeScript 5.9
41+
- **Runtime**: Node.js >= 22 + TypeScript 6
4142
- **Framework**: NestJS 11 + Fastify
4243
- **Database**: PostgreSQL 16 (Drizzle ORM)
4344
- **Cache**: Redis (ioredis)
@@ -55,13 +56,20 @@ MX Space Core is a headless CMS server built with **NestJS**, **PostgreSQL**, an
5556
mx-core/
5657
├── apps/
5758
│ ├── core/ # Main server application (NestJS + Fastify)
58-
│ └── admin/ # @mx-admin/admin — React 19 SPA, built locally and served at /proxy/qaqdmin
59+
│ ├── admin/ # @mx-admin/admin — React 19 SPA, built locally and served at /proxy/qaqdmin
60+
│ ├── ios/ # Space — native iOS admin client (UIKit + SwiftUI, XcodeGen)
61+
│ ├── push-relay/ # @mx-space/push-relay — independent, privacy-preserving APNs relay
62+
│ └── telemetry/ # Anonymous instance telemetry collector (Cloudflare Worker + D1)
5963
├── packages/
6064
│ ├── api-client/ # @mx-space/api-client — typed SDK for frontend & third-party clients
6165
│ ├── cli/ # @mx-space/cli (mxs) — owner-side CLI for content + config (Effect-TS)
6266
│ ├── db-schema/ # @mx-space/db-schema — shared Drizzle schema + Snowflake utilities (private)
67+
│ ├── editor/ # @mx-space/editor — Lexical-based editor contracts and projection utilities
68+
│ ├── ai/ # @mx-space/ai — shared AI contracts (SSE event unions) for server and clients
69+
│ ├── push-protocol/ # @mx-space/push-protocol — versioned protocol shared by mx-core and Push Relay
70+
│ ├── webhook/ # @mx-space/webhook — signature-verified webhook handler SDK
6371
│ ├── mongo-pg-cli/ # @mx-space/mongo-pg-cli — one-shot v11→v12 (MongoDB→PostgreSQL) data migration
64-
│ └── webhook/ # @mx-space/webhook — signature-verified webhook handler SDK
72+
│ └── e2e/ # End-to-end tests (Vitest + testcontainers)
6573
├── docker-compose.yml # Development stack (PostgreSQL + Redis + mx-migrate)
6674
├── dockerfile # Multi-stage production build
6775
└── docker-compose.server.yml # Production deployment template
@@ -71,7 +79,7 @@ mx-core/
7179

7280
```
7381
src/
74-
├── modules/ # 45 business modules
82+
├── modules/ # 50 business modules
7583
│ ├── ai/ # AI summary, translation, insights, writer, agent, task queue
7684
│ ├── auth/ # Better Auth: session, OAuth, passkey, API key
7785
│ ├── post/ # Blog posts
@@ -228,7 +236,10 @@ Every successful JSON response has the shape `{ data, meta? }`; every error has
228236

229237
### v11 → v12
230238

231-
v12 migrates the database from MongoDB to PostgreSQL. This is a hard cutover: all data must be migrated through the provided CLI before starting the new version. See [Upgrading to v12](./docs/migrations/v12.md).
239+
> [!WARNING]
240+
> v12 migrates the database from MongoDB to PostgreSQL. This is a hard cutover: all data must be migrated through the provided CLI before starting the new version.
241+
242+
See [Upgrading to v12](./docs/migrations/v12.md).
232243

233244
### v10 → v11
234245

@@ -244,15 +255,10 @@ v10 includes a breaking auth system refactor. See [Upgrading to v10](./docs/migr
244255
|---------|-------------|
245256
| [Yohaku](https://github.com/Innei/Yohaku) | Next.js frontend |
246257
| [`apps/admin`](./apps/admin) | `@mx-admin/admin` — React 19 admin dashboard (in-repo, built into the server release) |
258+
| [`apps/ios`](./apps/ios) | Space — native iOS admin client |
259+
| [`apps/push-relay`](./apps/push-relay) | Independent APNs Push Relay for self-hosted instances |
247260
| [@mx-space/api-client](./packages/api-client) | TypeScript API client SDK |
248261
| [@mx-space/cli](./packages/cli) | `mxs` CLI for posts/notes/pages/config (OIDC device auth) |
249262
| [@mx-space/mongo-pg-cli](./packages/mongo-pg-cli) | One-shot MongoDB → PostgreSQL migration for v11 → v12 |
250263
| [@mx-space/webhook](./packages/webhook) | Webhook handler SDK (signature-verified) |
251264
| [@haklex/rich-headless](https://github.com/innei/haklex) | Lexical editor (server-side) |
252-
253-
## License
254-
255-
- **`apps/`**[AGPLv3 with Additional Terms](./ADDITIONAL_TERMS.md)
256-
- **Everything else**[MIT](./LICENSE)
257-
258-
See [LICENSE](./LICENSE) for full details.

apps/admin/README.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# @mx-admin/admin
2+
3+
Admin dashboard for MX Space Core — a React 19 SPA served by the core server at `/proxy/qaqdmin`.
4+
5+
## Tech Stack
6+
7+
| Component | Technology |
8+
|-----------|------------|
9+
| Framework | React 19 + Vite 8 (+ React Compiler) |
10+
| Routing | react-router 8 (HashRouter, routes generated from `src/views`) |
11+
| State | Jotai + Zustand + TanStack Query |
12+
| UI | Base UI primitives + Tailwind CSS v4 |
13+
| Editor | Lexical (`@haklex/rich-*`), CodeMirror, Monaco, Excalidraw |
14+
| Auth | Better Auth + passkeys |
15+
| Realtime | socket.io-client |
16+
17+
Workspace contracts: `@mx-space/ai` (agent SSE events), `@mx-space/editor` (block node types).
18+
19+
## Development
20+
21+
```bash
22+
# From the repo root
23+
pnpm dev:admin # vite dev server on :9528
24+
25+
# Or from this directory
26+
pnpm dev
27+
pnpm build # production build → dist/
28+
pnpm test # vitest (happy-dom)
29+
pnpm typecheck # tsc --noEmit
30+
pnpm lint # oxlint
31+
```
32+
33+
For the full stack, run the core server alongside (`pnpm dev` at the repo root), then bridge the production-served admin to the local dev server via `http://localhost:2333/proxy/qaqdmin/dev-proxy`.
34+
35+
### Environment
36+
37+
All variables are optional (empty = safe defaults); see `.env.example`.
38+
39+
| Variable | Purpose |
40+
|----------|---------|
41+
| `VITE_APP_BASE_API` | mx-core API endpoint |
42+
| `VITE_APP_WEB_URL` | Blog frontend origin ("view on site" links) |
43+
| `VITE_APP_GATEWAY` | Socket.IO gateway (derived from API origin if empty) |
44+
| `VITE_APP_PUBLIC_URL` | Vite `base` for production builds |
45+
46+
Runtime URL resolution precedence: server-injected `window.injectData` → build-time `VITE_APP_*` → fallbacks (see `src/constants/env.ts`).
47+
48+
## Relation to apps/core
49+
50+
The admin is built **into** the core release, not downloaded at runtime:
51+
52+
- The production Docker image builds `apps/admin` and bakes `dist/` into `out/admin/` with a stamped `version` file.
53+
- The server serves it at `GET /proxy/qaqdmin` (see `apps/core/src/modules/pageproxy/`), semver-comparing the image-baked copy against a runtime-updated copy in `$DATA_DIR/admin` and serving the newer one.
54+
- The former standalone `mx-space/mx-admin` repo is archived; see [`docs/admin-monorepo-migration.md`](../../docs/admin-monorepo-migration.md).
55+
56+
## Release
57+
58+
- **With a core release**`apps/core/scripts/bump-admin-version.js` patch-bumps the admin version when it changed since the last server tag.
59+
- **Independently**`scripts/release-admin.sh [patch|minor|major]` tags `admin-v*`, triggering `admin-release.yml`, which builds, zips, and publishes the bundle to R2.

apps/core/readme.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ Design document: [docs/superpowers/specs/2026-05-05-database-migration-release-p
8383

8484
### Local Development
8585

86-
`pnpm dev` runs `pnpm migrate` automatically via the `predev` hook — no manual step required. To run it explicitly:
86+
`pnpm dev` applies pending schema migrations automatically before the server starts (see `src/dev.ts`) — no manual step required. To run it explicitly:
8787

8888
```bash
8989
pnpm -C apps/core run migrate # apply pending migrations
@@ -119,7 +119,7 @@ Before writing a migration, read the Claude skill at `.claude/skills/mx-migratio
119119
│ ├── schema/ # Drizzle table definitions
120120
│ ├── migrations/ # Drizzle SQL migrations (release-phase)
121121
│ └── app-migrations/ # application-layer one-shot data fixups
122-
├── modules/ # 45 business modules (ai, auth, post, note, comment …)
122+
├── modules/ # 50 business modules (ai, auth, post, note, comment …)
123123
├── processors/ # infrastructure services
124124
│ ├── database/ # PG connection + repository registry + BaseRepository
125125
│ ├── redis/ # cache / pub-sub / emitter
@@ -178,7 +178,7 @@ pnpm dev
178178

179179
| Component | Technology |
180180
|------------|-------------------------------------------|
181-
| Runtime | Node.js >= 22 + TypeScript 5.9 |
181+
| Runtime | Node.js >= 22 + TypeScript 6 |
182182
| Framework | NestJS 11 + Fastify |
183183
| Database | PostgreSQL 16 (Drizzle ORM) |
184184
| Cache | Redis (ioredis) |

apps/telemetry/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# @mx-space/telemetry
2+
3+
Anonymous instance telemetry collector for self-hosted mx-core deployments — a Cloudflare Worker backed by D1.
4+
5+
## What it collects
6+
7+
mx-core instances POST a minimal payload to `/collect`:
8+
9+
```json
10+
{ "instanceId": "<uuid>", "version": "13.25.1", "nodeVersion": "22.0.0", "event": "startup" }
11+
```
12+
13+
- `event` is one of `startup` or `heartbeat` (sent hourly).
14+
- `instanceId` is a random UUID persisted in the instance's data directory — it is not derived from any user or site data.
15+
- Field lengths are capped at 64 chars; anything else is rejected with `400`.
16+
17+
Users opt out with `--disable_telemetry` or `MX_DISABLE_TELEMETRY=true` (see `apps/core/src/utils/telemetry.util.ts`).
18+
19+
## Endpoints
20+
21+
| Route | Description |
22+
|-------|-------------|
23+
| `POST /collect` | Ingest one event (validated, inserted into D1) |
24+
| `GET /stats` | JSON aggregates: total/active instances, version & Node distributions, daily series (last 30d) |
25+
| `GET /` or `/dashboard` | Server-rendered Chart.js dashboard |
26+
27+
> [!NOTE]
28+
> `/stats` and the dashboard are gated externally by Cloudflare Zero Trust, not by the Worker itself. `/collect` is open (CORS `*`) so any self-hosted instance can post.
29+
30+
A daily cron (`0 3 * * *`) deletes rows older than 90 days.
31+
32+
## Development
33+
34+
```bash
35+
pnpm -C apps/telemetry run dev # wrangler dev
36+
pnpm -C apps/telemetry run db:migrate # apply schema.sql to local D1
37+
pnpm -C apps/telemetry run db:migrate:remote # apply schema.sql to remote D1
38+
pnpm -C apps/telemetry run deploy # wrangler deploy
39+
```
40+
41+
- `schema.sql` — the single `telemetry` table and its indexes.
42+
- `mock-data.sql` — seed data so the local dashboard renders non-empty.
43+
- `preview.html` — standalone static mock of the dashboard (hardcoded numbers) for design iteration; does not call the Worker.

packages/ai/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# @mx-space/ai
2+
3+
Shared AI contracts for mx-space — the single source of truth for wire formats exchanged between the server ([`apps/core`](../../apps/core)) and its clients ([`apps/admin`](../../apps/admin)).
4+
5+
> [!NOTE]
6+
> This package is private to the monorepo and consumed as TypeScript source via workspace exports.
7+
8+
## Contents
9+
10+
Currently the package contains one contract:
11+
12+
- **AI agent SSE events** (`src/ai-agent-sse.ts`) — a TypeBox union, `AiAgentSseEvent`, describing the server-sent event frames streamed by the AI agent: `text`, `thinking`, `toolcall`, `done`, and `error`. Exports `AiAgentSseEventSchema` (runtime validation) and the `AiAgentSseEvent` type.
13+
14+
## Usage
15+
16+
```ts
17+
// apps/core — server emits JSON-framed SSE
18+
import type { AiAgentSseEvent } from '@mx-space/ai'
19+
20+
// apps/admin — client transport parses the same union
21+
import type { AiAgentSseEvent } from '@mx-space/ai'
22+
```
23+
24+
Both sides import the same schema so neither defines the shape locally — a change here is a breaking change for server and client simultaneously, by design.
25+
26+
## Development
27+
28+
| Command | Description |
29+
|---------|-------------|
30+
| `pnpm build` | Build with tsdown (ESM + d.ts) |
31+
| `pnpm typecheck` | TypeScript type checking |

packages/api-client/readme.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# @mx-space/api-client
22

3-
A framework-agnostic TypeScript/JavaScript SDK for the MX Space server (MServer v3). It wraps common API endpoints with typed request methods and response types for fast frontend and server-side integration.
3+
A framework-agnostic TypeScript/JavaScript SDK for the MX Space Core server. It wraps common API endpoints with typed request methods and response types for fast frontend and server-side integration.
44

55
---
66

@@ -23,7 +23,7 @@ A framework-agnostic TypeScript/JavaScript SDK for the MX Space server (MServer
2323
## Requirements
2424

2525
- **Node.js** ≥ 22 (see `engines` in `package.json`)
26-
- **MX Space server**: v12+ (PostgreSQL + Snowflake IDs) for api-client **v4.x**. See the [Version Compatibility & Migration](#version-compatibility--migration) section for older lines.
26+
- **MX Space server**: v12+ (PostgreSQL + Snowflake IDs) for api-client **v5.x**. See the [Version Compatibility & Migration](#version-compatibility--migration) section for older lines.
2727

2828
---
2929

@@ -145,6 +145,9 @@ Inject one or more controllers so the client exposes them (e.g. `client.post`, `
145145
| SubscribeController | `subscribe` | Subscriptions |
146146
| ServerlessController | `serverless` | Serverless functions |
147147
| AckController | `ack` | Ack |
148+
| CompanionController | `companion` | Companion (presence/chat) |
149+
| EnrichmentController | `enrichment` | URL enrichment / screenshot pipeline |
150+
| MembershipController | `membership` | Membership / subscriptions |
148151

149152
**Example — inject all controllers:**
150153

@@ -239,7 +242,8 @@ client.note.proxy.something.other('123456').info.toString(true)
239242

240243
| api-client version | Server version | Notes |
241244
|--------------------|-----------------|-------|
242-
| **v4.x** (current) | ≥ 12 | PostgreSQL + Snowflake IDs. Pairs with the PG cutover. |
245+
| **v5.x** (current) | ≥ 12 | PostgreSQL + Snowflake IDs. Pairs with the PG cutover. |
246+
| v4.x | ≥ 12 | First PostgreSQL-compatible line. |
243247
| v3.x | 11 → early 12 | Transitional during the PG cutover. |
244248
| v2.x | 10 → 11 | MongoDB + Better Auth. ObjectId IDs. |
245249
| v1.x | ≤ 9 | Legacy JWT auth. |

packages/db-schema/README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# @mx-space/db-schema
2+
3+
Central Drizzle ORM schema and ID layer for mx-space. Single source of truth for table definitions, the branded `EntityId` type, and the Snowflake ID generator used across the monorepo.
4+
5+
> [!NOTE]
6+
> This package is private to the monorepo and consumed as TypeScript source via workspace exports.
7+
8+
## Exports
9+
10+
| Subpath | Contents |
11+
|---------|----------|
12+
| `@mx-space/db-schema` | Everything: schema + ID layer + `CollectionRefTypes` |
13+
| `@mx-space/db-schema/schema` | All Drizzle table definitions |
14+
| `@mx-space/db-schema/id` | `EntityId`, validators, `SnowflakeGenerator`, `resolveSnowflakeWorkerId` |
15+
16+
## Schema Modules
17+
18+
~50 tables organized by domain (`src/schema/`):
19+
20+
| Module | Tables |
21+
|--------|--------|
22+
| `content` | `posts`, `notes`, `pages`, `categories`, `topics`, `comments`, `drafts`, `draftHistories`, `recentlies`, `postRelatedPosts` |
23+
| `auth` | `readers`, `ownerProfiles`, `accounts`, `sessions`, `apiKeys`, `passkeys`, `verifications`, `deviceCodes` (Better Auth; camelCase columns kept on purpose) |
24+
| `ai` | `aiTranslations`, `translationEntries`, `aiSummaries`, `aiInsights`, `aiAgentConversations`, `aiTts`, `aiTtsBlocks`, `aiGenerationMetrics` |
25+
| `ops` | `options`, `activities`, `analyzes`, `links`, `projects`, `says`, `snippets`, `subscribes`, `fileReferences`, `fileUsages`, `webhooks`, `webhookEvents`, `serverlessStorages`, `serverlessLogs`, `slugTrackers`, `pollVotes`, `pollVoteOptions`, `metaPresets` |
26+
| `billing` | `memberships`, `billingWebhookEvents` (idempotency via provider + event_id unique) |
27+
| `companion` | `companionDevices`, `companionPairings` — device IDs are public protocol IDs (UUID/ULID), not Snowflake EntityIds |
28+
| `enrichment` | `enrichmentCache`, `enrichmentCaptures` |
29+
| `push` | `pushRelaySources`, `pushRelayBindings`, `pushRelayDeliveries` |
30+
| `search` | `searchDocuments` (per-ref + per-lang term-frequency store) |
31+
| `migration` | `appMigrations`, `schemaMigrations`, `mongoIdMap`, `authIdMap`, `dataMigrationRuns` — migration-only, not for business queries |
32+
33+
## ID Layer
34+
35+
- **`EntityId`** — branded Snowflake decimal string. Validators: `isEntityIdString`, `parse`, `tryParse`, `serialize`; Zod schemas `zEntityId` / `zEntityIdOrInt`. Max value 2^63 − 1.
36+
- **`SnowflakeGenerator`** — pure generator: 41-bit timestamp (custom epoch `1746144000000`), 10-bit worker, 12-bit sequence, with clock-drift guards.
37+
- **`resolveSnowflakeWorkerId`** — resolves the worker ID from the configured base plus `SNOWFLAKE_WORKER_OFFSET`, with a PM2 fallback.
38+
39+
## Usage
40+
41+
```ts
42+
// apps/core — the whole table surface in one line
43+
export * from '@mx-space/db-schema/schema'
44+
45+
// ID generation (wrapped by apps/core's Nest SnowflakeService)
46+
import { resolveSnowflakeWorkerId, SnowflakeGenerator } from '@mx-space/db-schema/id'
47+
48+
// api-client — polymorphic ref enum
49+
import { CollectionRefTypes } from '@mx-space/db-schema'
50+
```
51+
52+
## Development
53+
54+
| Command | Description |
55+
|---------|-------------|
56+
| `pnpm build` | Build with tsdown (three entries: index, schema, id) |
57+
| `pnpm typecheck` | TypeScript type checking |

packages/e2e/README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# @mx-space/e2e
2+
3+
End-to-end test harness for the mx-space CLI ↔ server contract. Boots a **real** in-process NestJS core server backed by throwaway PostgreSQL and Redis containers, seeds an owner, then drives the **real** `mxs` CLI binary as a subprocess against it.
4+
5+
## How it works
6+
7+
Each test file follows the same lifecycle:
8+
9+
1. **Containers**`createE2EBackend()` starts a `postgres:17-alpine` testcontainer (per-worker isolated database, migrated from `apps/core/src/database/migrations`) and a per-backend `redis:7-alpine` testcontainer.
10+
2. **Env seeding**`PG_*` / `REDIS_*` / `MIGRATIONS_DIR` / `JWT_SECRET` are written into `process.env` *before* `apps/core`'s `AppModule` is dynamically imported, so no frozen config defaults leak in.
11+
3. **Server**`@nestjs/testing` compiles `AppModule`; Fastify listens on an ephemeral loopback port in dev mode (no `/api/vN` prefix).
12+
4. **Auth** — an owner is inserted directly into PostgreSQL (Better Auth credential rows), a bearer token is minted via `signInUsername`, and a CLI profile is written under a temporary `$XDG_CONFIG_HOME`.
13+
5. **CLI** — tests spawn `packages/cli/src/bin/mxs.ts` via `tsx` with mode flags and assert on the parsed `{ ok, data }` envelope. `runAcrossModes()` walks all five output modes (`json`, `pretty-json`, `readable`, `llm`, `xml`).
14+
15+
## Running
16+
17+
```bash
18+
# From the repo root
19+
pnpm e2e
20+
21+
# Or from this package
22+
pnpm -C packages/e2e run test
23+
```
24+
25+
Requires Docker (testcontainers). No environment variables are strictly required — all are seeded with `??=` defaults. Set `PG_VERIFY_URL` to reuse an external PostgreSQL instead of starting a container.
26+
27+
> [!NOTE]
28+
> Hook and test timeouts are 120s; CI runs with 2 retries and 2 workers.
29+
30+
## Layout
31+
32+
```
33+
src/
34+
├── helpers/ # backend boot, PG/Redis containers, CLI subprocess, auth seeding, fixtures
35+
└── fixtures/ # canonical owner credentials
36+
test/
37+
├── auth-device-flow.test.ts, auth-login-state.test.ts, post-crud.test.ts, profile-switch.test.ts
38+
├── ai/ # AI artifact management
39+
├── file/ # upload flow
40+
├── help/ # help output contracts
41+
├── output/ # output-mode format matrix
42+
├── resources/ # category / comment / config / note / page / project / snippet / topic CRUD
43+
└── skill/ # skill list / get / search output
44+
```

0 commit comments

Comments
 (0)