@ciromaciel/contract 1.0.0 → 1.0.2
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 +81 -19
- package/package.json +7 -7
- package/src/index.js +1 -1
package/README.md
CHANGED
|
@@ -1,38 +1,100 @@
|
|
|
1
1
|
# @ciromaciel/contract
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
One response envelope, a closed catalogue of error codes, and helpers that make
|
|
4
|
+
answering outside it impossible.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
This package is the **executable** norm: what services import in order to
|
|
7
|
+
respond, and what tests import in order to check. The rules it enforces are
|
|
8
|
+
described below.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
10
11
|
|
|
11
12
|
```bash
|
|
12
13
|
bun add @ciromaciel/contract
|
|
13
14
|
```
|
|
14
15
|
|
|
16
|
+
`zod` is an **optional** peer, needed only by the `/schemas` entrypoint.
|
|
17
|
+
|
|
18
|
+
## Quick start
|
|
19
|
+
|
|
15
20
|
```js
|
|
16
|
-
import { ok, created,
|
|
21
|
+
import { ok, created, collection, failure } from '@ciromaciel/contract'
|
|
17
22
|
import { CODES } from '@ciromaciel/contract/codes'
|
|
18
23
|
|
|
19
|
-
return ok({ id,
|
|
20
|
-
return
|
|
24
|
+
return ok({ id, name })
|
|
25
|
+
return created({ id, name })
|
|
26
|
+
return failure(CODES.NOT_FOUND, 'project not found')
|
|
21
27
|
```
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
`failure()` derives the HTTP status from the code — there is no signature that
|
|
30
|
+
lets you return `NOT_FOUND` with a 200.
|
|
31
|
+
|
|
32
|
+
## API
|
|
33
|
+
|
|
34
|
+
### `@ciromaciel/contract`
|
|
35
|
+
|
|
36
|
+
| Export | What it does |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `ok(resource, { status, headers })` | One resource, at the root. No `data`, no `success`, no `message`. |
|
|
39
|
+
| `created(resource, { headers })` | A resource that has just been born — 201. |
|
|
40
|
+
| `collection(list, page, { status, headers })` | `{ items, page: { limit, offset, total, hasMore } }`. Without `total`, `total` and `hasMore` come back `null` — "nobody counted" is not the same as zero. |
|
|
41
|
+
| `page({ limit, offset, total, extra })` | Builds the `page` object for `collection`, requiring a numeric `total`. |
|
|
42
|
+
| `pagination(url, { defaultLimit, maxLimit })` | Reads `limit`/`offset` from a URL, with a ceiling. |
|
|
43
|
+
| `failure(code, message, { details, headers, status })` | An error `Response`. Status comes from the code. |
|
|
44
|
+
| `failureBody(code, message, details)` | `{ body, status }` for frameworks that assemble the response themselves. |
|
|
45
|
+
| `failureWithStatus(status, message, details, headers)` | For a global error handler that only has the HTTP number at hand. |
|
|
46
|
+
| `CODES` · `STATUS_BY_CODE` · `RETRIABLE` · `ALIASES` | The vocabulary and its tables. |
|
|
47
|
+
| `isCode` · `toCode` · `codeFromStatus` | Code guards and conversions. |
|
|
48
|
+
|
|
49
|
+
### `@ciromaciel/contract/codes`
|
|
50
|
+
|
|
51
|
+
The closed enum: `UNAUTHORIZED`, `FORBIDDEN`, `WRONG_AUDIENCE`,
|
|
52
|
+
`INSUFFICIENT_SCOPE`, `NOT_FOUND`, `CONFLICT`, `NAME_TAKEN`, `EXPIRED`,
|
|
53
|
+
`HAS_RESOURCES`, `VALIDATION_ERROR`, `UNPROCESSABLE`, `RATE_LIMITED`,
|
|
54
|
+
`PAYLOAD_TOO_LARGE`, `PLAN_LIMIT`, `SUBSCRIPTION_REQUIRED`, `INTERNAL_ERROR`,
|
|
55
|
+
`SERVICE_UNAVAILABLE`, `UPSTREAM_ERROR`.
|
|
56
|
+
|
|
57
|
+
`code` is the part the client reads; `message` is for the human and its wording
|
|
58
|
+
may change at any time. Renaming a code is a breaking change.
|
|
59
|
+
|
|
60
|
+
### `@ciromaciel/contract/schemas`
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
import { errorSchema, collectionSchema, resourceSchema, pageSchema, codeSchema } from '@ciromaciel/contract/schemas'
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Zod schemas for the same rules, from the reader's side. This path is separate on
|
|
67
|
+
purpose: `schemas.js` imports Zod, and a worker that only needs to respond
|
|
68
|
+
should not have to load a validator it never calls. While the index re-exported
|
|
69
|
+
the schemas, the import was eager — asking for `failure` pulled Zod in, and the
|
|
70
|
+
install broke with *"Cannot find package 'zod'"* for anyone who treated the peer
|
|
71
|
+
as optional.
|
|
72
|
+
|
|
73
|
+
### `@ciromaciel/contract/plan`
|
|
24
74
|
|
|
25
75
|
```js
|
|
26
|
-
import {
|
|
76
|
+
import { createPlanGate } from '@ciromaciel/contract/plan'
|
|
77
|
+
|
|
78
|
+
export const plan = createPlanGate({
|
|
79
|
+
product: 'auth',
|
|
80
|
+
resource: 'applications',
|
|
81
|
+
tiers: [
|
|
82
|
+
{ slug: 'auth-applications-1', limit: 1 },
|
|
83
|
+
{ slug: 'auth-applications-5', limit: 5 },
|
|
84
|
+
{ slug: 'auth-applications-unlimited', limit: Infinity },
|
|
85
|
+
],
|
|
86
|
+
})
|
|
27
87
|
```
|
|
28
88
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
89
|
+
Also exports `hasSlug`, `tierLimit`, `getPlanNotice`, `withPlanNotice`,
|
|
90
|
+
`gateTelemetry`, `recordGateDecision` and `resetGateTelemetry`. A product owns
|
|
91
|
+
the prefix, the slugs and the wording of the notice; the cache, the fail-open,
|
|
92
|
+
the product-prefix check and the choice between `PLAN_LIMIT` and
|
|
93
|
+
`SUBSCRIPTION_REQUIRED` are the same reasoning everywhere.
|
|
94
|
+
|
|
95
|
+
## Links
|
|
35
96
|
|
|
36
|
-
|
|
97
|
+
- [Documentation](https://myinfrastructure.click)
|
|
98
|
+
- [Package on npm](https://www.npmjs.com/package/@ciromaciel/contract)
|
|
37
99
|
|
|
38
|
-
|
|
100
|
+
MIT © [Ciro Cesar Maciel](https://ciromaciel.click)
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ciromaciel/contract",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "
|
|
6
|
+
"description": "A single response envelope, a closed catalogue of error codes, and helpers that make answering outside it impossible.",
|
|
7
7
|
"main": "src/index.js",
|
|
8
8
|
"files": [
|
|
9
9
|
"src",
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
"publishConfig": {
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
"url": "
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
"homepage": "https://myinfrastructure.click",
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://myinfrastructure.click"
|
|
38
|
+
},
|
|
39
|
+
"author": "Ciro Cesar Maciel <maciel.ciro@icloud.com> (https://ciromaciel.click)"
|
|
40
40
|
}
|
package/src/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// @ciromaciel/contract — the API contract of the seven products
|
|
3
3
|
// ============================================================================
|
|
4
4
|
//
|
|
5
|
-
// The written norm lives in https://github.com/
|
|
5
|
+
// The written norm lives in https://github.com/ciro-maciel/infrastructure/blob/prod/docs/2026-09-09-api-contract.md. This
|
|
6
6
|
// package is the EXECUTABLE norm: what the products import in order to respond,
|
|
7
7
|
// and what the tests import in order to check.
|
|
8
8
|
//
|