@groovenet/contracts 0.1.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -5
- package/build/albums.d.ts +692 -0
- package/build/albums.d.ts.map +1 -0
- package/build/albums.js +119 -0
- package/build/albums.js.map +1 -0
- package/build/friends.d.ts +63 -0
- package/build/friends.d.ts.map +1 -0
- package/build/friends.js +37 -0
- package/build/friends.js.map +1 -0
- package/build/index.d.ts +6 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +6 -0
- package/build/index.js.map +1 -1
- package/build/playlists.d.ts +432 -0
- package/build/playlists.d.ts.map +1 -0
- package/build/playlists.js +149 -0
- package/build/playlists.js.map +1 -0
- package/build/recordCare.d.ts +961 -0
- package/build/recordCare.d.ts.map +1 -0
- package/build/recordCare.js +245 -0
- package/build/recordCare.js.map +1 -0
- package/build/spins.d.ts +1077 -0
- package/build/spins.d.ts.map +1 -0
- package/build/spins.js +261 -0
- package/build/spins.js.map +1 -0
- package/build/tracks.d.ts +476 -0
- package/build/tracks.d.ts.map +1 -0
- package/build/tracks.js +171 -0
- package/build/tracks.js.map +1 -0
- package/package.json +15 -5
package/README.md
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
# @groovenet/contracts
|
|
2
2
|
|
|
3
|
-
Shared runtime schemas and TypeScript types for Groovenet API resources.
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Shared runtime schemas and TypeScript types for Groovenet API resources. It is
|
|
4
|
+
the source of truth for the wire enums, entities, and request/response payloads
|
|
5
|
+
of live sets (`liveSets.ts`), playlists (`playlists.ts`), tracks (`tracks.ts`),
|
|
6
|
+
albums (`albums.ts`), friends with the default-library setting (`friends.ts`)
|
|
7
|
+
and physical-copy record care (`recordCare.ts`).
|
|
8
|
+
|
|
9
|
+
[`INVENTORY.md`](INVENTORY.md) lists every published API operation with its
|
|
10
|
+
family and migration status, and the order the remaining families follow.
|
|
6
11
|
|
|
7
12
|
## Adding or migrating a contract
|
|
8
13
|
|
|
@@ -17,7 +22,119 @@ entities, and update payloads.
|
|
|
17
22
|
components from the route contract. Add an OpenAPI test for the resource's
|
|
18
23
|
shape and enum values.
|
|
19
24
|
|
|
20
|
-
Migrate one resource family at a time: live sets, playlists, tracks
|
|
21
|
-
|
|
25
|
+
Migrate one resource family at a time: live sets, playlists, tracks, albums,
|
|
26
|
+
friends, record care and spins are done; `INVENTORY.md` orders the rest. Preserve permissive response fields where
|
|
22
27
|
the database returns additional wire fields; make request schemas strict only
|
|
23
28
|
when the endpoint already rejects unknown fields.
|
|
29
|
+
|
|
30
|
+
## Pattern for endpoints with route-specific coercion
|
|
31
|
+
|
|
32
|
+
Playlist endpoints showed three cases the live-set migration did not need:
|
|
33
|
+
|
|
34
|
+
- **Text-coerced ids stay in the app.** Path and query ids arrive as strings, so
|
|
35
|
+
the app keeps `intFromInputSchema` for `params`/`query` schemas. The shared
|
|
36
|
+
schema declares the wire type (`id: z.number().int()`); the app derives its
|
|
37
|
+
route schema with `.extend({ id: intFromInputSchema })`. Do not move
|
|
38
|
+
`z.preprocess` into this package: it has no JSON Schema form.
|
|
39
|
+
- **Schemas with defaults or refinements publish their input shape.** Pass
|
|
40
|
+
`{ io: "input" }` to `z.toJSONSchema` for those (`PlaylistCreate`,
|
|
41
|
+
`PlaylistSpinsInput`) so OpenAPI documents what a client sends, not what the
|
|
42
|
+
parser produces. Refinements such as "not both `performed_at` and
|
|
43
|
+
`performance_id`" run at runtime but are not expressible in JSON Schema;
|
|
44
|
+
describe them in the route description.
|
|
45
|
+
- **Keep the app's schema names as aliases.** `api-contract/schemas.ts` re-exports
|
|
46
|
+
shared schemas under the names routes and `internalApi/*` already import, so a
|
|
47
|
+
migration changes where a schema is defined without touching every caller.
|
|
48
|
+
|
|
49
|
+
Name wire types after the resource, not the endpoint (`Playlist`,
|
|
50
|
+
`PlaylistDetail`, `PlaylistSpinsResult`), and reference them from
|
|
51
|
+
`openapi.ts` via the exported `*JsonSchemas` object. Cover new shapes with a
|
|
52
|
+
test in this package and an OpenAPI `$ref` assertion in
|
|
53
|
+
`my-collection-search/src/api-contract/__tests__/openapi.test.ts`.
|
|
54
|
+
|
|
55
|
+
## Pattern for large entities (tracks and albums)
|
|
56
|
+
|
|
57
|
+
Track and album rows carry dozens of database columns, which raised conventions
|
|
58
|
+
the smaller resources did not need:
|
|
59
|
+
|
|
60
|
+
- **Export the entity type from a non-passthrough shape.** `.passthrough()` adds
|
|
61
|
+
an index signature (`[x: string]: unknown`) that collapses `Omit`/`Pick` and
|
|
62
|
+
makes every key `unknown`. Declare `const trackShape = z.object({...})`, export
|
|
63
|
+
`trackSchema = trackShape.passthrough()` for parsing and OpenAPI, and infer
|
|
64
|
+
`TrackEntity` from `trackShape`.
|
|
65
|
+
- **The wire schema is honest about nulls; consumers narrow.** `Track` and
|
|
66
|
+
`Album` allow `null` on columns the database can return null for. The app's
|
|
67
|
+
`types/track.ts` narrows the handful of fields its UI never sees null
|
|
68
|
+
(`discogs_url`, `album_thumbnail`, ...) with `Omit<> & {...}` instead of
|
|
69
|
+
loosening the contract.
|
|
70
|
+
- **Entities in list/search responses stay opaque at runtime.** The shared
|
|
71
|
+
`TrackSearchResponse` documents `hits` as `Track`, but the search route's
|
|
72
|
+
runtime schema overrides `hits` to `z.array(z.unknown())` because raw SQL rows
|
|
73
|
+
have never been rejected. Do not parse whole entities in a route unless a
|
|
74
|
+
real-database test shows they conform (timestamps and numerics arrive in
|
|
75
|
+
driver-specific types).
|
|
76
|
+
- **Query schemas stay in the app.** `trackSearchQuerySchema` and
|
|
77
|
+
`albumSearchQuerySchema` here describe the coerced wire values; the app's
|
|
78
|
+
`*GetQuerySchema` keeps `intFromInputSchema`/`booleanFlagSchema` coercion.
|
|
79
|
+
The client's `trackSearchGetQuerySchema` is still a coercing copy (deferred).
|
|
80
|
+
- **Add response validation only to flat payloads.** Routes that return a small
|
|
81
|
+
fixed object (`essentia`, `playlists`, `DELETE`, extract-cover) parse their
|
|
82
|
+
response with the shared schema; lenient request handling (e.g.
|
|
83
|
+
`playlist_counts` silently dropping malformed refs) is preserved.
|
|
84
|
+
|
|
85
|
+
## Pattern for the remaining families (#552)
|
|
86
|
+
|
|
87
|
+
Friends and the default-library setting were the first of #552's slices, and
|
|
88
|
+
fixed the conventions the rest follow:
|
|
89
|
+
|
|
90
|
+
- **Update `INVENTORY.md` in the same PR.** `contractInventory.test.ts` in the
|
|
91
|
+
app fails when an OpenAPI operation has no row, a row names an operation that
|
|
92
|
+
no longer exists, an `exception` has no reason, or a row marked `contracts`
|
|
93
|
+
references no shared component. Add the family's `*JsonSchemas` to that test's
|
|
94
|
+
`SHARED_COMPONENTS` when you register them in `openapi.ts`.
|
|
95
|
+
- **Request bodies publish their input shape.** A plain `z.object` strips
|
|
96
|
+
unknown keys at runtime, but its default (output) JSON Schema says
|
|
97
|
+
`additionalProperties: false`, which tells clients the server rejects them.
|
|
98
|
+
Publish request bodies with `{ io: "input" }` unless the route really rejects
|
|
99
|
+
unknown fields (`.strict()`).
|
|
100
|
+
- **Keep the route's own coercion when the app helper would change it.**
|
|
101
|
+
`intFromInputSchema` uses `parseInt`, so it accepts `"3abc"`; the
|
|
102
|
+
default-library `PUT` has always coerced with `Number()` and rejected that.
|
|
103
|
+
The route keeps `Number()` and validates the result with the shared wire
|
|
104
|
+
schema, so only already-broken input changes (`2.5` is now a 400, where it
|
|
105
|
+
used to reach Postgres and come back a 500).
|
|
106
|
+
- **Reference components with `componentRef(name)`** in `routes.ts`.
|
|
107
|
+
`openapi.ts` inlines `$ref`s before generating response examples, so a
|
|
108
|
+
migrated response keeps a realistic example instead of the literal
|
|
109
|
+
`"example"`, which every `$ref`'d response produced until this fix.
|
|
110
|
+
- **Streams and binaries are exceptions, not gaps.** An operation whose body is
|
|
111
|
+
not JSON (the friend-removal progress stream, audio and dump downloads, SSE)
|
|
112
|
+
stays `exception` in the inventory with its reason, even when its query is
|
|
113
|
+
validated with a shared schema.
|
|
114
|
+
- **Refined request bodies export their object and their refinement.** Zod
|
|
115
|
+
will not `.extend()` an object that carries a `.refine`/`.superRefine`, and
|
|
116
|
+
the app must swap ids for its coercing versions. Export the plain
|
|
117
|
+
`*ObjectSchema`, the refinement (`requireLabelOrNotes`,
|
|
118
|
+
`refineRecordActionCreate`) and the refined wire schema; the app rebuilds
|
|
119
|
+
`object.extend({...coercion}).superRefine(refinement)`. Where the refinement
|
|
120
|
+
enforces "exactly one of", export a TypeScript union for callers
|
|
121
|
+
(`RecordActionCreate`) built from the inferred base type.
|
|
122
|
+
- **Documentation travels with the schema.** Put OpenAPI descriptions,
|
|
123
|
+
examples and `format: "date-time"` on the field with `.meta({...})`, which
|
|
124
|
+
`z.toJSONSchema` copies through, instead of keeping them in a hand-written
|
|
125
|
+
schema object in `routes.ts`. Use `format` rather than `z.iso.datetime()` on
|
|
126
|
+
responses, so the parser stays as lenient as it was.
|
|
127
|
+
- **Enum tuples come from the schema.** Export `SLEEVE_TYPES =
|
|
128
|
+
sleeveTypeSchema.options` and have app modules (`lib/recordCare.ts`)
|
|
129
|
+
re-export it, so UI, SQL helpers and analytics share one list. Database row
|
|
130
|
+
types that differ only in timestamp types derive from the wire type with
|
|
131
|
+
`Omit<> & {...}` rather than repeating every column.
|
|
132
|
+
- **The client's own Zod copy is replaced, not kept.** `groovenet-client`
|
|
133
|
+
exports Zod schemas from `schemas.ts` as well as types. When a family has
|
|
134
|
+
both, re-export the shared shapes there and rebuild only the coercing
|
|
135
|
+
bodies and queries with the same `.extend()` + exported refinement the app
|
|
136
|
+
uses (spins). A hand-copied enum can lag the database: the client's spin
|
|
137
|
+
schema had no `playlist` selection mode or provenance until it was replaced.
|
|
138
|
+
- **Wire queries omit defaults.** Shared query schemas describe what a client
|
|
139
|
+
may send (`limit?: number`); the app adds `.default(50)` after coercion and
|
|
140
|
+
the OpenAPI `parameters` list documents the default.
|