Skip to content

Commit c895067

Browse files
os-zhuangclaude
andcommitted
feat(runtime): package-scoped single-item metadata resolution via ?package= (ADR-0048)
REST single-item GET /meta/:type/:name now passes its `?package=` query into `getItem(type, name, currentPackageId)` — the package-scoped (prefer-local) resolution added with the namespace gate. Single-item fetch was previously context-free (only list + protocol paths were package-aware). Enables package-scoped single-doc resolution (doc content lives only on the single-item endpoint), so `doc` names no longer need a namespace prefix for uniqueness — prefix demoted to a recommended convention (doc.zod comments updated), matching page/dashboard/report. Verified: curl `/api/v1/meta/doc/showcase_index?package=com.example.showcase` returns the doc with content; the package param reaches getItem's 3rd arg. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent b9ceaa4 commit c895067

3 files changed

Lines changed: 32 additions & 11 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
"@objectstack/runtime": minor
3+
"@objectstack/spec": minor
4+
---
5+
6+
feat(runtime): package-scoped single-item metadata resolution via `?package=` (ADR-0048)
7+
8+
The REST single-item GET `/meta/:type/:name` now threads its `?package=` query
9+
into `getItem(type, name, currentPackageId)` (the package-scoped resolution
10+
added in the namespace-gate work). Previously only the *list* and the protocol
11+
path were package-aware; a single-item fetch was context-free.
12+
13+
This lets a caller resolve one item scoped to a package — notably the doc
14+
viewer (`/apps/:packageId/docs/:name`), whose content lives only on the
15+
single-item endpoint. As a result, `doc` names no longer need a namespace
16+
prefix for uniqueness (the prefix becomes a recommended convention, like
17+
`page`/`dashboard`/`report`); `doc.zod` doc-comments updated accordingly.

packages/runtime/src/http-dispatcher.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1173,7 +1173,9 @@ export class HttpDispatcher {
11731173
const metaSvc = await this.resolveService('metadata', _context.environmentId);
11741174
if (metaSvc && typeof (metaSvc as any).getItem === 'function') {
11751175
try {
1176-
const data = await (metaSvc as any).getItem(singularType, name);
1176+
// ADR-0048 — thread `?package=` so single-item resolution is
1177+
// package-scoped (prefer-local), matching list resolution.
1178+
const data = await (metaSvc as any).getItem(singularType, name, packageId);
11771179
if (data) return { handled: true, response: this.success(data) };
11781180
} catch { /* not found */ }
11791181
}

packages/spec/src/system/doc.zod.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ import { lazySchema } from '../shared/lazy-schema';
1212
* build time; TS-first stacks may also declare items inline via
1313
* `defineStack({ docs: [...] })`.
1414
*
15-
* Identity model: `name` = filename stem and MUST carry the package
16-
* namespace prefix (`crm_lead_guide.md` → `crm_lead_guide`). Unlike object
17-
* names (validated in the kernel because they map to physical table
18-
* names), doc-name prefixing is a *logical* uniqueness requirement — the
19-
* metadata registry key carries no package coordinate — so it is enforced
20-
* by build/publish lint, not by this schema.
15+
* Identity model: `name` = filename stem (lowercase snake_case). A namespace
16+
* prefix (`crm_lead_guide`) is a *recommended convention*, no longer required:
17+
* per ADR-0048, single-doc resolution is package-scoped (`getItem('doc', name,
18+
* packageId)` via `?package=` on the detail route), so two packages may ship a
19+
* doc with the same bare name and each resolves within its own package — just
20+
* like `page`/`dashboard`/`report`. The prefix stays useful for readable,
21+
* globally-unique filenames but is not load-bearing for uniqueness.
2122
*
2223
* Docs are inert data: the kernel registers them without parsing
2324
* `content`, and they participate in no runtime behavior. Renderers
@@ -26,13 +27,14 @@ import { lazySchema } from '../shared/lazy-schema';
2627
*/
2728
export const DocSchema = lazySchema(() => z.object({
2829
/**
29-
* Unique doc name; equals the source filename stem. Namespace-prefixed
30-
* (e.g. `crm_lead_guide`) — see ADR-0046 §3.2 for the enforcement
31-
* posture (build/publish lint, not kernel rejection).
30+
* Doc name; equals the source filename stem. Lowercase snake_case. A
31+
* namespace prefix (e.g. `crm_lead_guide`) is recommended for readable,
32+
* globally-unique filenames but NOT required — single-doc resolution is
33+
* package-scoped (ADR-0048), so bare names are unique within their package.
3234
*/
3335
name: z.string()
3436
.regex(/^[a-z][a-z0-9_]*$/, 'name must be lowercase snake_case')
35-
.describe('Unique doc name (= filename stem, namespace-prefixed snake_case)'),
37+
.describe('Doc name (= filename stem, snake_case; namespace prefix recommended, not required)'),
3638

3739
/**
3840
* Display title. The CLI derives it from frontmatter `title:` or the

0 commit comments

Comments
 (0)