@softure-ai/core 0.2.1 → 0.2.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/CHANGELOG.md CHANGED
@@ -4,6 +4,20 @@ Newest first. Each version lists what changed for an app that uses `@softure-ai/
4
4
  production, the version gets a line `verified in: <app>@<commit>` ([docs/05](../../docs/05-adoption-playbook.md),
5
5
  "Definition of done"). Versions before the first one below are described in their GitHub Releases (`core@x.y.z`).
6
6
 
7
+ ## 0.2.2
8
+
9
+ - `requestJson` writes (#635): `method` (`"GET"` by default, `"POST" | "PUT" | "PATCH" | "DELETE"`), `body` sent as
10
+ JSON with `Content-Type: application/json` unless the caller sets its own, and `idempotencyKey` sent as
11
+ `Idempotency-Key`. A write answered with an empty body (204, or 200 with nothing) returns `ok(undefined)`, so a
12
+ write with a schema returns `Ok<T | undefined>`; a non-empty answer still goes through the schema. A GET takes no
13
+ body, and its empty answer stays `core.http_unexpected_answer`. New types `HttpJsonMethod`, `HttpJsonWriteMethod`,
14
+ `RequestJsonReadOptions`, `RequestJsonWriteOptions`.
15
+ - New code `core.http_conflict` for 409 and 412 (a stale version or sha), for every method, with English copy in
16
+ `coreMessages` (`errors.http_conflict`).
17
+ - Migrate: a caller that switched on `core.http_unexpected_answer` to spot a 409 or 412 switches on
18
+ `core.http_conflict` instead; an exhaustive `switch` over `HttpJsonFailureCode` gets the new case. An app with
19
+ its own write helper replaces it with `requestJson({ method, body })` and handles `ok(undefined)` for 204s.
20
+
7
21
  ## 0.2.1
8
22
 
9
23
  - New (#599): calendar periods and local wall-clock date-times next to calendar days.
package/README.md CHANGED
@@ -305,8 +305,8 @@ if (plan.endedAt !== null) throw new PublicError("This plan has ended; pick anot
305
305
  `isPublicError` recognises one from another copy of core too (a registry symbol, not `instanceof`).
306
306
  `safeError` itself still answers `core.unexpected` for it, since callers switch on its two codes.
307
307
 
308
- **External JSON APIs.** `requestJson({ url, fetch?, headers?, timeoutMs?, schema? })` GETs a third-party JSON API
309
- and returns `Ok<value>` or an `HttpJsonFailure` (`{ ok: false, error, status }`). It sets the details a hand-written
308
+ **External JSON APIs.** `requestJson({ url, method?, body?, idempotencyKey?, fetch?, headers?, timeoutMs?, schema? })`
309
+ calls a third-party JSON API (a GET by default) and returns `Ok<value>` or an `HttpJsonFailure` (`{ ok: false, error, status }`). It sets the details a hand-written
310
310
  helper tends to miss:
311
311
 
312
312
  - a timeout, `AbortSignal.timeout(timeoutMs)` (default 10 000 ms), body included;
@@ -319,6 +319,7 @@ helper tends to miss:
319
319
  |---|---|
320
320
  | `core.http_unreachable` | network error, timeout, a body that breaks off |
321
321
  | `core.http_refused` | 3xx, an opaque redirect, 401, 403 |
322
+ | `core.http_conflict` | 409, 412 (a stale version or sha: re-read, then retry) |
322
323
  | `core.http_rate_limited` | 429 |
323
324
  | `core.http_unavailable` | 5xx |
324
325
  | `core.http_unexpected_answer` | any other non-2xx status, a body that is not JSON or fails the schema |
@@ -344,6 +345,25 @@ if (!result.ok) {
344
345
  }
345
346
  ```
346
347
 
348
+ Writes take `method: "POST" | "PUT" | "PATCH" | "DELETE"` and a `body`, sent as JSON with
349
+ `Content-Type: application/json` unless `headers` sets its own; a value JSON cannot represent (a cycle, a BigInt)
350
+ throws before anything is sent. `idempotencyKey` is sent as `Idempotency-Key`. A write answered with an empty body
351
+ (a 204, or a 200 with nothing) is `ok(undefined)`, schema or not, so a write's value is `T | undefined`; a non-empty
352
+ answer still goes through the schema. A GET carries no body, and an empty answer to it stays an unexpected answer.
353
+
354
+ ```ts
355
+ const saved = await requestJson({
356
+ url: `https://api.example.com/tasks/${task.id}`,
357
+ method: "PUT",
358
+ body: { title: task.title, version: task.version },
359
+ headers: { Authorization: `Bearer ${env.TASKS_API_KEY}` },
360
+ schema: z.object({ version: z.number() }),
361
+ });
362
+ if (!saved.ok && saved.error === "core.http_conflict") {
363
+ // someone else changed the task: read it again and retry the write
364
+ }
365
+ ```
366
+
347
367
  **Time.** Server code receives a `Clock` (`ModuleContext.clock`) instead of calling `new Date()`.
348
368
  `systemClock` is the real one; `createTestClock(start)` has `advance(ms)` and `set(date)`.
349
369
  A whole test run can also move to another day without code changes: `TEST_TODAY=YYYY-MM-DD` with the
@@ -1,10 +1,12 @@
1
1
  import type { z } from "zod";
2
2
  import { type Err, type Ok } from "./result.js";
3
- export type HttpJsonFailureCode = "core.http_unreachable" | "core.http_refused" | "core.http_rate_limited" | "core.http_unavailable" | "core.http_unexpected_answer";
3
+ export type HttpJsonFailureCode = "core.http_unreachable" | "core.http_refused" | "core.http_rate_limited" | "core.http_unavailable" | "core.http_conflict" | "core.http_unexpected_answer";
4
4
  /** A failed `requestJson`: the code and the HTTP status (`null` when no response arrived). Nothing else. */
5
5
  export type HttpJsonFailure = Err<HttpJsonFailureCode> & {
6
6
  readonly status: number | null;
7
7
  };
8
+ export type HttpJsonWriteMethod = "POST" | "PUT" | "PATCH" | "DELETE";
9
+ export type HttpJsonMethod = "GET" | HttpJsonWriteMethod;
8
10
  export type FetchFunction = (input: string | URL, init: RequestInit) => Promise<Response>;
9
11
  export interface RequestJsonOptions {
10
12
  readonly url: string | URL;
@@ -17,13 +19,29 @@ export interface RequestJsonOptions {
17
19
  export interface RequestJsonSchemaOptions<S extends z.ZodType> extends RequestJsonOptions {
18
20
  readonly schema: S;
19
21
  }
22
+ /** A GET: no body, no idempotency key (a GET is idempotent already). */
23
+ export interface RequestJsonReadOptions extends RequestJsonOptions {
24
+ readonly method?: "GET";
25
+ readonly body?: never;
26
+ readonly idempotencyKey?: never;
27
+ }
28
+ /** A write: the body is sent as JSON, and an answer with no body is `ok(undefined)`. */
29
+ export interface RequestJsonWriteOptions extends RequestJsonOptions {
30
+ readonly method: HttpJsonWriteMethod;
31
+ /** Serialised with `JSON.stringify`; `undefined` sends no body. A value JSON cannot represent throws. */
32
+ readonly body?: unknown;
33
+ /** Sent as `Idempotency-Key`, for providers that deduplicate retried writes by it. */
34
+ readonly idempotencyKey?: string;
35
+ }
20
36
  /**
21
- * GET `url` and read its JSON body. Without a schema the value is `unknown`, for the caller to narrow; with one
22
- * it is the schema's output. 3xx, 401 and 403 are refused, 429 rate limited, 5xx unavailable, and any other
23
- * non-2xx status or a body that is not JSON or fails the schema an unexpected answer.
37
+ * Call `url` (a GET unless `method` says otherwise) and read its JSON body. Without a schema the value is `unknown`,
38
+ * for the caller to narrow; with one it is the schema's output. A write answered with an empty body (a 204) is
39
+ * `ok(undefined)`, schema or not. 3xx, 401 and 403 are refused, 409 and 412 a conflict, 429 rate limited, 5xx
40
+ * unavailable, and any other non-2xx status or a body that is not JSON or fails the schema an unexpected answer.
24
41
  */
25
- export declare function requestJson<S extends z.ZodType>(options: RequestJsonSchemaOptions<S>): Promise<Ok<z.output<S>> | HttpJsonFailure>;
26
- export declare function requestJson(options: RequestJsonOptions): Promise<Ok<unknown> | HttpJsonFailure>;
42
+ export declare function requestJson<S extends z.ZodType>(options: RequestJsonSchemaOptions<S> & RequestJsonReadOptions): Promise<Ok<z.output<S>> | HttpJsonFailure>;
43
+ export declare function requestJson<S extends z.ZodType>(options: RequestJsonSchemaOptions<S> & RequestJsonWriteOptions): Promise<Ok<z.output<S> | undefined> | HttpJsonFailure>;
44
+ export declare function requestJson(options: RequestJsonReadOptions | RequestJsonWriteOptions): Promise<Ok<unknown> | HttpJsonFailure>;
27
45
  /**
28
46
  * The query part of a URL (without `?`) from a flat record, in insertion order. `null` and `undefined` are left
29
47
  * out, so optional parameters can be passed as they are.
@@ -1 +1 @@
1
- {"version":3,"file":"http-json.d.ts","sourceRoot":"","sources":["../src/http-json.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EAAW,KAAK,GAAG,EAAE,KAAK,EAAE,EAAE,MAAM,aAAa,CAAC;AAEzD,MAAM,MAAM,mBAAmB,GAC3B,uBAAuB,GACvB,mBAAmB,GACnB,wBAAwB,GACxB,uBAAuB,GACvB,6BAA6B,CAAC;AAElC,4GAA4G;AAC5G,MAAM,MAAM,eAAe,GAAG,GAAG,CAAC,mBAAmB,CAAC,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAE5F,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAE1F,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;IAC3B,yDAAyD;IACzD,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;IAC/B,oFAAoF;IACpF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,wBAAwB,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,CAAE,SAAQ,kBAAkB;IACvF,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;CACpB;AAID;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAC7C,OAAO,EAAE,wBAAwB,CAAC,CAAC,CAAC,GACnC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,CAAC;AAC9C,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,eAAe,CAAC,CAAC;AAsDjG;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC,GAAG,MAAM,CAQpH"}
1
+ {"version":3,"file":"http-json.d.ts","sourceRoot":"","sources":["../src/http-json.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EAAW,KAAK,GAAG,EAAE,KAAK,EAAE,EAAE,MAAM,aAAa,CAAC;AAEzD,MAAM,MAAM,mBAAmB,GAC3B,uBAAuB,GACvB,mBAAmB,GACnB,wBAAwB,GACxB,uBAAuB,GACvB,oBAAoB,GACpB,6BAA6B,CAAC;AAElC,4GAA4G;AAC5G,MAAM,MAAM,eAAe,GAAG,GAAG,CAAC,mBAAmB,CAAC,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAE5F,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;AACtE,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,mBAAmB,CAAC;AAEzD,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAE1F,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;IAC3B,yDAAyD;IACzD,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;IAC/B,oFAAoF;IACpF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,wBAAwB,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,CAAE,SAAQ,kBAAkB;IACvF,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;CACpB;AAED,wEAAwE;AACxE,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IAChE,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,cAAc,CAAC,EAAE,KAAK,CAAC;CACjC;AAED,wFAAwF;AACxF,MAAM,WAAW,uBAAwB,SAAQ,kBAAkB;IACjE,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,yGAAyG;IACzG,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,sFAAsF;IACtF,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAID;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAC7C,OAAO,EAAE,wBAAwB,CAAC,CAAC,CAAC,GAAG,sBAAsB,GAC5D,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,CAAC;AAC9C,wBAAgB,WAAW,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAC7C,OAAO,EAAE,wBAAwB,CAAC,CAAC,CAAC,GAAG,uBAAuB,GAC7D,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,GAAG,eAAe,CAAC,CAAC;AAC1D,wBAAgB,WAAW,CACzB,OAAO,EAAE,sBAAsB,GAAG,uBAAuB,GACxD,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,eAAe,CAAC,CAAC;AAsE1C;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC,GAAG,MAAM,CAQpH"}
package/dist/http-json.js CHANGED
@@ -2,6 +2,10 @@ import { err, ok } from "./result.js";
2
2
  const DEFAULT_TIMEOUT_MS = 10_000;
3
3
  export async function requestJson(options) {
4
4
  const fetchFunction = options.fetch ?? globalThis.fetch;
5
+ const method = options.method ?? "GET";
6
+ const isWrite = method !== "GET";
7
+ // Outside the try on purpose: a body JSON cannot represent is the caller's bug, not an unreachable service.
8
+ const body = options.body === undefined ? undefined : JSON.stringify(options.body);
5
9
  let response;
6
10
  try {
7
11
  // Inside the try: an invalid header value throws a message that quotes it.
@@ -9,9 +13,16 @@ export async function requestJson(options) {
9
13
  if (!headers.has("accept")) {
10
14
  headers.set("accept", "application/json");
11
15
  }
16
+ if (body !== undefined && !headers.has("content-type")) {
17
+ headers.set("content-type", "application/json");
18
+ }
19
+ if (options.idempotencyKey !== undefined) {
20
+ headers.set("idempotency-key", options.idempotencyKey);
21
+ }
12
22
  response = await fetchFunction(options.url, {
13
- method: "GET",
23
+ method,
14
24
  headers,
25
+ body,
15
26
  redirect: "manual",
16
27
  signal: AbortSignal.timeout(options.timeoutMs ?? DEFAULT_TIMEOUT_MS),
17
28
  });
@@ -34,18 +45,22 @@ export async function requestJson(options) {
34
45
  // The body broke off, usually the timeout firing mid-body.
35
46
  return httpFailure("core.http_unreachable", response.status);
36
47
  }
37
- let body;
48
+ if (isWrite && text.trim() === "") {
49
+ // A 204, or a write that answers 200 with nothing: the write succeeded and there is nothing to read.
50
+ return ok(undefined);
51
+ }
52
+ let answer;
38
53
  try {
39
- body = JSON.parse(text);
54
+ answer = JSON.parse(text);
40
55
  }
41
56
  catch {
42
57
  return httpFailure("core.http_unexpected_answer", response.status);
43
58
  }
44
59
  if (options.schema === undefined) {
45
- return ok(body);
60
+ return ok(answer);
46
61
  }
47
62
  // The schema's issues can quote the body's values, so only the code leaves.
48
- const parsed = options.schema.safeParse(body);
63
+ const parsed = options.schema.safeParse(answer);
49
64
  return parsed.success ? ok(parsed.data) : httpFailure("core.http_unexpected_answer", response.status);
50
65
  }
51
66
  /**
@@ -66,6 +81,9 @@ function getStatusFailure(response) {
66
81
  if (response.type === "opaqueredirect" || (status >= 300 && status < 400) || status === 401 || status === 403) {
67
82
  return httpFailure("core.http_refused", status);
68
83
  }
84
+ if (status === 409 || status === 412) {
85
+ return httpFailure("core.http_conflict", status);
86
+ }
69
87
  if (status === 429) {
70
88
  return httpFailure("core.http_rate_limited", status);
71
89
  }
@@ -1 +1 @@
1
- {"version":3,"file":"http-json.js","sourceRoot":"","sources":["../src/http-json.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"http-json.js","sourceRoot":"","sources":["../src/http-json.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAqB,MAAM,aAAa,CAAC;AA+CzD,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAiBlC,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,OAA6F;IAE7F,MAAM,aAAa,GAAG,OAAO,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC;IACxD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,KAAK,CAAC;IACvC,MAAM,OAAO,GAAG,MAAM,KAAK,KAAK,CAAC;IACjC,4GAA4G;IAC5G,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnF,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,2EAA2E;QAC3E,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC7C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC3B,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC;QAC5C,CAAC;QACD,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC;YACvD,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;QAClD,CAAC;QACD,IAAI,OAAO,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;YACzC,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;QACzD,CAAC;QACD,QAAQ,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,GAAG,EAAE;YAC1C,MAAM;YACN,OAAO;YACP,IAAI;YACJ,QAAQ,EAAE,QAAQ;YAClB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,IAAI,kBAAkB,CAAC;SACrE,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,6FAA6F;QAC7F,OAAO,WAAW,CAAC,uBAAuB,EAAE,IAAI,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,aAAa,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACjD,IAAI,aAAa,KAAK,IAAI,EAAE,CAAC;QAC3B,0FAA0F;QAC1F,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACrD,OAAO,aAAa,CAAC;IACvB,CAAC;IAED,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,2DAA2D;QAC3D,OAAO,WAAW,CAAC,uBAAuB,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC/D,CAAC;IAED,IAAI,OAAO,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAClC,qGAAqG;QACrG,OAAO,EAAE,CAAC,SAAS,CAAC,CAAC;IACvB,CAAC;IAED,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,WAAW,CAAC,6BAA6B,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACrE,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QACjC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;IACpB,CAAC;IACD,4EAA4E;IAC5E,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAChD,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,6BAA6B,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;AACxG,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,MAA8E;IAC1G,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;IACrC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YAC1C,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAC;AAC3B,CAAC;AAED,SAAS,gBAAgB,CAAC,QAAkB;IAC1C,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC;IAC5B,IAAI,QAAQ,CAAC,IAAI,KAAK,gBAAgB,IAAI,CAAC,MAAM,IAAI,GAAG,IAAI,MAAM,GAAG,GAAG,CAAC,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QAC9G,OAAO,WAAW,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;IAClD,CAAC;IACD,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACrC,OAAO,WAAW,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC;IACnD,CAAC;IACD,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACnB,OAAO,WAAW,CAAC,wBAAwB,EAAE,MAAM,CAAC,CAAC;IACvD,CAAC;IACD,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;QAClB,OAAO,WAAW,CAAC,uBAAuB,EAAE,MAAM,CAAC,CAAC;IACtD,CAAC;IACD,IAAI,MAAM,GAAG,GAAG,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;QAClC,OAAO,WAAW,CAAC,6BAA6B,EAAE,MAAM,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,WAAW,CAAC,IAAyB,EAAE,MAAqB;IACnE,OAAO,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;AAClC,CAAC"}
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ export { calendarPeriodSchema, countPeriodDays, doPeriodsOverlap, isDayInPeriod,
3
3
  export { getLocalDay, isLocalDateTime, localDateTimeSchema, type LocalDateTime } from "./local-date-time.js";
4
4
  export { CURRENCY_MINOR_UNIT_DIGITS } from "./currency-digits.js";
5
5
  export { DEFAULT_DURATION_MESSAGES, formatCalendarDay, formatCalendarMonth, formatDuration, formatMoney, formatPercent, isCalendarMonth, type CalendarDayStyle, type CalendarMonthStyle, type DurationMessages, type FormatDurationOptions, type FormatMoneyOptions, type FormatPercentOptions, } from "./format.js";
6
- export { requestJson, toQueryString, type FetchFunction, type HttpJsonFailure, type HttpJsonFailureCode, type RequestJsonOptions, type RequestJsonSchemaOptions, } from "./http-json.js";
6
+ export { requestJson, toQueryString, type FetchFunction, type HttpJsonFailure, type HttpJsonFailureCode, type HttpJsonMethod, type HttpJsonWriteMethod, type RequestJsonOptions, type RequestJsonReadOptions, type RequestJsonSchemaOptions, type RequestJsonWriteOptions, } from "./http-json.js";
7
7
  export { memoizeByContent, type MemoizeByContentOptions, type MemoizedFunction } from "./memoize.js";
8
8
  export { createTestClock, systemClock, type Clock, type TestClock } from "./clock.js";
9
9
  export { formatMessage, getMessage, DEFAULT_LOCALE, isLocale, mergeMessages, pickLocalizedText, selectMessages, selectPlural, type DeepPartial, type Dictionaries, type Locale, type LocalizedText, type MessageOverrides, type MessageTree, type PluralForms, } from "./i18n.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,mGAAmG;AACnG,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,aAAa,EACb,kBAAkB,GAEnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,gBAAgB,EAChB,aAAa,GAEd,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,mBAAmB,EAAsB,MAAM,sBAAsB,CAAC;AAC7G,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EACL,yBAAyB,EACzB,iBAAiB,EACjB,mBAAmB,EACnB,cAAc,EACd,WAAW,EACX,aAAa,EACb,eAAe,GAOhB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,WAAW,EACX,aAAa,GAMd,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAuD,MAAM,cAAc,CAAC;AACrG,OAAO,EAAE,eAAe,EAAE,WAAW,EAA8B,MAAM,YAAY,CAAC;AACtF,OAAO,EACL,aAAa,EACb,UAAU,EACV,cAAc,EACd,QAAQ,EACR,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,YAAY,GAQb,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,YAAY,EAAqB,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAkD,MAAM,aAAa,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAsB,MAAM,iBAAiB,CAAC;AAC7H,OAAO,EACL,mBAAmB,EACnB,SAAS,EACT,yBAAyB,GAQ1B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAuB,MAAM,eAAe,CAAC;AAC1E,OAAO,EACL,YAAY,EACZ,oBAAoB,EACpB,YAAY,GAUb,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,iBAAiB,EACjB,WAAW,EACX,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,GAIjB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAuC,MAAM,gBAAgB,CAAC;AACvG,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAyC,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,cAAc,EAAmC,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,mGAAmG;AACnG,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,aAAa,EACb,kBAAkB,GAEnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,gBAAgB,EAChB,aAAa,GAEd,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,mBAAmB,EAAsB,MAAM,sBAAsB,CAAC;AAC7G,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EACL,yBAAyB,EACzB,iBAAiB,EACjB,mBAAmB,EACnB,cAAc,EACd,WAAW,EACX,aAAa,EACb,eAAe,GAOhB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,WAAW,EACX,aAAa,GAUd,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAuD,MAAM,cAAc,CAAC;AACrG,OAAO,EAAE,eAAe,EAAE,WAAW,EAA8B,MAAM,YAAY,CAAC;AACtF,OAAO,EACL,aAAa,EACb,UAAU,EACV,cAAc,EACd,QAAQ,EACR,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,YAAY,GAQb,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,YAAY,EAAqB,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAkD,MAAM,aAAa,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAsB,MAAM,iBAAiB,CAAC;AAC7H,OAAO,EACL,mBAAmB,EACnB,SAAS,EACT,yBAAyB,GAQ1B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAuB,MAAM,eAAe,CAAC;AAC1E,OAAO,EACL,YAAY,EACZ,oBAAoB,EACpB,YAAY,GAUb,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,iBAAiB,EACjB,WAAW,EACX,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,GAIjB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAuC,MAAM,gBAAgB,CAAC;AACvG,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAyC,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,cAAc,EAAmC,MAAM,oBAAoB,CAAC"}
@@ -6,6 +6,7 @@ export declare const en: {
6
6
  http_refused: string;
7
7
  http_rate_limited: string;
8
8
  http_unavailable: string;
9
+ http_conflict: string;
9
10
  http_unexpected_answer: string;
10
11
  };
11
12
  };
@@ -1 +1 @@
1
- {"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../src/messages/en.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,EAAE;;;;;;;;;;CAUd,CAAC"}
1
+ {"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../src/messages/en.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,EAAE;;;;;;;;;;;CAWd,CAAC"}
@@ -6,6 +6,7 @@ export const en = {
6
6
  http_refused: "The service refused the request.",
7
7
  http_rate_limited: "The service is receiving too many requests. Please try again later.",
8
8
  http_unavailable: "The service is unavailable right now. Please try again later.",
9
+ http_conflict: "The data changed in the meantime. Please reload it and try again.",
9
10
  http_unexpected_answer: "The service sent an answer that could not be read.",
10
11
  },
11
12
  };
@@ -1 +1 @@
1
- {"version":3,"file":"en.js","sourceRoot":"","sources":["../../src/messages/en.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,EAAE,GAAG;IAChB,MAAM,EAAE;QACN,eAAe,EAAE,sEAAsE;QACvF,UAAU,EAAE,yCAAyC;QACrD,gBAAgB,EAAE,2DAA2D;QAC7E,YAAY,EAAE,kCAAkC;QAChD,iBAAiB,EAAE,qEAAqE;QACxF,gBAAgB,EAAE,+DAA+D;QACjF,sBAAsB,EAAE,oDAAoD;KAC7E;CACF,CAAC"}
1
+ {"version":3,"file":"en.js","sourceRoot":"","sources":["../../src/messages/en.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,EAAE,GAAG;IAChB,MAAM,EAAE;QACN,eAAe,EAAE,sEAAsE;QACvF,UAAU,EAAE,yCAAyC;QACrD,gBAAgB,EAAE,2DAA2D;QAC7E,YAAY,EAAE,kCAAkC;QAChD,iBAAiB,EAAE,qEAAqE;QACxF,gBAAgB,EAAE,+DAA+D;QACjF,aAAa,EAAE,mEAAmE;QAClF,sBAAsB,EAAE,oDAAoD;KAC7E;CACF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softure-ai/core",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "The SOFTURE AI module contract: app config, module definitions, Result, Clock and messages.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/http-json.ts CHANGED
@@ -1,7 +1,7 @@
1
- // A JSON GET to a third-party API, with the details every app's own copy has to get right: a timeout, no redirect
2
- // followed (a key in a header or the URL never reaches another host), and every failure returned as a code. A caught
3
- // error's message is never read, since it can quote the URL and some APIs put the key there. The body stays
4
- // `unknown` unless a zod schema narrows it.
1
+ // A JSON call to a third-party API (a GET, or a write with a JSON body), with the details every app's own copy has to
2
+ // get right: a timeout, no redirect followed (a key in a header or the URL never reaches another host), and every
3
+ // failure returned as a code. A caught error's message is never read, since it can quote the URL and some APIs put
4
+ // the key there. The body stays `unknown` unless a zod schema narrows it.
5
5
  import type { z } from "zod";
6
6
  import { err, ok, type Err, type Ok } from "./result.js";
7
7
 
@@ -10,11 +10,15 @@ export type HttpJsonFailureCode =
10
10
  | "core.http_refused"
11
11
  | "core.http_rate_limited"
12
12
  | "core.http_unavailable"
13
+ | "core.http_conflict"
13
14
  | "core.http_unexpected_answer";
14
15
 
15
16
  /** A failed `requestJson`: the code and the HTTP status (`null` when no response arrived). Nothing else. */
16
17
  export type HttpJsonFailure = Err<HttpJsonFailureCode> & { readonly status: number | null };
17
18
 
19
+ export type HttpJsonWriteMethod = "POST" | "PUT" | "PATCH" | "DELETE";
20
+ export type HttpJsonMethod = "GET" | HttpJsonWriteMethod;
21
+
18
22
  export type FetchFunction = (input: string | URL, init: RequestInit) => Promise<Response>;
19
23
 
20
24
  export interface RequestJsonOptions {
@@ -30,21 +34,47 @@ export interface RequestJsonSchemaOptions<S extends z.ZodType> extends RequestJs
30
34
  readonly schema: S;
31
35
  }
32
36
 
37
+ /** A GET: no body, no idempotency key (a GET is idempotent already). */
38
+ export interface RequestJsonReadOptions extends RequestJsonOptions {
39
+ readonly method?: "GET";
40
+ readonly body?: never;
41
+ readonly idempotencyKey?: never;
42
+ }
43
+
44
+ /** A write: the body is sent as JSON, and an answer with no body is `ok(undefined)`. */
45
+ export interface RequestJsonWriteOptions extends RequestJsonOptions {
46
+ readonly method: HttpJsonWriteMethod;
47
+ /** Serialised with `JSON.stringify`; `undefined` sends no body. A value JSON cannot represent throws. */
48
+ readonly body?: unknown;
49
+ /** Sent as `Idempotency-Key`, for providers that deduplicate retried writes by it. */
50
+ readonly idempotencyKey?: string;
51
+ }
52
+
33
53
  const DEFAULT_TIMEOUT_MS = 10_000;
34
54
 
35
55
  /**
36
- * GET `url` and read its JSON body. Without a schema the value is `unknown`, for the caller to narrow; with one
37
- * it is the schema's output. 3xx, 401 and 403 are refused, 429 rate limited, 5xx unavailable, and any other
38
- * non-2xx status or a body that is not JSON or fails the schema an unexpected answer.
56
+ * Call `url` (a GET unless `method` says otherwise) and read its JSON body. Without a schema the value is `unknown`,
57
+ * for the caller to narrow; with one it is the schema's output. A write answered with an empty body (a 204) is
58
+ * `ok(undefined)`, schema or not. 3xx, 401 and 403 are refused, 409 and 412 a conflict, 429 rate limited, 5xx
59
+ * unavailable, and any other non-2xx status or a body that is not JSON or fails the schema an unexpected answer.
39
60
  */
40
61
  export function requestJson<S extends z.ZodType>(
41
- options: RequestJsonSchemaOptions<S>,
62
+ options: RequestJsonSchemaOptions<S> & RequestJsonReadOptions,
42
63
  ): Promise<Ok<z.output<S>> | HttpJsonFailure>;
43
- export function requestJson(options: RequestJsonOptions): Promise<Ok<unknown> | HttpJsonFailure>;
64
+ export function requestJson<S extends z.ZodType>(
65
+ options: RequestJsonSchemaOptions<S> & RequestJsonWriteOptions,
66
+ ): Promise<Ok<z.output<S> | undefined> | HttpJsonFailure>;
67
+ export function requestJson(
68
+ options: RequestJsonReadOptions | RequestJsonWriteOptions,
69
+ ): Promise<Ok<unknown> | HttpJsonFailure>;
44
70
  export async function requestJson(
45
- options: RequestJsonOptions & { readonly schema?: z.ZodType },
71
+ options: (RequestJsonReadOptions | RequestJsonWriteOptions) & { readonly schema?: z.ZodType },
46
72
  ): Promise<Ok<unknown> | HttpJsonFailure> {
47
73
  const fetchFunction = options.fetch ?? globalThis.fetch;
74
+ const method = options.method ?? "GET";
75
+ const isWrite = method !== "GET";
76
+ // Outside the try on purpose: a body JSON cannot represent is the caller's bug, not an unreachable service.
77
+ const body = options.body === undefined ? undefined : JSON.stringify(options.body);
48
78
 
49
79
  let response: Response;
50
80
  try {
@@ -53,9 +83,16 @@ export async function requestJson(
53
83
  if (!headers.has("accept")) {
54
84
  headers.set("accept", "application/json");
55
85
  }
86
+ if (body !== undefined && !headers.has("content-type")) {
87
+ headers.set("content-type", "application/json");
88
+ }
89
+ if (options.idempotencyKey !== undefined) {
90
+ headers.set("idempotency-key", options.idempotencyKey);
91
+ }
56
92
  response = await fetchFunction(options.url, {
57
- method: "GET",
93
+ method,
58
94
  headers,
95
+ body,
59
96
  redirect: "manual",
60
97
  signal: AbortSignal.timeout(options.timeoutMs ?? DEFAULT_TIMEOUT_MS),
61
98
  });
@@ -79,18 +116,23 @@ export async function requestJson(
79
116
  return httpFailure("core.http_unreachable", response.status);
80
117
  }
81
118
 
82
- let body: unknown;
119
+ if (isWrite && text.trim() === "") {
120
+ // A 204, or a write that answers 200 with nothing: the write succeeded and there is nothing to read.
121
+ return ok(undefined);
122
+ }
123
+
124
+ let answer: unknown;
83
125
  try {
84
- body = JSON.parse(text);
126
+ answer = JSON.parse(text);
85
127
  } catch {
86
128
  return httpFailure("core.http_unexpected_answer", response.status);
87
129
  }
88
130
 
89
131
  if (options.schema === undefined) {
90
- return ok(body);
132
+ return ok(answer);
91
133
  }
92
134
  // The schema's issues can quote the body's values, so only the code leaves.
93
- const parsed = options.schema.safeParse(body);
135
+ const parsed = options.schema.safeParse(answer);
94
136
  return parsed.success ? ok(parsed.data) : httpFailure("core.http_unexpected_answer", response.status);
95
137
  }
96
138
 
@@ -113,6 +155,9 @@ function getStatusFailure(response: Response): HttpJsonFailure | null {
113
155
  if (response.type === "opaqueredirect" || (status >= 300 && status < 400) || status === 401 || status === 403) {
114
156
  return httpFailure("core.http_refused", status);
115
157
  }
158
+ if (status === 409 || status === 412) {
159
+ return httpFailure("core.http_conflict", status);
160
+ }
116
161
  if (status === 429) {
117
162
  return httpFailure("core.http_rate_limited", status);
118
163
  }
package/src/index.ts CHANGED
@@ -41,8 +41,12 @@ export {
41
41
  type FetchFunction,
42
42
  type HttpJsonFailure,
43
43
  type HttpJsonFailureCode,
44
+ type HttpJsonMethod,
45
+ type HttpJsonWriteMethod,
44
46
  type RequestJsonOptions,
47
+ type RequestJsonReadOptions,
45
48
  type RequestJsonSchemaOptions,
49
+ type RequestJsonWriteOptions,
46
50
  } from "./http-json.js";
47
51
  export { memoizeByContent, type MemoizeByContentOptions, type MemoizedFunction } from "./memoize.js";
48
52
  export { createTestClock, systemClock, type Clock, type TestClock } from "./clock.js";
@@ -6,6 +6,7 @@ export const en = {
6
6
  http_refused: "The service refused the request.",
7
7
  http_rate_limited: "The service is receiving too many requests. Please try again later.",
8
8
  http_unavailable: "The service is unavailable right now. Please try again later.",
9
+ http_conflict: "The data changed in the meantime. Please reload it and try again.",
9
10
  http_unexpected_answer: "The service sent an answer that could not be read.",
10
11
  },
11
12
  };