@legalize-dev/sdk 0.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/CHANGELOG.md ADDED
@@ -0,0 +1,49 @@
1
+ # Changelog
2
+
3
+ All notable changes to the `@legalize-dev/sdk` Node SDK will be
4
+ documented in this file. The format is based on [Keep a Changelog][kac]
5
+ and this project adheres to [Semantic Versioning][semver].
6
+
7
+ [kac]: https://keepachangelog.com/en/1.1.0/
8
+ [semver]: https://semver.org/spec/v2.0.0.html
9
+
10
+ ## [Unreleased]
11
+
12
+ ## [0.1.0] — 2026-04-20
13
+
14
+ ### Added
15
+
16
+ - First public release of the Node SDK for the Legalize API, published
17
+ as [`@legalize-dev/sdk`](https://www.npmjs.com/package/@legalize-dev/sdk)
18
+ on npm.
19
+ - `Legalize` client with configurable timeout, retry policy, default
20
+ headers, and `fetch` override for tests.
21
+ - Zero-config construction from `LEGALIZE_API_KEY`, `LEGALIZE_BASE_URL`,
22
+ `LEGALIZE_API_VERSION` — matches the cross-SDK `ENVIRONMENT.md`
23
+ contract byte-for-byte with the Python SDK.
24
+ - Full resource surface: `countries`, `jurisdictions`, `lawTypes`,
25
+ `laws`, `reforms`, `stats`, `webhooks` (create / list / retrieve /
26
+ update / delete / deliveries / retry / test).
27
+ - Auto-paginated async iterators `laws.iter`, `laws.searchIter`,
28
+ `reforms.iter`.
29
+ - Typed response models generated from `openapi-sdk.json`.
30
+ - Error hierarchy rooted at `LegalizeError`: `APIError`,
31
+ `AuthenticationError`, `ForbiddenError`, `NotFoundError`,
32
+ `InvalidRequestError`, `ValidationError`, `RateLimitError`,
33
+ `ServerError`, `ServiceUnavailableError`, `APIConnectionError`,
34
+ `APITimeoutError`, `WebhookVerificationError`.
35
+ - Retry policy with exponential backoff + full jitter. Honors
36
+ `Retry-After` in both delta-seconds and HTTP-date form. POST/PATCH
37
+ are not auto-retried unless `retryNonIdempotent: true`.
38
+ - `AbortSignal` support on every method.
39
+ - `Webhook.verify` and `Webhook.computeSignature` — constant-time HMAC
40
+ comparison, multi-signature header parsing, 300-second anti-replay
41
+ window.
42
+ - Dual ESM + CJS build via `tsup` with `.d.ts` for both variants.
43
+ - `await using` / `[Symbol.asyncDispose]` support.
44
+ - `lastResponse` populated on success AND error paths for inspecting
45
+ rate-limit headers and `X-Request-Id`.
46
+ - Examples for the four main use cases — list laws, search,
47
+ time-travel, Express webhook receiver, Fastify webhook receiver.
48
+ - Test suite with ≥ 95% line coverage and a contract test that asserts
49
+ the SDK exposes every `(method, path)` tuple in `openapi-sdk.json`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Legalize
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,246 @@
1
+ # @legalize-dev/sdk
2
+
3
+ [![npm](https://img.shields.io/npm/v/@legalize-dev/sdk.svg)](https://www.npmjs.com/package/@legalize-dev/sdk)
4
+ [![Node](https://img.shields.io/node/v/@legalize-dev/sdk.svg)](https://www.npmjs.com/package/@legalize-dev/sdk)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/legalize-dev/legalize-sdks/blob/main/LICENSE)
6
+
7
+ Official Node client for the [Legalize API](https://legalize.dev/api) — legal texts as structured, versioned data.
8
+
9
+ ```bash
10
+ npm install @legalize-dev/sdk
11
+ ```
12
+
13
+ ```ts
14
+ import { Legalize } from "@legalize-dev/sdk";
15
+
16
+ const client = new Legalize({ apiKey: "leg_..." });
17
+
18
+ for await (const law of client.laws.iter("es", { lawType: "ley_organica" })) {
19
+ console.log(law.id, law.title);
20
+ }
21
+ ```
22
+
23
+ ## Why this SDK
24
+
25
+ - **Typed end-to-end.** Types are generated from the canonical OpenAPI
26
+ spec and shipped in the package. `strict` TypeScript friendly.
27
+ - **Zero runtime dependencies.** Uses Node's built-in `fetch`,
28
+ `AbortController`, and `crypto`. No `undici`, `axios`, or `node-fetch`.
29
+ - **Retries with backoff built in.** Honors `Retry-After`, handles
30
+ 429/5xx, exponential delay with full jitter, and never auto-retries
31
+ POST/PATCH by default (no duplicate mutations).
32
+ - **Webhook verification is a one-liner.** Constant-time HMAC compare,
33
+ 5-minute anti-replay window, clock-skew tolerant.
34
+ - **Works in any Node 20+ environment.** Lambda, Cloud Functions, Fly,
35
+ Railway, plain `node`, `tsx`, `ts-node`, etc.
36
+ - **ESM and CJS dual build.** `"type": "module"` with a proper `exports`
37
+ map so `import` and `require` both resolve cleanly.
38
+
39
+ ## Quick tour
40
+
41
+ ### List, iterate, search
42
+
43
+ ```ts
44
+ // One page
45
+ const page = await client.laws.list("es", { page: 1, perPage: 50 });
46
+ console.log(page.total, page.results.length);
47
+
48
+ // Auto-paginated async iterator (fetches pages as needed)
49
+ for await (const law of client.laws.iter("es", { status: "vigente" })) {
50
+ // ...
51
+ }
52
+
53
+ // Full-text search
54
+ const results = await client.laws.search("es", "protección de datos");
55
+ ```
56
+
57
+ ### Time-travel
58
+
59
+ Every law has a git-tracked history. Retrieve it at any past revision:
60
+
61
+ ```ts
62
+ const commits = await client.laws.commits("es", "ley_organica_3_2018");
63
+ const oldest = commits.commits[commits.commits.length - 1]!.sha;
64
+ const past = await client.laws.atCommit("es", "ley_organica_3_2018", oldest);
65
+ console.log(past.content_md); // Markdown at that revision
66
+ ```
67
+
68
+ ### Abort + timeout
69
+
70
+ Every method accepts a standard `AbortSignal`:
71
+
72
+ ```ts
73
+ const ac = new AbortController();
74
+ setTimeout(() => ac.abort(), 5000);
75
+ const out = await client.laws.list("es", { signal: ac.signal });
76
+ ```
77
+
78
+ Per-request timeouts are configured on the client:
79
+
80
+ ```ts
81
+ const client = new Legalize({ apiKey: "leg_...", timeout: 10_000 });
82
+ ```
83
+
84
+ ### Webhooks
85
+
86
+ Verify a signed delivery in one call. **Pass the raw request bytes** —
87
+ re-serialized JSON will NOT verify:
88
+
89
+ ```ts
90
+ import express from "express";
91
+ import { Webhook, WebhookVerificationError } from "@legalize-dev/sdk";
92
+
93
+ app.post(
94
+ "/webhooks/legalize",
95
+ express.raw({ type: "application/json" }),
96
+ (req, res) => {
97
+ try {
98
+ const event = Webhook.verify({
99
+ payload: req.body as Buffer,
100
+ sigHeader: req.header("X-Legalize-Signature") ?? "",
101
+ timestamp: req.header("X-Legalize-Timestamp") ?? "",
102
+ secret: process.env.LEGALIZE_WHSEC!,
103
+ });
104
+ if (event.type === "law.updated") {
105
+ // ...
106
+ }
107
+ res.status(204).send();
108
+ } catch (err) {
109
+ if (err instanceof WebhookVerificationError) {
110
+ res.status(400).send();
111
+ return;
112
+ }
113
+ throw err;
114
+ }
115
+ },
116
+ );
117
+ ```
118
+
119
+ Working Express and Fastify receivers in
120
+ [`examples/`](https://github.com/legalize-dev/legalize-sdks/tree/main/node/examples).
121
+
122
+ ## Configuration
123
+
124
+ ### Zero-config (recommended for servers + Kubernetes)
125
+
126
+ Set the environment and just instantiate:
127
+
128
+ ```bash
129
+ export LEGALIZE_API_KEY=leg_live_...
130
+ # Optional:
131
+ export LEGALIZE_BASE_URL=https://legalize.dev
132
+ export LEGALIZE_API_VERSION=v1
133
+ ```
134
+
135
+ ```ts
136
+ import { Legalize } from "@legalize-dev/sdk";
137
+
138
+ const client = new Legalize(); // picks everything up from the environment
139
+ ```
140
+
141
+ ### Explicit
142
+
143
+ ```ts
144
+ import { Legalize, RetryPolicy } from "@legalize-dev/sdk";
145
+
146
+ const client = new Legalize({
147
+ apiKey: "leg_...",
148
+ baseUrl: "https://legalize.dev",
149
+ apiVersion: "v1", // negotiated via Legalize-API-Version header
150
+ timeout: 30_000, // milliseconds
151
+ retry: new RetryPolicy({
152
+ maxRetries: 5,
153
+ initialDelay: 0.5,
154
+ maxDelay: 10,
155
+ }),
156
+ defaultHeaders: { "X-Correlation-Id": "..." },
157
+ });
158
+ ```
159
+
160
+ Precedence: explicit argument > environment variable > built-in default.
161
+ The full cross-SDK contract is documented in
162
+ [`ENVIRONMENT.md`](https://github.com/legalize-dev/legalize-sdks/blob/main/ENVIRONMENT.md).
163
+
164
+ Read rate-limit headers from the last response:
165
+
166
+ ```ts
167
+ await client.countries.list();
168
+ const resp = client.lastResponse;
169
+ console.log(resp?.headers.get("X-RateLimit-Remaining"));
170
+ ```
171
+
172
+ The same `lastResponse` is populated when a call fails, so you can
173
+ inspect `X-Request-Id` and rate-limit headers after an error too:
174
+
175
+ ```ts
176
+ try {
177
+ await client.laws.retrieve("es", "unknown");
178
+ } catch (err) {
179
+ console.error(client.lastResponse?.headers.get("X-Request-Id"));
180
+ throw err;
181
+ }
182
+ ```
183
+
184
+ ### Cleanup
185
+
186
+ The client holds no persistent connection pool (Node's `fetch` manages
187
+ sockets globally), but `close()` is exported for API symmetry across
188
+ SDKs. TS 5.2+ `await using` is supported:
189
+
190
+ ```ts
191
+ {
192
+ await using client = new Legalize({ apiKey: "leg_..." });
193
+ await client.countries.list();
194
+ } // client auto-disposed
195
+ ```
196
+
197
+ ## Retries
198
+
199
+ Auto-retries on 429 + 5xx + transport errors, with exponential backoff
200
+ and full jitter. `Retry-After` (integer seconds or HTTP-date form) is
201
+ honored when present, capped at `maxDelay`.
202
+
203
+ **POST and PATCH are NOT retried by default** — they may be
204
+ non-idempotent. Opt in per-policy with `retryNonIdempotent: true`, or
205
+ send an `Idempotency-Key` and wrap your own retry loop.
206
+
207
+ ## Errors
208
+
209
+ All errors inherit from `LegalizeError`. Catch the specific one you
210
+ care about and let the rest bubble:
211
+
212
+ ```ts
213
+ import {
214
+ AuthenticationError, // 401 — bad/missing key
215
+ ForbiddenError, // 403
216
+ NotFoundError, // 404
217
+ InvalidRequestError, // 400
218
+ ValidationError, // 422
219
+ RateLimitError, // 429 — retried automatically by default
220
+ ServerError, // 5xx
221
+ ServiceUnavailableError, // 503
222
+ APIConnectionError, // network failure
223
+ APITimeoutError, // timeout
224
+ WebhookVerificationError,
225
+ } from "@legalize-dev/sdk";
226
+ ```
227
+
228
+ Every `APIError` exposes `.statusCode`, `.code`, `.body`, `.response`,
229
+ and `.requestId`.
230
+
231
+ ## Compatibility
232
+
233
+ - Node 20, 22, 24
234
+ - Linux, macOS, Windows
235
+ - ESM and CommonJS consumers (dual package)
236
+
237
+ ## Links
238
+
239
+ - [API reference](https://legalize.dev/api/docs)
240
+ - [Monorepo](https://github.com/legalize-dev/legalize-sdks) (all language SDKs)
241
+ - [Changelog](https://github.com/legalize-dev/legalize-sdks/blob/main/node/CHANGELOG.md)
242
+ - [Contributing](https://github.com/legalize-dev/legalize-sdks/blob/main/CONTRIBUTING.md)
243
+
244
+ ## License
245
+
246
+ MIT