@distilled.cloud/core 0.30.3 → 1.0.0-rc.10

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.
Files changed (216) hide show
  1. package/LICENSE +201 -0
  2. package/lib/api.d.ts +165 -0
  3. package/lib/api.d.ts.map +1 -0
  4. package/lib/api.js +190 -0
  5. package/lib/api.js.map +1 -0
  6. package/lib/category.d.ts +4 -4
  7. package/lib/category.js +4 -4
  8. package/lib/codegen/boolean-string-enums.d.ts +36 -0
  9. package/lib/codegen/boolean-string-enums.d.ts.map +1 -0
  10. package/lib/codegen/boolean-string-enums.js +94 -0
  11. package/lib/codegen/boolean-string-enums.js.map +1 -0
  12. package/lib/codegen/boolean-string-enums.test.d.ts +2 -0
  13. package/lib/codegen/boolean-string-enums.test.d.ts.map +1 -0
  14. package/lib/codegen/boolean-string-enums.test.js +147 -0
  15. package/lib/codegen/boolean-string-enums.test.js.map +1 -0
  16. package/lib/codegen/cli.d.ts +79 -0
  17. package/lib/codegen/cli.d.ts.map +1 -0
  18. package/lib/codegen/cli.js +149 -0
  19. package/lib/codegen/cli.js.map +1 -0
  20. package/lib/codegen/emit.d.ts +125 -0
  21. package/lib/codegen/emit.d.ts.map +1 -0
  22. package/lib/codegen/emit.js +101 -0
  23. package/lib/codegen/emit.js.map +1 -0
  24. package/lib/codegen/format.d.ts +23 -0
  25. package/lib/codegen/format.d.ts.map +1 -0
  26. package/lib/codegen/format.js +28 -0
  27. package/lib/codegen/format.js.map +1 -0
  28. package/lib/codegen/generator.d.ts +334 -0
  29. package/lib/codegen/generator.d.ts.map +1 -0
  30. package/lib/codegen/generator.js +813 -0
  31. package/lib/codegen/generator.js.map +1 -0
  32. package/lib/codegen/graph.d.ts +36 -0
  33. package/lib/codegen/graph.d.ts.map +1 -0
  34. package/lib/codegen/graph.js +136 -0
  35. package/lib/codegen/graph.js.map +1 -0
  36. package/lib/codegen/graphql-client.d.ts +62 -0
  37. package/lib/codegen/graphql-client.d.ts.map +1 -0
  38. package/lib/codegen/graphql-client.js +294 -0
  39. package/lib/codegen/graphql-client.js.map +1 -0
  40. package/lib/codegen/graphql-client.test.d.ts +2 -0
  41. package/lib/codegen/graphql-client.test.d.ts.map +1 -0
  42. package/lib/codegen/graphql-client.test.js +311 -0
  43. package/lib/codegen/graphql-client.test.js.map +1 -0
  44. package/lib/codegen/graphql.d.ts +207 -0
  45. package/lib/codegen/graphql.d.ts.map +1 -0
  46. package/lib/codegen/graphql.js +799 -0
  47. package/lib/codegen/graphql.js.map +1 -0
  48. package/lib/codegen/members.d.ts +25 -0
  49. package/lib/codegen/members.d.ts.map +1 -0
  50. package/lib/codegen/members.js +55 -0
  51. package/lib/codegen/members.js.map +1 -0
  52. package/lib/codegen/naming.d.ts +29 -0
  53. package/lib/codegen/naming.d.ts.map +1 -0
  54. package/lib/codegen/naming.js +74 -0
  55. package/lib/codegen/naming.js.map +1 -0
  56. package/lib/codegen/openapi-cli.d.ts +52 -0
  57. package/lib/codegen/openapi-cli.d.ts.map +1 -0
  58. package/lib/codegen/openapi-cli.js +109 -0
  59. package/lib/codegen/openapi-cli.js.map +1 -0
  60. package/lib/codegen/openapi.d.ts +178 -0
  61. package/lib/codegen/openapi.d.ts.map +1 -0
  62. package/lib/codegen/openapi.js +1377 -0
  63. package/lib/codegen/openapi.js.map +1 -0
  64. package/lib/codegen/operations.d.ts +24 -0
  65. package/lib/codegen/operations.d.ts.map +1 -0
  66. package/lib/codegen/operations.js +56 -0
  67. package/lib/codegen/operations.js.map +1 -0
  68. package/lib/codegen/pagination.d.ts +39 -0
  69. package/lib/codegen/pagination.d.ts.map +1 -0
  70. package/lib/codegen/pagination.js +33 -0
  71. package/lib/codegen/pagination.js.map +1 -0
  72. package/lib/codegen/patches.d.ts +65 -0
  73. package/lib/codegen/patches.d.ts.map +1 -0
  74. package/lib/codegen/patches.js +236 -0
  75. package/lib/codegen/patches.js.map +1 -0
  76. package/lib/codegen/patches.test.d.ts +2 -0
  77. package/lib/codegen/patches.test.d.ts.map +1 -0
  78. package/lib/codegen/patches.test.js +105 -0
  79. package/lib/codegen/patches.test.js.map +1 -0
  80. package/lib/codegen/prelude.d.ts +15 -0
  81. package/lib/codegen/prelude.d.ts.map +1 -0
  82. package/lib/codegen/prelude.js +60 -0
  83. package/lib/codegen/prelude.js.map +1 -0
  84. package/lib/codegen/proto.d.ts +121 -0
  85. package/lib/codegen/proto.d.ts.map +1 -0
  86. package/lib/codegen/proto.js +962 -0
  87. package/lib/codegen/proto.js.map +1 -0
  88. package/lib/codegen/rewrite-operation-ids.d.ts +131 -0
  89. package/lib/codegen/rewrite-operation-ids.d.ts.map +1 -0
  90. package/lib/codegen/rewrite-operation-ids.js +1079 -0
  91. package/lib/codegen/rewrite-operation-ids.js.map +1 -0
  92. package/lib/codegen/rewrite-operation-ids.test.d.ts +2 -0
  93. package/lib/codegen/rewrite-operation-ids.test.d.ts.map +1 -0
  94. package/lib/codegen/rewrite-operation-ids.test.js +533 -0
  95. package/lib/codegen/rewrite-operation-ids.test.js.map +1 -0
  96. package/lib/codegen/spec-path.d.ts +16 -0
  97. package/lib/codegen/spec-path.d.ts.map +1 -0
  98. package/lib/codegen/spec-path.js +101 -0
  99. package/lib/codegen/spec-path.js.map +1 -0
  100. package/lib/error-category.d.ts +28 -0
  101. package/lib/error-category.d.ts.map +1 -0
  102. package/lib/error-category.js +46 -0
  103. package/lib/error-category.js.map +1 -0
  104. package/lib/errors.d.ts +1 -0
  105. package/lib/errors.d.ts.map +1 -1
  106. package/lib/errors.js +18 -13
  107. package/lib/errors.js.map +1 -1
  108. package/lib/graphql.d.ts +284 -0
  109. package/lib/graphql.d.ts.map +1 -0
  110. package/lib/graphql.fixture.d.ts +249 -0
  111. package/lib/graphql.fixture.d.ts.map +1 -0
  112. package/lib/graphql.fixture.js +240 -0
  113. package/lib/graphql.fixture.js.map +1 -0
  114. package/lib/graphql.js +718 -0
  115. package/lib/graphql.js.map +1 -0
  116. package/lib/graphql.test.d.ts +2 -0
  117. package/lib/graphql.test.d.ts.map +1 -0
  118. package/lib/graphql.test.js +780 -0
  119. package/lib/graphql.test.js.map +1 -0
  120. package/lib/graphql.types.d.ts +2 -0
  121. package/lib/graphql.types.d.ts.map +1 -0
  122. package/lib/graphql.types.js +45 -0
  123. package/lib/graphql.types.js.map +1 -0
  124. package/lib/json-patch.d.ts +30 -30
  125. package/lib/json-patch.d.ts.map +1 -1
  126. package/lib/json-patch.js +73 -107
  127. package/lib/json-patch.js.map +1 -1
  128. package/lib/pagination.d.ts +77 -51
  129. package/lib/pagination.d.ts.map +1 -1
  130. package/lib/pagination.js +162 -94
  131. package/lib/pagination.js.map +1 -1
  132. package/lib/protocol-http.d.ts +74 -0
  133. package/lib/protocol-http.d.ts.map +1 -0
  134. package/lib/protocol-http.js +590 -0
  135. package/lib/protocol-http.js.map +1 -0
  136. package/lib/protocol-http.test.d.ts +2 -0
  137. package/lib/protocol-http.test.d.ts.map +1 -0
  138. package/lib/protocol-http.test.js +88 -0
  139. package/lib/protocol-http.test.js.map +1 -0
  140. package/lib/protocol-rest.d.ts +134 -0
  141. package/lib/protocol-rest.d.ts.map +1 -0
  142. package/lib/protocol-rest.js +256 -0
  143. package/lib/protocol-rest.js.map +1 -0
  144. package/lib/retry.d.ts +8 -2
  145. package/lib/retry.d.ts.map +1 -1
  146. package/lib/retry.js +22 -16
  147. package/lib/retry.js.map +1 -1
  148. package/lib/schema.d.ts +8 -9
  149. package/lib/schema.d.ts.map +1 -1
  150. package/lib/schema.js +8 -9
  151. package/lib/schema.js.map +1 -1
  152. package/lib/trait.d.ts +174 -0
  153. package/lib/trait.d.ts.map +1 -0
  154. package/lib/trait.js +123 -0
  155. package/lib/trait.js.map +1 -0
  156. package/package.json +24 -78
  157. package/src/api.ts +460 -0
  158. package/src/category.ts +4 -4
  159. package/src/codegen/boolean-string-enums.test.ts +168 -0
  160. package/src/codegen/boolean-string-enums.ts +106 -0
  161. package/src/codegen/cli.ts +285 -0
  162. package/src/codegen/emit.ts +203 -0
  163. package/src/codegen/format.ts +47 -0
  164. package/src/codegen/generator.ts +1283 -0
  165. package/src/codegen/graph.ts +151 -0
  166. package/src/codegen/graphql-client.test.ts +386 -0
  167. package/src/codegen/graphql-client.ts +419 -0
  168. package/src/codegen/graphql.ts +1217 -0
  169. package/src/codegen/members.ts +71 -0
  170. package/src/codegen/naming.ts +86 -0
  171. package/src/codegen/openapi-cli.ts +182 -0
  172. package/src/codegen/openapi.ts +1689 -0
  173. package/src/codegen/operations.ts +76 -0
  174. package/src/codegen/pagination.ts +71 -0
  175. package/src/codegen/patches.test.ts +130 -0
  176. package/src/codegen/patches.ts +291 -0
  177. package/src/codegen/prelude.ts +70 -0
  178. package/src/codegen/proto.ts +1128 -0
  179. package/src/codegen/rewrite-operation-ids.test.ts +563 -0
  180. package/src/codegen/rewrite-operation-ids.ts +1206 -0
  181. package/src/codegen/spec-path.ts +115 -0
  182. package/src/error-category.ts +84 -0
  183. package/src/errors.ts +22 -25
  184. package/src/graphql.fixture.ts +371 -0
  185. package/src/graphql.test.ts +974 -0
  186. package/src/graphql.ts +1321 -0
  187. package/src/graphql.types.ts +185 -0
  188. package/src/json-patch.ts +95 -122
  189. package/src/pagination.ts +217 -146
  190. package/src/protocol-http.test.ts +107 -0
  191. package/src/protocol-http.ts +735 -0
  192. package/src/protocol-rest.ts +391 -0
  193. package/src/retry.ts +21 -22
  194. package/src/schema.ts +9 -10
  195. package/src/trait.ts +274 -0
  196. package/README.md +0 -30
  197. package/lib/client.d.ts +0 -167
  198. package/lib/client.d.ts.map +0 -1
  199. package/lib/client.js +0 -659
  200. package/lib/client.js.map +0 -1
  201. package/lib/schemas.d.ts +0 -60
  202. package/lib/schemas.d.ts.map +0 -1
  203. package/lib/schemas.js +0 -79
  204. package/lib/schemas.js.map +0 -1
  205. package/lib/sensitive.d.ts +0 -71
  206. package/lib/sensitive.d.ts.map +0 -1
  207. package/lib/sensitive.js +0 -96
  208. package/lib/sensitive.js.map +0 -1
  209. package/lib/traits.d.ts +0 -421
  210. package/lib/traits.d.ts.map +0 -1
  211. package/lib/traits.js +0 -737
  212. package/lib/traits.js.map +0 -1
  213. package/src/client.ts +0 -1177
  214. package/src/schemas.ts +0 -128
  215. package/src/sensitive.ts +0 -119
  216. package/src/traits.ts +0 -996
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Spec path resolution — the seam between a production spec mirror and a
3
+ * local working copy of one (dev-time only).
4
+ *
5
+ * Every SDK package reads its specs from `packages/<pkg>/specs/`. In
6
+ * production that directory holds a submodule of the package's mirror
7
+ * repository, `distilled-mirror/spec-mirror-<pkg>`, whose layout is fixed:
8
+ *
9
+ * packages/<pkg>/specs/spec-mirror-<pkg>/
10
+ * ├── .meta/ fetch-specs.ts + its package.json
11
+ * └── specs/ what the generator reads
12
+ *
13
+ * A contributor adding a provider cannot create that repository — it lives in
14
+ * an org they have no write access to, and it is only created once the
15
+ * `distilled-submodules` stack deploys on merge to main. So `pnpm specs:local
16
+ * <pkg>` runs the same `fetch-specs.ts` into a gitignored directory shaped
17
+ * exactly like the mirror:
18
+ *
19
+ * packages/<pkg>/specs/.local/
20
+ * ├── .meta/
21
+ * └── specs/
22
+ *
23
+ * Setting {@link LOCAL_ENV} then re-roots every spec read into it. Nothing in
24
+ * the package's source changes — the `specPath` a `convert.ts` declares stays
25
+ * the production one — so there is no `.local` reference that could be
26
+ * committed by accident, and the switch is impossible to leave on: it lives
27
+ * in the environment of one command, never in a file.
28
+ *
29
+ * The opt-in is deliberate rather than a fallback-when-missing. A silent
30
+ * fallback would regenerate an SDK from a stale `.local` copy the moment a
31
+ * submodule failed to check out, and the result — committed, formatted,
32
+ * plausible — would be indistinguishable from a real regeneration.
33
+ */
34
+ import * as fs from "node:fs";
35
+ import * as path from "node:path";
36
+
37
+ /** Set to a non-empty value to read specs from `specs/.local` instead. */
38
+ export const LOCAL_ENV = "DISTILLED_SPECS_LOCAL";
39
+
40
+ /** The local working copy of a mirror, relative to a package root. */
41
+ export const LOCAL_DIR = path.join("specs", ".local");
42
+
43
+ /** Repository name of the mirror feeding `packages/<pkg>`. */
44
+ export const mirrorRepositoryName = (pkg: string) => `spec-mirror-${pkg}`;
45
+
46
+ /** Whether this process reads specs from `specs/.local`. */
47
+ export const isLocalSpecs = (): boolean =>
48
+ (process.env[LOCAL_ENV] ?? "").trim() !== "";
49
+
50
+ let announced = false;
51
+
52
+ /**
53
+ * The path within a mirror's `specs/` directory that `specPath` names.
54
+ *
55
+ * Spec paths take one of two shapes — `specs/<mirror>/specs/<tail>` for a
56
+ * submodule and `specs/<tail>` for a committed file — and in both the tail is
57
+ * everything after the LAST `specs` segment. That one rule covers the
58
+ * submodule form, the committed form, the nested forms
59
+ * (`specs/<mirror>/specs/models/<service>/...`) and the directory form
60
+ * (`specs/<mirror>/specs`, whose tail is empty — the mirror's `specs/`
61
+ * directory itself) without needing to know which it was handed.
62
+ *
63
+ * `undefined` means there is no `specs` segment at all, which local mode
64
+ * cannot interpret.
65
+ */
66
+ const tailWithinSpecs = (specPath: string): string | undefined => {
67
+ const segments = specPath.split(/[/\\]/).filter((s) => s !== "" && s !== ".");
68
+ const last = segments.lastIndexOf("specs");
69
+ if (last === -1) return undefined;
70
+ return segments.slice(last + 1).join(path.sep);
71
+ };
72
+
73
+ /**
74
+ * Resolve a package-relative spec path to an absolute one.
75
+ *
76
+ * @param root absolute package root (`path.resolve(import.meta.dir, "..")`)
77
+ * @param specPath the production path, relative to `root` (or absolute)
78
+ */
79
+ export const resolveSpecPath = (root: string, specPath: string): string => {
80
+ const production = path.resolve(root, specPath);
81
+ if (!isLocalSpecs() || path.isAbsolute(specPath)) return production;
82
+
83
+ const tail = tailWithinSpecs(specPath);
84
+ if (tail === undefined) {
85
+ throw new Error(
86
+ `${LOCAL_ENV} is set, but "${specPath}" has no \`specs/\` segment to ` +
87
+ `re-root. Local mode only understands paths under the package's ` +
88
+ `specs/ directory.`,
89
+ );
90
+ }
91
+
92
+ if (!announced) {
93
+ announced = true;
94
+ // stderr, not stdout: generate-all captures stdout per package and only
95
+ // prints it on failure, and this is exactly the line you need to see on
96
+ // a run that succeeded against the wrong specs.
97
+ console.error(
98
+ `⚠ ${LOCAL_ENV} — reading specs from ${LOCAL_DIR}, not the mirror submodule.`,
99
+ );
100
+ }
101
+
102
+ const local = path.join(root, LOCAL_DIR, "specs", tail);
103
+ if (!fs.existsSync(local)) {
104
+ // A missing local spec is a setup mistake, not a generator bug, and the
105
+ // ENOENT the caller would otherwise raise names a path nobody recognises.
106
+ throw new Error(
107
+ `${local} does not exist. Materialise this package's mirror first:\n` +
108
+ ` pnpm specs:local ${path.basename(root)}\n` +
109
+ `If that succeeds and this path is still missing, the package's ` +
110
+ `declared spec path (${specPath}) is not mirror-shaped yet — it must ` +
111
+ `read \`specs/<mirror>/specs/<file>\` for local mode to find it.`,
112
+ );
113
+ }
114
+ return local;
115
+ };
@@ -0,0 +1,84 @@
1
+ import { Effect, Predicate } from "effect";
2
+
3
+ export const categoriesKey = "@distilled/meta/error-categories";
4
+
5
+ export const withCategory =
6
+ <Categories extends Array<PropertyKey>>(...categories: Categories) =>
7
+ <Args extends Array<any>, Ret, C extends { new (...args: Args): Ret }>(
8
+ C: C,
9
+ ): C & {
10
+ new (...args: Args): Ret & {
11
+ [categoriesKey]: { [Cat in Categories[number]]: true };
12
+ };
13
+ } => {
14
+ // @ts-expect-error
15
+ const Mixed = class extends C {};
16
+
17
+ for (const category of categories) {
18
+ if (!(categoriesKey in Mixed.prototype)) {
19
+ // @ts-expect-error
20
+ Mixed.prototype[categoriesKey] = {};
21
+ }
22
+ // @ts-expect-error
23
+ Mixed.prototype[categoriesKey][category] = true;
24
+ }
25
+
26
+ return Mixed as any;
27
+ };
28
+
29
+ export type AllKeys<E> = E extends { [categoriesKey]: infer Q }
30
+ ? keyof Q
31
+ : never;
32
+
33
+ export type ExtractAll<E, Cats extends PropertyKey> = Cats extends any
34
+ ? Extract<E, { [categoriesKey]: { [K in Cats]: any } }>
35
+ : never;
36
+
37
+ /**
38
+ * Runtime check: does `value` carry the given category?
39
+ * Works on instances tagged via `withCategory(...)`.
40
+ */
41
+ export const hasCategory =
42
+ <const Cat extends PropertyKey>(category: Cat) =>
43
+ (
44
+ value: unknown,
45
+ ): value is { readonly [categoriesKey]: { readonly [K in Cat]: true } } => {
46
+ if (!Predicate.isObject(value)) return false;
47
+ if (!Predicate.hasProperty(categoriesKey)(value)) return false;
48
+ return category in (value as any)[categoriesKey];
49
+ };
50
+
51
+ /** True if `value` has any of the given categories. */
52
+ export const hasAnyCategory =
53
+ (...categories: ReadonlyArray<PropertyKey>) =>
54
+ (value: unknown): boolean => {
55
+ if (!Predicate.isObject(value)) return false;
56
+ if (!Predicate.hasProperty(categoriesKey)(value)) return false;
57
+ const cats = (value as any)[categoriesKey];
58
+ for (const c of categories) if (c in cats) return true;
59
+ return false;
60
+ };
61
+
62
+ export const catchCategory =
63
+ <E, const Categories extends Array<AllKeys<E>>, A2, E2, R2>(
64
+ ...args: [
65
+ ...Categories,
66
+ f: (err: ExtractAll<E, Categories[number]>) => Effect.Effect<A2, E2, R2>,
67
+ ]
68
+ ) =>
69
+ <A, R>(
70
+ effect: Effect.Effect<A, E, R>,
71
+ ): Effect.Effect<
72
+ A | A2,
73
+ E2 | Exclude<E, ExtractAll<E, Categories[number]>>,
74
+ R | R2
75
+ > => {
76
+ const f = args.pop()! as (
77
+ err: ExtractAll<E, Categories[number]>,
78
+ ) => Effect.Effect<A2, E2, R2>;
79
+ const categories = args as Array<PropertyKey>;
80
+ const matches = hasAnyCategory(...categories);
81
+ return Effect.catchIf(effect, matches, (e) =>
82
+ f(e as ExtractAll<E, Categories[number]>),
83
+ ) as any;
84
+ };
package/src/errors.ts CHANGED
@@ -33,7 +33,7 @@ export const DurationSchema = Schema.declare<Duration.Duration>(
33
33
  /**
34
34
  * Unauthorized - Authentication failure (401).
35
35
  */
36
- export class Unauthorized extends Schema.TaggedErrorClass<Unauthorized>()(
36
+ export class Unauthorized extends Schema.TaggedError<Unauthorized>()(
37
37
  "Unauthorized",
38
38
  { message: Schema.String },
39
39
  ).pipe(Category.withAuthError) {}
@@ -41,37 +41,35 @@ export class Unauthorized extends Schema.TaggedErrorClass<Unauthorized>()(
41
41
  /**
42
42
  * Forbidden - Access denied (403).
43
43
  */
44
- export class Forbidden extends Schema.TaggedErrorClass<Forbidden>()(
45
- "Forbidden",
46
- { message: Schema.String },
47
- ).pipe(Category.withAuthError) {}
44
+ export class Forbidden extends Schema.TaggedError<Forbidden>()("Forbidden", {
45
+ message: Schema.String,
46
+ }).pipe(Category.withAuthError) {}
48
47
 
49
48
  /**
50
49
  * NotFound - Resource not found (404).
51
50
  */
52
- export class NotFound extends Schema.TaggedErrorClass<NotFound>()("NotFound", {
51
+ export class NotFound extends Schema.TaggedError<NotFound>()("NotFound", {
53
52
  message: Schema.String,
54
53
  }).pipe(Category.withNotFoundError) {}
55
54
 
56
55
  /**
57
56
  * BadRequest - Invalid request (400).
58
57
  */
59
- export class BadRequest extends Schema.TaggedErrorClass<BadRequest>()(
60
- "BadRequest",
61
- { message: Schema.String },
62
- ).pipe(Category.withBadRequestError) {}
58
+ export class BadRequest extends Schema.TaggedError<BadRequest>()("BadRequest", {
59
+ message: Schema.String,
60
+ }).pipe(Category.withBadRequestError) {}
63
61
 
64
62
  /**
65
63
  * Conflict - Resource conflict (409).
66
64
  */
67
- export class Conflict extends Schema.TaggedErrorClass<Conflict>()("Conflict", {
65
+ export class Conflict extends Schema.TaggedError<Conflict>()("Conflict", {
68
66
  message: Schema.String,
69
67
  }).pipe(Category.withConflictError) {}
70
68
 
71
69
  /**
72
70
  * UnprocessableEntity - Validation error (422).
73
71
  */
74
- export class UnprocessableEntity extends Schema.TaggedErrorClass<UnprocessableEntity>()(
72
+ export class UnprocessableEntity extends Schema.TaggedError<UnprocessableEntity>()(
75
73
  "UnprocessableEntity",
76
74
  { message: Schema.String },
77
75
  ).pipe(Category.withBadRequestError) {}
@@ -79,7 +77,7 @@ export class UnprocessableEntity extends Schema.TaggedErrorClass<UnprocessableEn
79
77
  /**
80
78
  * TooManyRequests - Rate limited (429).
81
79
  */
82
- export class TooManyRequests extends Schema.TaggedErrorClass<TooManyRequests>()(
80
+ export class TooManyRequests extends Schema.TaggedError<TooManyRequests>()(
83
81
  "TooManyRequests",
84
82
  {
85
83
  message: Schema.String,
@@ -93,7 +91,7 @@ export class TooManyRequests extends Schema.TaggedErrorClass<TooManyRequests>()(
93
91
  /**
94
92
  * Locked - Resource locked (423).
95
93
  */
96
- export class Locked extends Schema.TaggedErrorClass<Locked>()("Locked", {
94
+ export class Locked extends Schema.TaggedError<Locked>()("Locked", {
97
95
  message: Schema.String,
98
96
  retryAfter: Schema.optional(DurationSchema),
99
97
  }).pipe(Category.withLockedError, Category.withRetryable()) {}
@@ -106,7 +104,7 @@ export class Locked extends Schema.TaggedErrorClass<Locked>()("Locked", {
106
104
  * 200 envelope carrying `{ code: 10002, message: "An unknown error has
107
105
  * occurred" }` for transient backend failures on some endpoints).
108
106
  */
109
- export class InternalServerError extends Schema.TaggedErrorClass<InternalServerError>()(
107
+ export class InternalServerError extends Schema.TaggedError<InternalServerError>()(
110
108
  "InternalServerError",
111
109
  {
112
110
  message: Schema.String,
@@ -118,18 +116,15 @@ export class InternalServerError extends Schema.TaggedErrorClass<InternalServerE
118
116
  /**
119
117
  * BadGateway - Bad gateway (502).
120
118
  */
121
- export class BadGateway extends Schema.TaggedErrorClass<BadGateway>()(
122
- "BadGateway",
123
- {
124
- message: Schema.String,
125
- retryAfter: Schema.optional(DurationSchema),
126
- },
127
- ).pipe(Category.withServerError, Category.withRetryable()) {}
119
+ export class BadGateway extends Schema.TaggedError<BadGateway>()("BadGateway", {
120
+ message: Schema.String,
121
+ retryAfter: Schema.optional(DurationSchema),
122
+ }).pipe(Category.withServerError, Category.withRetryable()) {}
128
123
 
129
124
  /**
130
125
  * ServiceUnavailable - Service unavailable (503).
131
126
  */
132
- export class ServiceUnavailable extends Schema.TaggedErrorClass<ServiceUnavailable>()(
127
+ export class ServiceUnavailable extends Schema.TaggedError<ServiceUnavailable>()(
133
128
  "ServiceUnavailable",
134
129
  {
135
130
  message: Schema.String,
@@ -140,7 +135,7 @@ export class ServiceUnavailable extends Schema.TaggedErrorClass<ServiceUnavailab
140
135
  /**
141
136
  * GatewayTimeout - Gateway timeout (504).
142
137
  */
143
- export class GatewayTimeout extends Schema.TaggedErrorClass<GatewayTimeout>()(
138
+ export class GatewayTimeout extends Schema.TaggedError<GatewayTimeout>()(
144
139
  "GatewayTimeout",
145
140
  {
146
141
  message: Schema.String,
@@ -151,7 +146,7 @@ export class GatewayTimeout extends Schema.TaggedErrorClass<GatewayTimeout>()(
151
146
  /**
152
147
  * Configuration error - missing or invalid configuration.
153
148
  */
154
- export class ConfigError extends Schema.TaggedErrorClass<ConfigError>()(
149
+ export class ConfigError extends Schema.TaggedError<ConfigError>()(
155
150
  "ConfigError",
156
151
  { message: Schema.String },
157
152
  ).pipe(Category.withConfigurationError) {}
@@ -228,3 +223,5 @@ export const DEFAULT_ERRORS = [
228
223
  ] as const;
229
224
 
230
225
  export type DefaultErrors = InstanceType<(typeof DEFAULT_ERRORS)[number]>;
226
+
227
+ export const RETRYABLE = "@distilled/errors/retryable" as const;
@@ -0,0 +1,371 @@
1
+ import * as Schema from "effect/Schema";
2
+ import * as G from "./graphql.ts";
3
+ import type { GraphQLField, GraphQLModel } from "./codegen/graphql-client.ts";
4
+
5
+ export class ProjectNotFound extends Schema.TaggedError<ProjectNotFound>()(
6
+ "ProjectNotFound",
7
+ G.errorFields,
8
+ ) {}
9
+ export class ServicesUnavailable extends Schema.TaggedError<ServicesUnavailable>()(
10
+ "ServicesUnavailable",
11
+ G.errorFields,
12
+ ) {}
13
+ export class Unauthorized extends Schema.TaggedError<Unauthorized>()(
14
+ "Unauthorized",
15
+ G.errorFields,
16
+ ) {}
17
+ export class ProjectDescriptionUnavailable extends Schema.TaggedError<ProjectDescriptionUnavailable>()(
18
+ "ProjectDescriptionUnavailable",
19
+ G.errorFields,
20
+ ) {}
21
+ export class ServiceDescriptionUnavailable extends Schema.TaggedError<ServiceDescriptionUnavailable>()(
22
+ "ServiceDescriptionUnavailable",
23
+ G.errorFields,
24
+ ) {}
25
+ export class PageInfoUnavailable extends Schema.TaggedError<PageInfoUnavailable>()(
26
+ "PageInfoUnavailable",
27
+ G.errorFields,
28
+ ) {}
29
+
30
+ export const errorClasses = {
31
+ ProjectNotFound,
32
+ ServicesUnavailable,
33
+ Unauthorized,
34
+ ProjectDescriptionUnavailable,
35
+ ServiceDescriptionUnavailable,
36
+ PageInfoUnavailable,
37
+ };
38
+
39
+ export type FixtureSchema = {
40
+ query: "Query";
41
+ mutation: "Mutation";
42
+ subscription: never;
43
+ globalErrors: "Unauthorized";
44
+ errors: {
45
+ ProjectNotFound: ProjectNotFound;
46
+ ServicesUnavailable: ServicesUnavailable;
47
+ Unauthorized: Unauthorized;
48
+ ProjectDescriptionUnavailable: ProjectDescriptionUnavailable;
49
+ ServiceDescriptionUnavailable: ServiceDescriptionUnavailable;
50
+ PageInfoUnavailable: PageInfoUnavailable;
51
+ };
52
+ scalars: {
53
+ ID: string;
54
+ String: string;
55
+ Int: number;
56
+ Boolean: boolean;
57
+ JSON: unknown;
58
+ Status: "ACTIVE" | "ARCHIVED";
59
+ };
60
+ possibleTypes: {
61
+ Node: "Project" | "Service";
62
+ SearchResult: "Project" | "Service";
63
+ };
64
+ types: {
65
+ Query: {
66
+ project: G.Field<{ id: string }, "Project", "ProjectNotFound">;
67
+ projects: G.Field<
68
+ {
69
+ filter?: {
70
+ name?: string | null;
71
+ statuses?: Array<"ACTIVE" | "ARCHIVED"> | null;
72
+ metadata?: unknown;
73
+ } | null;
74
+ },
75
+ "[Project]!"
76
+ >;
77
+ node: G.Field<{ id: string }, "Node">;
78
+ search: G.Field<{ text: string }, "[SearchResult!]!">;
79
+ services: G.Field<
80
+ { first?: number | null; after?: string | null },
81
+ "ServiceConnection!",
82
+ "ServicesUnavailable"
83
+ >;
84
+ ping: G.Field<{}, "Boolean!">;
85
+ };
86
+ Mutation: {
87
+ editProject: G.Field<
88
+ { id: string; name: string },
89
+ "Project!",
90
+ "ProjectNotFound"
91
+ >;
92
+ deleteProject: G.Field<{ id: string }, "Boolean!", "ProjectNotFound">;
93
+ };
94
+ Project: {
95
+ id: G.Field<{}, "ID!">;
96
+ name: G.Field<{}, "String!">;
97
+ description: G.Field<{}, "String", "ProjectDescriptionUnavailable">;
98
+ parent: G.Field<{}, "Project">;
99
+ services: G.Field<
100
+ {
101
+ first?: number | null;
102
+ after?: string | null;
103
+ filter?: { name?: string | null } | null;
104
+ },
105
+ "ServiceConnection",
106
+ "ServicesUnavailable"
107
+ >;
108
+ };
109
+ Service: {
110
+ id: G.Field<{}, "ID!">;
111
+ parent: G.Field<{}, "Project">;
112
+ name: G.Field<{}, "String!">;
113
+ description: G.Field<{}, "String", "ServiceDescriptionUnavailable">;
114
+ project: G.Field<{}, "Project!">;
115
+ status: G.Field<{}, "Status!">;
116
+ };
117
+ ServiceConnection: {
118
+ edges: G.Field<{}, "[ServiceEdge!]!">;
119
+ pageInfo: G.Field<{}, "PageInfo!">;
120
+ };
121
+ ServiceEdge: {
122
+ cursor: G.Field<{}, "String!">;
123
+ node: G.Field<{}, "Service!">;
124
+ };
125
+ PageInfo: {
126
+ endCursor: G.Field<{}, "String">;
127
+ hasNextPage: G.Field<{}, "Boolean!", "PageInfoUnavailable">;
128
+ };
129
+ Node: { id: G.Field<{}, "ID!">; parent: G.Field<{}, "Project"> };
130
+ SearchResult: {};
131
+ };
132
+ };
133
+
134
+ const field = (
135
+ type: string,
136
+ args: Record<string, { type: string; defaultValue?: string }> = {},
137
+ errors: string[] = [],
138
+ ): GraphQLField => ({ type, args, errors });
139
+
140
+ export const fixtureModel: GraphQLModel = {
141
+ version: 1,
142
+ queryType: "Query",
143
+ mutationType: "Mutation",
144
+ errors: {
145
+ ProjectNotFound: { matchers: [{ code: "NOT_FOUND" }] },
146
+ ServicesUnavailable: { matchers: [{ code: "UNAVAILABLE" }] },
147
+ ProjectDescriptionUnavailable: {
148
+ matchers: [{ code: "DESCRIPTION_UNAVAILABLE" }],
149
+ },
150
+ ServiceDescriptionUnavailable: {
151
+ matchers: [{ code: "DESCRIPTION_UNAVAILABLE" }],
152
+ },
153
+ PageInfoUnavailable: { matchers: [{ code: "PAGE_INFO_UNAVAILABLE" }] },
154
+ Unauthorized: {
155
+ matchers: [
156
+ { code: "UNAUTHENTICATED" },
157
+ { code: "INTERNAL_SERVER_ERROR", message: "Not Authorized" },
158
+ ],
159
+ },
160
+ },
161
+ globalErrors: ["Unauthorized"],
162
+ types: {
163
+ ID: { kind: "SCALAR", scalar: "string" },
164
+ String: { kind: "SCALAR", scalar: "string" },
165
+ Int: { kind: "SCALAR", scalar: "number" },
166
+ Boolean: { kind: "SCALAR", scalar: "boolean" },
167
+ JSON: { kind: "SCALAR", scalar: "unknown" },
168
+ Status: { kind: "ENUM", enumValues: ["ACTIVE", "ARCHIVED"] },
169
+ ProjectFilter: {
170
+ kind: "INPUT_OBJECT",
171
+ inputFields: {
172
+ name: { type: "String" },
173
+ statuses: { type: "[Status!]", defaultValue: "[ACTIVE]" },
174
+ metadata: { type: "JSON" },
175
+ },
176
+ },
177
+ Query: {
178
+ kind: "OBJECT",
179
+ fields: {
180
+ project: field("Project", { id: { type: "ID!" } }, ["ProjectNotFound"]),
181
+ projects: field("[Project]!", { filter: { type: "ProjectFilter" } }),
182
+ node: field("Node", { id: { type: "ID!" } }),
183
+ search: field("[SearchResult!]!", { text: { type: "String!" } }),
184
+ services: field(
185
+ "ServiceConnection!",
186
+ {
187
+ first: { type: "Int", defaultValue: "20" },
188
+ after: { type: "String" },
189
+ },
190
+ ["ServicesUnavailable"],
191
+ ),
192
+ ping: field("Boolean!"),
193
+ },
194
+ },
195
+ Mutation: {
196
+ kind: "OBJECT",
197
+ fields: {
198
+ editProject: field(
199
+ "Project!",
200
+ { id: { type: "ID!" }, name: { type: "String!" } },
201
+ ["ProjectNotFound"],
202
+ ),
203
+ deleteProject: field("Boolean!", { id: { type: "ID!" } }, [
204
+ "ProjectNotFound",
205
+ ]),
206
+ },
207
+ },
208
+ Project: {
209
+ kind: "OBJECT",
210
+ interfaces: ["Node"],
211
+ fields: {
212
+ id: field("ID!"),
213
+ name: field("String!"),
214
+ description: field("String", {}, ["ProjectDescriptionUnavailable"]),
215
+ parent: field("Project"),
216
+ services: field(
217
+ "ServiceConnection",
218
+ {
219
+ first: { type: "Int", defaultValue: "20" },
220
+ after: { type: "String" },
221
+ filter: { type: "ProjectFilter" },
222
+ },
223
+ ["ServicesUnavailable"],
224
+ ),
225
+ },
226
+ },
227
+ Service: {
228
+ kind: "OBJECT",
229
+ interfaces: ["Node"],
230
+ fields: {
231
+ id: field("ID!"),
232
+ parent: field("Project"),
233
+ name: field("String!"),
234
+ description: field("String", {}, ["ServiceDescriptionUnavailable"]),
235
+ project: field("Project!"),
236
+ status: field("Status!"),
237
+ },
238
+ },
239
+ ServiceConnection: {
240
+ kind: "OBJECT",
241
+ fields: { edges: field("[ServiceEdge!]!"), pageInfo: field("PageInfo!") },
242
+ },
243
+ ServiceEdge: {
244
+ kind: "OBJECT",
245
+ fields: { cursor: field("String!"), node: field("Service!") },
246
+ },
247
+ PageInfo: {
248
+ kind: "OBJECT",
249
+ fields: {
250
+ endCursor: field("String"),
251
+ hasNextPage: field("Boolean!", {}, ["PageInfoUnavailable"]),
252
+ },
253
+ },
254
+ Node: {
255
+ kind: "INTERFACE",
256
+ fields: { id: field("ID!"), parent: field("Project") },
257
+ possibleTypes: ["Project", "Service"],
258
+ },
259
+ SearchResult: { kind: "UNION", possibleTypes: ["Project", "Service"] },
260
+ },
261
+ };
262
+
263
+ /** A deliberately small recursive schema for GraphQL compiler/runtime tests. */
264
+ export const fixtureSDL = /* GraphQL */ `
265
+ scalar JSON
266
+
267
+ enum Status {
268
+ ACTIVE
269
+ ARCHIVED
270
+ }
271
+
272
+ input ProjectFilter {
273
+ name: String
274
+ statuses: [Status!] = [ACTIVE]
275
+ metadata: JSON
276
+ }
277
+
278
+ interface Node {
279
+ id: ID!
280
+ parent: Project
281
+ }
282
+
283
+ type Project implements Node {
284
+ id: ID!
285
+ name: String!
286
+ description: String
287
+ parent: Project
288
+ services(
289
+ first: Int = 20
290
+ after: String
291
+ filter: ProjectFilter
292
+ ): ServiceConnection
293
+ }
294
+
295
+ type Service implements Node {
296
+ id: ID!
297
+ parent: Project
298
+ name: String!
299
+ description: String
300
+ project: Project!
301
+ status: Status!
302
+ }
303
+
304
+ type ServiceConnection {
305
+ edges: [ServiceEdge!]!
306
+ pageInfo: PageInfo!
307
+ }
308
+
309
+ type ServiceEdge {
310
+ cursor: String!
311
+ node: Service!
312
+ }
313
+
314
+ type PageInfo {
315
+ endCursor: String
316
+ hasNextPage: Boolean!
317
+ }
318
+
319
+ union SearchResult = Project | Service
320
+
321
+ type Query {
322
+ project(id: ID!): Project
323
+ projects(filter: ProjectFilter): [Project]!
324
+ node(id: ID!): Node
325
+ search(text: String!): [SearchResult!]!
326
+ services(first: Int = 20, after: String): ServiceConnection!
327
+ ping: Boolean!
328
+ }
329
+
330
+ type Mutation {
331
+ editProject(id: ID!, name: String!): Project!
332
+ deleteProject(id: ID!): Boolean!
333
+ }
334
+ `;
335
+
336
+ /** Includes characters that must travel in variables, never in GraphQL text. */
337
+ export const adversarialArgument = 'quoted "value" \\ path\nline\t\u0000 😀';
338
+
339
+ export const partialProjectEnvelope = {
340
+ data: {
341
+ project: { id: "p1", services: null },
342
+ ping: true,
343
+ },
344
+ errors: [
345
+ {
346
+ message: "Services temporarily unavailable",
347
+ path: ["project", "services"],
348
+ locations: [{ line: 1, column: 25 }],
349
+ extensions: { code: "UNAVAILABLE", incident: "incident-1" },
350
+ },
351
+ ],
352
+ } as const;
353
+
354
+ export const multipleErrorEnvelope = {
355
+ data: {
356
+ production: null,
357
+ staging: { id: "p2", services: null },
358
+ },
359
+ errors: [
360
+ {
361
+ message: "Project not found",
362
+ path: ["production"],
363
+ extensions: { code: "NOT_FOUND" },
364
+ },
365
+ {
366
+ message: "Services temporarily unavailable",
367
+ path: ["staging", "services"],
368
+ extensions: { code: "UNAVAILABLE" },
369
+ },
370
+ ],
371
+ } as const;