You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fields that hold record IDs of another type use `"x-ref-type": "TypeName"` to document the relationship. This enables linked-record navigation in the UI and helps LLMs understand the relational graph.
215
+
216
+
### Schema labeling
217
+
218
+
Schemas can be labeled post-hoc with human-readable names or URIs (e.g. `schema.org/Person`, `dc.author.v1`). Labels enable discovery across collections without upfront coordination.
219
+
220
+
-`POST /api/schemas/:id/labels` — Add a label
221
+
-`DELETE /api/schemas/:id/labels/:label` — Remove a label
222
+
-`GET /api/schemas?label=...` — Search by label
223
+
- Labels are injected as `x-underlay-labels` in schema exports (opt-out via `?raw=true`)
224
+
225
+
### Schema discovery API
226
+
227
+
| Endpoint | Purpose |
228
+
|----------|--------|
229
+
|`GET /api/schemas`| Global search (filter by `q`, `slug`, `label`, `schema_hash`) |
230
+
|`GET /api/schemas/:id`| Single schema with labels + usage info |
The system supports three levels of privacy (type-level, field-level, record-level) via `"private": true` annotations. When changing how privacy works, update:
206
-
-`src/api/routes/versions.ts` — filtering logic
257
+
The system supports three levels of privacy (type-level, field-level, record-level) via `"private": true` annotations in per-type schemas. When changing how privacy works, update:
258
+
-`src/api/routes/versions.ts` — filtering logic (reads from `version_schemas` JOIN `schemas`)
207
259
-`src/api/routes/files.ts` — file access checks
260
+
-`src/api/routes/schemas.ts` — public schema filtering
- Version: an immutable snapshot containing a JSON Schema, records, and file references. Sequential integer numbers, auto-derived semver.
36
36
- Record: a flat JSON object with { id, type, data }. Records reference other records by id and files by hash.
37
37
- File: a binary blob stored by SHA-256 hash, referenced in record data as {"$file": "sha256:<hex>"}.
38
-
- Schema: a JSON Schema document describing the record types in a version. Schema changes trigger a major version bump.
38
+
- Schema: a JSON Schema document for a single record type, stored as a global, immutable, content-addressed entity. Each type gets its own schema. Schema changes trigger a major version bump.
39
+
- Schema labeling: schemas can be labeled post-hoc with URIs or names (e.g. "schema.org/Person") for cross-collection discovery.
- base_version: the version number you diffed against. null for first push. Used for optimistic locking.
138
-
- schema: required on first push and whenever the schema changes. Omit if unchanged.
144
+
- schemas: per-type JSON Schema map. Required on first push. If omitted on subsequent pushes and records validate against carried-forward schemas, they are carried forward automatically.
145
+
- schemas[TypeName]: JSON Schema for that record type. Use "x-ref-type" to annotate foreign key fields.
- app_id: identifier for the pushing application (optional).
141
148
- actor_id: identifier for the user or process that triggered the push (optional).
@@ -157,7 +164,7 @@ Missing files (422 — records reference files not yet uploaded):
157
164
→ Upload the listed files, then retry the push.
158
165
159
166
### First push (no existing versions)
160
-
Set base_version to null. Put all records in changes.added. Include the schema.
167
+
Set base_version to null. Put all records in changes.added. Include schemas for all types.
161
168
162
169
---
163
170
@@ -182,11 +189,26 @@ The schema declares which fields are references, so tools can resolve them at re
182
189
183
190
---
184
191
192
+
## Schema Discovery
193
+
194
+
Schemas are globally deduplicated by content hash. If two collections use the same type shape, they share the same schema row. This enables cross-collection discovery.
195
+
196
+
GET /api/schemas → search schemas (?q=text&slug=TypeName&label=uri&schema_hash=sha256:...&limit=50&offset=0)
197
+
GET /api/schemas/:id → single schema with labels and usage info
198
+
GET /api/collections/:owner/:slug/schemas → schemas for latest version (?version=N for specific, ?raw=true to skip label enrichment)
199
+
POST /api/schemas/:id/labels → add label {"label": "schema.org/Person"} (requires write scope)
When schemas are returned via the collection schemas endpoint, known labels are injected as "x-underlay-labels" on the schema body (opt-out with ?raw=true).
203
+
204
+
---
205
+
185
206
## Versioning
186
207
187
208
- Versions are sequential integers (1, 2, 3, ...).
188
209
- Semver is derived automatically: schema change → major, record change → minor, metadata-only → patch.
189
-
- Each version has a content-addressed hash computed from schema + sorted records + sorted file hashes.
210
+
- Schema change = any type's schema_id changed, or types added/removed between versions.
211
+
- Each version has a content-addressed hash computed from sorted type→schema_hash map + sorted records + sorted file hashes.
190
212
- Versions are immutable once created.
191
213
192
214
---
@@ -206,27 +228,24 @@ Underlay supports fine-grained privacy at three levels: types, fields, and indiv
206
228
Private data is stored alongside public data in the same version but is only visible to the collection owner.
207
229
208
230
### Private Types
209
-
Mark an entire type as private in the schema. All records of that type are hidden from public readers.
231
+
Mark an entire type as private in its schema. All records of that type are hidden from public readers.
0 commit comments