|
| 1 | +# DO Naming Convention: Include Type Segment |
| 2 | + |
| 3 | +**Status:** Pending |
| 4 | +**Scope:** 2 files, ~10 line changes |
| 5 | +**Worktree:** Separate branch off `main` (not this branch) |
| 6 | + |
| 7 | +## Problem |
| 8 | + |
| 9 | +DO names are `user:{userId}:{resourceName}` for both workspace and document routes. Since `WORKSPACE_ROOM` and `DOCUMENT_ROOM` are separate CF namespaces, `idFromName()` produces different DO instances—but the name strings are identical. This causes a bug in the `durable_object_instance` tracking table (see sibling spec) where a UNIQUE constraint on `doName` collides when a user has a workspace and document with the same `resourceName`. |
| 10 | + |
| 11 | +More fundamentally, the current naming scheme doesn't encode **what kind of DO** it is. Given the string `user:abc:notes`, you can't tell if it's a workspace or a document without additional context. |
| 12 | + |
| 13 | +## Decision |
| 14 | + |
| 15 | +**All DO names include the type segment: `user:{userId}:{doType}:{resourceName}`.** |
| 16 | + |
| 17 | +| Route | Old DO Name | New DO Name | |
| 18 | +|---|---|---| |
| 19 | +| `GET /workspaces/epicenter.tab-manager` | `user:abc:epicenter.tab-manager` | `user:abc:workspace:epicenter.tab-manager` | |
| 20 | +| `GET /documents/my-note` | `user:abc:my-note` | `user:abc:document:my-note` | |
| 21 | + |
| 22 | +### Why this matters |
| 23 | + |
| 24 | +1. **Self-documenting**—the DO name tells you exactly what it is |
| 25 | +2. **Globally unique across namespaces**—no collisions in tracking tables |
| 26 | +3. **Reverse-lookup friendly**—given a DO name from logs/analytics, you can parse out type + resource |
| 27 | +4. **Future-proof**—if a third DO type is added, the pattern scales |
| 28 | + |
| 29 | +### Migration strategy |
| 30 | + |
| 31 | +**Clean break.** Same approach as the `tab-manager` → `epicenter.tab-manager` rename. Local-first clients hold the full Y.Doc and will re-sync to the new (empty) DO on next connection. Old DOs sit idle and can be cleaned up later. |
| 32 | + |
| 33 | +**Document snapshots will be lost** for `DocumentRoom` DOs (stored in the old DO's SQLite). This is acceptable during early development—no production users have critical snapshot history yet. |
| 34 | + |
| 35 | +## Implementation Plan |
| 36 | + |
| 37 | +### Task 1: Update stub functions in `app.ts` |
| 38 | + |
| 39 | +- [ ] Change `getWorkspaceStub` DO name: `user:${userId}:${workspace}` → `user:${userId}:workspace:${workspace}` |
| 40 | +- [ ] Change `getDocumentStub` DO name: `user:${userId}:${document}` → `user:${userId}:document:${document}` |
| 41 | + |
| 42 | +**`getWorkspaceStub` (app.ts:294–297):** |
| 43 | + |
| 44 | +```typescript |
| 45 | +function getWorkspaceStub(c: Context<Env>) { |
| 46 | + const doName = `user:${c.var.user.id}:workspace:${c.req.param('workspace')}`; |
| 47 | + return c.env.WORKSPACE_ROOM.get(c.env.WORKSPACE_ROOM.idFromName(doName)); |
| 48 | +} |
| 49 | +``` |
| 50 | + |
| 51 | +**`getDocumentStub` (app.ts:300–303):** |
| 52 | + |
| 53 | +```typescript |
| 54 | +function getDocumentStub(c: Context<Env>) { |
| 55 | + const doName = `user:${c.var.user.id}:document:${c.req.param('document')}`; |
| 56 | + return c.env.DOCUMENT_ROOM.get(c.env.DOCUMENT_ROOM.idFromName(doName)); |
| 57 | +} |
| 58 | +``` |
| 59 | + |
| 60 | +### Task 2: Update JSDoc comments |
| 61 | + |
| 62 | +- [ ] Update the DO name namespacing JSDoc block above `getWorkspaceStub` (app.ts:265–291) |
| 63 | +- [ ] Update JSDoc in `base-sync-room.ts` (line 90–93) that references the naming scheme |
| 64 | + |
| 65 | +**JSDoc update in app.ts (first line of the block, line 266):** |
| 66 | + |
| 67 | +``` |
| 68 | +- * DO name namespacing: `user:{userId}:{workspace|document}` |
| 69 | ++ * DO name namespacing: `user:{userId}:{type}:{name}` |
| 70 | +``` |
| 71 | + |
| 72 | +**JSDoc update in base-sync-room.ts (lines 90–93):** |
| 73 | + |
| 74 | +``` |
| 75 | +- * DO names are user-scoped: the Worker prefixes `user:{userId}:` to the |
| 76 | +- * client-provided workspace or document name before calling `idFromName()`. |
| 77 | ++ * DO names are user-scoped: the Worker constructs |
| 78 | ++ * `user:{userId}:{type}:{name}` before calling `idFromName()`, where |
| 79 | ++ * `{type}` is `workspace` or `document`. |
| 80 | + * This ensures each user's data is isolated in separate DO instances, even |
| 81 | + * if multiple users create workspaces with the same name (e.g., "epicenter.tab-manager"). |
| 82 | +``` |
| 83 | + |
| 84 | +### Verification |
| 85 | + |
| 86 | +- [ ] `bun run typecheck` passes from `apps/api/` |
| 87 | +- [ ] Grep for old pattern `user:\${c.var.user.id}:\${c.req.param` returns 0 results in `apps/api/src/` |
| 88 | +- [ ] Grep for new pattern `user:\${c.var.user.id}:workspace:` and `user:\${c.var.user.id}:document:` returns expected results |
| 89 | + |
| 90 | +## Files Changed |
| 91 | + |
| 92 | +- `apps/api/src/app.ts` — `getWorkspaceStub`, `getDocumentStub`, JSDoc |
| 93 | +- `apps/api/src/base-sync-room.ts` — JSDoc comment only |
| 94 | + |
| 95 | +## Commit |
| 96 | + |
| 97 | +``` |
| 98 | +refactor(api): include type segment in DO names |
| 99 | +
|
| 100 | +Change DO naming from `user:{userId}:{name}` to |
| 101 | +`user:{userId}:{type}:{name}` so names are globally unique across |
| 102 | +DO namespaces. Clean break—clients re-sync to new DOs. |
| 103 | +``` |
0 commit comments