@hideyukimori/nene2-client 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.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +138 -0
  3. package/dist/client/config.d.ts +26 -0
  4. package/dist/client/config.d.ts.map +1 -0
  5. package/dist/client/config.js +19 -0
  6. package/dist/client/config.js.map +1 -0
  7. package/dist/client/create-nene2-client.d.ts +92 -0
  8. package/dist/client/create-nene2-client.d.ts.map +1 -0
  9. package/dist/client/create-nene2-client.js +54 -0
  10. package/dist/client/create-nene2-client.js.map +1 -0
  11. package/dist/client/errors.d.ts +17 -0
  12. package/dist/client/errors.d.ts.map +1 -0
  13. package/dist/client/errors.js +20 -0
  14. package/dist/client/errors.js.map +1 -0
  15. package/dist/client/index.d.ts +5 -0
  16. package/dist/client/index.d.ts.map +1 -0
  17. package/dist/client/index.js +4 -0
  18. package/dist/client/index.js.map +1 -0
  19. package/dist/client/path.d.ts +5 -0
  20. package/dist/client/path.d.ts.map +1 -0
  21. package/dist/client/path.js +17 -0
  22. package/dist/client/path.js.map +1 -0
  23. package/dist/client/request.d.ts +23 -0
  24. package/dist/client/request.d.ts.map +1 -0
  25. package/dist/client/request.js +99 -0
  26. package/dist/client/request.js.map +1 -0
  27. package/dist/client/validation-errors.d.ts +11 -0
  28. package/dist/client/validation-errors.d.ts.map +1 -0
  29. package/dist/client/validation-errors.js +28 -0
  30. package/dist/client/validation-errors.js.map +1 -0
  31. package/dist/index.d.ts +11 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +9 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/problem/constants.d.ts +7 -0
  36. package/dist/problem/constants.d.ts.map +1 -0
  37. package/dist/problem/constants.js +7 -0
  38. package/dist/problem/constants.js.map +1 -0
  39. package/dist/problem/guards.d.ts +38 -0
  40. package/dist/problem/guards.d.ts.map +1 -0
  41. package/dist/problem/guards.js +111 -0
  42. package/dist/problem/guards.js.map +1 -0
  43. package/dist/problem/index.d.ts +4 -0
  44. package/dist/problem/index.d.ts.map +1 -0
  45. package/dist/problem/index.js +3 -0
  46. package/dist/problem/index.js.map +1 -0
  47. package/dist/problem/types.d.ts +26 -0
  48. package/dist/problem/types.d.ts.map +1 -0
  49. package/dist/problem/types.js +6 -0
  50. package/dist/problem/types.js.map +1 -0
  51. package/dist/types/examples/index.d.ts +7 -0
  52. package/dist/types/examples/index.d.ts.map +1 -0
  53. package/dist/types/examples/index.js +4 -0
  54. package/dist/types/examples/index.js.map +1 -0
  55. package/dist/types/examples/notes.d.ts +28 -0
  56. package/dist/types/examples/notes.d.ts.map +1 -0
  57. package/dist/types/examples/notes.js +37 -0
  58. package/dist/types/examples/notes.js.map +1 -0
  59. package/dist/types/examples/protected.d.ts +10 -0
  60. package/dist/types/examples/protected.d.ts.map +1 -0
  61. package/dist/types/examples/protected.js +15 -0
  62. package/dist/types/examples/protected.js.map +1 -0
  63. package/dist/types/examples/tags.d.ts +23 -0
  64. package/dist/types/examples/tags.d.ts.map +1 -0
  65. package/dist/types/examples/tags.js +31 -0
  66. package/dist/types/examples/tags.js.map +1 -0
  67. package/dist/types/index.d.ts +8 -0
  68. package/dist/types/index.d.ts.map +1 -0
  69. package/dist/types/index.js +4 -0
  70. package/dist/types/index.js.map +1 -0
  71. package/dist/types/system.d.ts +36 -0
  72. package/dist/types/system.d.ts.map +1 -0
  73. package/dist/types/system.js +54 -0
  74. package/dist/types/system.js.map +1 -0
  75. package/package.json +64 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 hideyuki MORI
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,138 @@
1
+ # nene2-js
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
4
+ [![Node](https://img.shields.io/badge/Node-%3E%3D22%20LTS-339933)](https://nodejs.org/)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178C6)](https://www.typescriptlang.org/)
6
+
7
+ TypeScript ecosystem for [NENE2](https://github.com/hideyukiMORI/NENE2): OpenAPI-aligned types, HTTP client helpers, and Problem Details utilities for apps that consume NENE2 JSON APIs.
8
+
9
+ **This repository is not a Node.js port of the PHP framework.** The PHP runtime stays in [NENE2](https://github.com/hideyukiMORI/NENE2). MCP stdio servers in PHP live in [nene-mcp](https://github.com/hideyukiMORI/nene-mcp).
10
+
11
+ ## What this repo is for
12
+
13
+ - Typed fetch wrappers and shared client boundaries derived from NENE2 OpenAPI
14
+ - RFC 9457 Problem Details parsing and validation-error helpers for TypeScript consumers
15
+ - Optional codegen scripts and published npm packages (`@hideyukimori/nene2-client`, scoped subpackages later)
16
+ - Documentation and tooling that follow the same Issue-driven workflow as NENE2
17
+
18
+ ## What this repo is not for
19
+
20
+ - Replacing NENE2 PHP HTTP runtime, routing, middleware, or DI
21
+ - Duplicating [nene-mcp](https://github.com/hideyukiMORI/nene-mcp) (stdio MCP server in PHP)
22
+ - React/Vite starter UI (that remains in NENE2 `frontend/` unless explicitly extracted later)
23
+ - Direct database access from AI tools or SDK code
24
+ - Application-specific business logic (belongs in consumer projects and [NENE2-FT](https://github.com/hideyukiMORI/NENE2-FT) style trials)
25
+
26
+ See [docs/scope.md](docs/scope.md) for the full in-scope / out-of-scope matrix.
27
+
28
+ ## Install (npm)
29
+
30
+ ```bash
31
+ npm install @hideyukimori/nene2-client
32
+ ```
33
+
34
+ Requires **Node 22+** (native `fetch`) or a browser with `fetch`. TypeScript consumers get `.d.ts` from the package.
35
+
36
+ **Monorepo / contributors:** clone this repo and use `npm run check` (builds `dist/` and runs pack smoke).
37
+
38
+ ## Local layout (sibling of NENE2)
39
+
40
+ ```text
41
+ ../docker/
42
+ ├── NENE2/ # PHP framework (contract source: docs/openapi/openapi.yaml)
43
+ ├── nene2-js/ # this repository
44
+ ├── nene-mcp/ # PHP MCP stdio library (separate concern)
45
+ └── NENE2-FT/ # field-trial reference apps
46
+ ```
47
+
48
+ Clone next to your existing NENE2 checkout:
49
+
50
+ ```bash
51
+ cd /path/to/parent-of-NENE2
52
+ git clone git@github.com:hideyukiMORI/nene2-js.git
53
+ cd nene2-js
54
+ npm install
55
+ npm run check
56
+ ```
57
+
58
+ Point local development at a running NENE2 API when needed:
59
+
60
+ ```bash
61
+ cp .env.example .env
62
+ # NENE2_JS_API_BASE_URL=http://localhost:8080
63
+ ```
64
+
65
+ ## Usage (typed client)
66
+
67
+ ```ts
68
+ import { createNene2Client, Nene2ClientError } from '@hideyukimori/nene2-client';
69
+
70
+ const client = createNene2Client({
71
+ baseUrl: 'http://localhost:8080',
72
+ // apiKey: process.env.NENE2_MACHINE_API_KEY,
73
+ // bearer: process.env.NENE2_BEARER_TOKEN,
74
+ });
75
+
76
+ const { health, ping } = await client.smoke();
77
+ const root = await client.frameworkSmoke();
78
+ const notes = await client.listNotes({ limit: 20 });
79
+
80
+ // Load balancers may return 503 with status "degraded" — opt in:
81
+ const degraded = await client.health({ allowDegraded: true });
82
+
83
+ try {
84
+ await client.getNote(1);
85
+ } catch (err) {
86
+ if (err instanceof Nene2ClientError && err.problem) {
87
+ console.error(err.problem.title, err.problem.detail);
88
+ }
89
+ }
90
+ ```
91
+
92
+ Works in Node 22+ and browsers that provide `fetch`.
93
+
94
+ ### Verify the API before live smoke
95
+
96
+ Port `8080` is not always NENE2. Confirm the canonical health shape:
97
+
98
+ ```bash
99
+ curl -sS http://localhost:8080/health | jq .
100
+ # expect: { "status": "ok", "service": "NENE2" }
101
+ ```
102
+
103
+ If you see a different JSON wrapper, point `NENE2_JS_API_BASE_URL` at a running NENE2 PHP instance (sibling `../NENE2`).
104
+
105
+ **Multi-backend live smoke** (same client, OpenAPI contract — see [ADR 0003](docs/adr/0003-multi-backend-live-verification.md)):
106
+
107
+ ```bash
108
+ export NENE2_JS_API_BASE_URL=http://localhost:8080 # NENE2 PHP
109
+ export NENE2_JS_PYTHON_BASE_URL=http://localhost:8000 # optional: nene2-python
110
+ # export NENE2_JS_NODE_BASE_URL=http://localhost:3000 # optional: nene2-node
111
+
112
+ npm test -- tests/client/live-smoke-matrix.test.ts
113
+ ```
114
+
115
+ Unset URLs are skipped; CI runs fixture tests only. Field-trial friction: [ADR 0004](docs/adr/0004-field-trial-friction-resolution-cycle.md).
116
+
117
+ ## Contributing
118
+
119
+ Work is **GitHub Issue driven**. Read [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) and [docs/workflow.md](docs/workflow.md) before opening a PR.
120
+
121
+ AI agents: start at [AGENTS.md](AGENTS.md).
122
+
123
+ Consumer DX evidence: [field trials](docs/field-trials/INDEX.md) (FT1–129; see [methodology](docs/development/field-trials.md)). Quick start: [howto/consume-client.md](docs/howto/consume-client.md).
124
+
125
+ **Release:** [docs/development/publish.md](docs/development/publish.md) · Phase history: [docs/phase-2.md](docs/phase-2.md).
126
+
127
+ ## Related projects
128
+
129
+ | Project | Role |
130
+ | ----------------------------------------------------------------------- | -------------------------------------------------------- |
131
+ | [NENE2](https://github.com/hideyukiMORI/NENE2) | PHP API framework, OpenAPI contract, MCP catalog in-repo |
132
+ | [nene2-node](https://github.com/hideyukiMORI/nene2-node) | Node.js framework port (`@hideyukimori/nene2-framework`) |
133
+ | [nene-mcp](https://github.com/hideyukiMORI/nene-mcp) | Standalone PHP stdio MCP server |
134
+ | [hideyukimori/nene2](https://packagist.org/packages/hideyukimori/nene2) | Composer package for PHP consumers |
135
+
136
+ ## License
137
+
138
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Configuration for {@link createNene2Client}.
3
+ */
4
+ export interface Nene2ClientConfig {
5
+ /** API origin without trailing slash (e.g. `http://localhost:8080`). */
6
+ readonly baseUrl: string;
7
+ /** Machine client API key (`X-NENE2-API-Key`). */
8
+ readonly apiKey?: string | undefined;
9
+ /** Bearer JWT for protected routes. */
10
+ readonly bearer?: string | undefined;
11
+ /** Custom fetch implementation (tests, Node 18 polyfill). */
12
+ readonly fetch?: typeof fetch | undefined;
13
+ /** Optional abort signal forwarded to every request (Persona A: cancel in-flight calls). */
14
+ readonly signal?: AbortSignal | undefined;
15
+ }
16
+ /** @internal */
17
+ export interface ResolvedNene2ClientConfig {
18
+ readonly baseUrl: string;
19
+ readonly apiKey: string | undefined;
20
+ readonly bearer: string | undefined;
21
+ readonly fetch: typeof fetch;
22
+ readonly signal: AbortSignal | undefined;
23
+ }
24
+ /** @internal */
25
+ export declare function resolveConfig(config: Nene2ClientConfig): ResolvedNene2ClientConfig;
26
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/client/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,wEAAwE;IACxE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,kDAAkD;IAClD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,uCAAuC;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,GAAG,SAAS,CAAC;IAC1C,4FAA4F;IAC5F,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CAC3C;AAED,gBAAgB;AAChB,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,CAAC;CAC1C;AAED,gBAAgB;AAChB,wBAAgB,aAAa,CAAC,MAAM,EAAE,iBAAiB,GAAG,yBAAyB,CAgBlF"}
@@ -0,0 +1,19 @@
1
+ /** @internal */
2
+ export function resolveConfig(config) {
3
+ const baseUrl = config.baseUrl.replace(/\/+$/, '');
4
+ if (baseUrl.length === 0) {
5
+ throw new Error('Nene2ClientConfig.baseUrl must not be empty');
6
+ }
7
+ const fetchFn = config.fetch ?? globalThis.fetch;
8
+ if (typeof fetchFn !== 'function') {
9
+ throw new Error('fetch is not available; pass config.fetch');
10
+ }
11
+ return {
12
+ baseUrl,
13
+ apiKey: config.apiKey,
14
+ bearer: config.bearer,
15
+ fetch: fetchFn,
16
+ signal: config.signal,
17
+ };
18
+ }
19
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/client/config.ts"],"names":[],"mappings":"AAyBA,gBAAgB;AAChB,MAAM,UAAU,aAAa,CAAC,MAAyB;IACrD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACnD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IACjE,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC;IACjD,IAAI,OAAO,OAAO,KAAK,UAAU,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC/D,CAAC;IACD,OAAO;QACL,OAAO;QACP,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,KAAK,EAAE,OAAO;QACd,MAAM,EAAE,MAAM,CAAC,MAAM;KACtB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,92 @@
1
+ import { type Nene2ClientConfig } from './config.js';
2
+ import { type CreateNoteRequest, type CreateTagRequest, type ExampleTag, type ExampleTagListResponse, type ListTagsParams, type ExampleNote, type ExampleNoteListResponse, type ListNotesParams, type ProtectedResponse } from '../types/examples/index.js';
3
+ import { type ExamplePingResponse, type FrameworkSmokeResponse, type HealthResponse, type MachineHealthResponse, type SmokeCheckResult } from '../types/system.js';
4
+ export type HealthOptions = {
5
+ /**
6
+ * When true, HTTP 503 with a valid {@link HealthResponse} body (`status: degraded`) is returned instead of throwing.
7
+ * Matches OpenAPI `getHealth` degraded response.
8
+ */
9
+ readonly allowDegraded?: boolean;
10
+ };
11
+ /**
12
+ * Typed NENE2 HTTP client (Phase 2 surface).
13
+ */
14
+ export interface Nene2Client {
15
+ /**
16
+ * `GET /` — framework smoke (OpenAPI `getFrameworkSmoke`).
17
+ */
18
+ frameworkSmoke(): Promise<FrameworkSmokeResponse>;
19
+ /**
20
+ * `GET /health` — operational health (OpenAPI `getHealth`).
21
+ * By default, non-2xx (including 503 degraded) throws {@link Nene2ClientError}.
22
+ */
23
+ health(options?: HealthOptions): Promise<HealthResponse>;
24
+ /**
25
+ * `GET /machine/health` — API-key protected health (OpenAPI `getMachineHealth`). Requires `apiKey`.
26
+ */
27
+ machineHealth(): Promise<MachineHealthResponse>;
28
+ /**
29
+ * `GET /examples/ping` — example scaffold ping (OpenAPI `getExamplePing`).
30
+ */
31
+ ping(): Promise<ExamplePingResponse>;
32
+ /**
33
+ * Parallel `health()` + `ping()` for dashboards (FT2 Persona B: single smoke helper).
34
+ */
35
+ smoke(): Promise<SmokeCheckResult>;
36
+ /**
37
+ * `GET /examples/notes` — paginated list (OpenAPI `listExampleNotes`).
38
+ */
39
+ listNotes(params?: ListNotesParams): Promise<ExampleNoteListResponse>;
40
+ /**
41
+ * `GET /examples/notes/{id}` — single note (OpenAPI `getExampleNoteById`).
42
+ */
43
+ getNote(id: number): Promise<ExampleNote>;
44
+ /**
45
+ * `POST /examples/notes` — create note (OpenAPI `createExampleNote`).
46
+ */
47
+ createNote(body: CreateNoteRequest): Promise<ExampleNote>;
48
+ /**
49
+ * `PUT /examples/notes/{id}` — update note (OpenAPI `updateExampleNoteById`).
50
+ */
51
+ updateNote(id: number, body: CreateNoteRequest): Promise<ExampleNote>;
52
+ /**
53
+ * `DELETE /examples/notes/{id}` — delete note (OpenAPI `deleteExampleNoteById`, 204).
54
+ */
55
+ deleteNote(id: number): Promise<void>;
56
+ /**
57
+ * `GET /examples/tags` — paginated list (OpenAPI `listExampleTags`).
58
+ */
59
+ listTags(params?: ListTagsParams): Promise<ExampleTagListResponse>;
60
+ /**
61
+ * `GET /examples/tags/{id}` — single tag (OpenAPI `getExampleTagById`).
62
+ */
63
+ getTag(id: number): Promise<ExampleTag>;
64
+ /**
65
+ * `POST /examples/tags` — create tag (OpenAPI `createExampleTag`).
66
+ */
67
+ createTag(body: CreateTagRequest): Promise<ExampleTag>;
68
+ /**
69
+ * `PUT /examples/tags/{id}` — update tag (OpenAPI `updateExampleTagById`).
70
+ */
71
+ updateTag(id: number, body: CreateTagRequest): Promise<ExampleTag>;
72
+ /**
73
+ * `DELETE /examples/tags/{id}` — delete tag (OpenAPI `deleteExampleTagById`, 204).
74
+ */
75
+ deleteTag(id: number): Promise<void>;
76
+ /**
77
+ * `GET /examples/protected` — JWT claims demo (OpenAPI `getProtected`). Requires `bearer` in config.
78
+ */
79
+ getProtected(): Promise<ProtectedResponse>;
80
+ }
81
+ /**
82
+ * Create a typed client for documented NENE2 system endpoints.
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * const client = createNene2Client({ baseUrl: 'http://localhost:8080' });
87
+ * const health = await client.health();
88
+ * const notes = await client.listNotes({ limit: 10 });
89
+ * ```
90
+ */
91
+ export declare function createNene2Client(config: Nene2ClientConfig): Nene2Client;
92
+ //# sourceMappingURL=create-nene2-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-nene2-client.d.ts","sourceRoot":"","sources":["../../src/client/create-nene2-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGpE,OAAO,EAML,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,uBAAuB,EAC5B,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACvB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAKL,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,MAAM,aAAa,GAAG;IAC1B;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;CAClC,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,cAAc,IAAI,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAElD;;;OAGG;IACH,MAAM,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAEzD;;OAEG;IACH,aAAa,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAEhD;;OAEG;IACH,IAAI,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAErC;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAEnC;;OAEG;IACH,SAAS,CAAC,MAAM,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAEtE;;OAEG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAE1C;;OAEG;IACH,UAAU,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAE1D;;OAEG;IACH,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAEtE;;OAEG;IACH,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtC;;OAEG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAEnE;;OAEG;IACH,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAExC;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAEvD;;OAEG;IACH,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAEnE;;OAEG;IACH,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAErC;;OAEG;IACH,YAAY,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAC;CAC5C;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,WAAW,CAkDxE"}
@@ -0,0 +1,54 @@
1
+ import { resolveConfig } from './config.js';
2
+ import { withQuery } from './path.js';
3
+ import { deleteNoContent, getJson, postJson, putJson } from './request.js';
4
+ import { isExampleNoteListResponse, isExampleNoteResponse, isProtectedResponse, isExampleTagListResponse, isExampleTagResponse, } from '../types/examples/index.js';
5
+ import { isExamplePingResponse, isFrameworkSmokeResponse, isHealthResponse, isMachineHealthResponse, } from '../types/system.js';
6
+ /**
7
+ * Create a typed client for documented NENE2 system endpoints.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const client = createNene2Client({ baseUrl: 'http://localhost:8080' });
12
+ * const health = await client.health();
13
+ * const notes = await client.listNotes({ limit: 10 });
14
+ * ```
15
+ */
16
+ export function createNene2Client(config) {
17
+ const resolved = resolveConfig(config);
18
+ return {
19
+ frameworkSmoke: () => getJson(resolved, '/', isFrameworkSmokeResponse),
20
+ health: (options) => {
21
+ if (options?.allowDegraded) {
22
+ return getJson(resolved, '/health', isHealthResponse, { alsoOkStatuses: [503] });
23
+ }
24
+ return getJson(resolved, '/health', isHealthResponse);
25
+ },
26
+ machineHealth: () => getJson(resolved, '/machine/health', isMachineHealthResponse),
27
+ ping: () => getJson(resolved, '/examples/ping', isExamplePingResponse),
28
+ smoke: async () => {
29
+ const [health, ping] = await Promise.all([
30
+ getJson(resolved, '/health', isHealthResponse),
31
+ getJson(resolved, '/examples/ping', isExamplePingResponse),
32
+ ]);
33
+ return { health, ping };
34
+ },
35
+ listNotes: (params) => getJson(resolved, withQuery('/examples/notes', {
36
+ limit: params?.limit,
37
+ offset: params?.offset,
38
+ }), isExampleNoteListResponse),
39
+ getNote: (id) => getJson(resolved, `/examples/notes/${String(id)}`, isExampleNoteResponse),
40
+ createNote: (body) => postJson(resolved, '/examples/notes', body, isExampleNoteResponse),
41
+ updateNote: (id, body) => putJson(resolved, `/examples/notes/${String(id)}`, body, isExampleNoteResponse),
42
+ deleteNote: (id) => deleteNoContent(resolved, `/examples/notes/${String(id)}`),
43
+ listTags: (params) => getJson(resolved, withQuery('/examples/tags', {
44
+ limit: params?.limit,
45
+ offset: params?.offset,
46
+ }), isExampleTagListResponse),
47
+ getTag: (id) => getJson(resolved, `/examples/tags/${String(id)}`, isExampleTagResponse),
48
+ createTag: (body) => postJson(resolved, '/examples/tags', body, isExampleTagResponse),
49
+ updateTag: (id, body) => putJson(resolved, `/examples/tags/${String(id)}`, body, isExampleTagResponse),
50
+ deleteTag: (id) => deleteNoContent(resolved, `/examples/tags/${String(id)}`),
51
+ getProtected: () => getJson(resolved, '/examples/protected', isProtectedResponse),
52
+ };
53
+ }
54
+ //# sourceMappingURL=create-nene2-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-nene2-client.js","sourceRoot":"","sources":["../../src/client/create-nene2-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { ProblemDetailsDocument } from '../problem/types.js';
2
+ /**
3
+ * Error thrown when the NENE2 API returns a non-success HTTP status or an unexpected body.
4
+ */
5
+ /** Type guard for {@link Nene2ClientError}. */
6
+ export declare function isNene2ClientError(error: unknown): error is Nene2ClientError;
7
+ export declare class Nene2ClientError extends Error {
8
+ readonly status: number;
9
+ readonly problem: ProblemDetailsDocument | undefined;
10
+ readonly url: string;
11
+ constructor(message: string, options: {
12
+ status: number;
13
+ url: string;
14
+ problem?: ProblemDetailsDocument | undefined;
15
+ });
16
+ }
17
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/client/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAElE;;GAEG;AACH,+CAA+C;AAC/C,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,gBAAgB,CAE5E;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,sBAAsB,GAAG,SAAS,CAAC;IACrD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;gBAGnB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG,SAAS,CAAA;KAAE;CAQzF"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Error thrown when the NENE2 API returns a non-success HTTP status or an unexpected body.
3
+ */
4
+ /** Type guard for {@link Nene2ClientError}. */
5
+ export function isNene2ClientError(error) {
6
+ return error instanceof Nene2ClientError;
7
+ }
8
+ export class Nene2ClientError extends Error {
9
+ status;
10
+ problem;
11
+ url;
12
+ constructor(message, options) {
13
+ super(message);
14
+ this.name = 'Nene2ClientError';
15
+ this.status = options.status;
16
+ this.url = options.url;
17
+ this.problem = options.problem;
18
+ }
19
+ }
20
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/client/errors.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,+CAA+C;AAC/C,MAAM,UAAU,kBAAkB,CAAC,KAAc;IAC/C,OAAO,KAAK,YAAY,gBAAgB,CAAC;AAC3C,CAAC;AAED,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAChC,MAAM,CAAS;IACf,OAAO,CAAqC;IAC5C,GAAG,CAAS;IAErB,YACE,OAAe,EACf,OAAsF;QAEtF,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAC/B,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;QACvB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IACjC,CAAC;CACF"}
@@ -0,0 +1,5 @@
1
+ export { createNene2Client, type Nene2Client, type HealthOptions } from './create-nene2-client.js';
2
+ export { isNene2ClientError, Nene2ClientError } from './errors.js';
3
+ export type { Nene2ClientConfig } from './config.js';
4
+ export { validationErrorsByField, validationErrorsFromClientError } from './validation-errors.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,KAAK,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACnG,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACnE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,uBAAuB,EAAE,+BAA+B,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { createNene2Client } from './create-nene2-client.js';
2
+ export { isNene2ClientError, Nene2ClientError } from './errors.js';
3
+ export { validationErrorsByField, validationErrorsFromClientError } from './validation-errors.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAwC,MAAM,0BAA0B,CAAC;AACnG,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEnE,OAAO,EAAE,uBAAuB,EAAE,+BAA+B,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Append optional query parameters to a path (no leading `?` duplication).
3
+ */
4
+ export declare function withQuery(path: string, query?: Readonly<Record<string, number | string | undefined>>): string;
5
+ //# sourceMappingURL=path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/client/path.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC,GAC5D,MAAM,CAYR"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Append optional query parameters to a path (no leading `?` duplication).
3
+ */
4
+ export function withQuery(path, query) {
5
+ if (query === undefined) {
6
+ return path;
7
+ }
8
+ const params = new URLSearchParams();
9
+ for (const [key, value] of Object.entries(query)) {
10
+ if (value !== undefined) {
11
+ params.set(key, String(value));
12
+ }
13
+ }
14
+ const qs = params.toString();
15
+ return qs.length > 0 ? `${path}?${qs}` : path;
16
+ }
17
+ //# sourceMappingURL=path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.js","sourceRoot":"","sources":["../../src/client/path.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,UAAU,SAAS,CACvB,IAAY,EACZ,KAA6D;IAE7D,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;IACrC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IACD,MAAM,EAAE,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;IAC7B,OAAO,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAChD,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { ResolvedNene2ClientConfig } from './config.js';
2
+ export declare function buildAuthHeaders(config: ResolvedNene2ClientConfig): Headers;
3
+ export type JsonRequestOptions = {
4
+ /** HTTP status codes to treat as success (e.g. 503 degraded health). */
5
+ readonly alsoOkStatuses?: readonly number[];
6
+ };
7
+ /**
8
+ * GET JSON and validate the body with a type guard. Throws {@link Nene2ClientError} on failure.
9
+ */
10
+ export declare function getJson<T>(config: ResolvedNene2ClientConfig, path: string, isValid: (value: unknown) => value is T, options?: JsonRequestOptions): Promise<T>;
11
+ /**
12
+ * POST JSON and validate the response body. Throws {@link Nene2ClientError} on failure.
13
+ */
14
+ export declare function postJson<T>(config: ResolvedNene2ClientConfig, path: string, payload: unknown, isValid: (value: unknown) => value is T, options?: JsonRequestOptions): Promise<T>;
15
+ /**
16
+ * PUT JSON and validate the response body. Throws {@link Nene2ClientError} on failure.
17
+ */
18
+ export declare function putJson<T>(config: ResolvedNene2ClientConfig, path: string, payload: unknown, isValid: (value: unknown) => value is T, options?: JsonRequestOptions): Promise<T>;
19
+ /**
20
+ * DELETE with no response body (204). Throws {@link Nene2ClientError} on failure.
21
+ */
22
+ export declare function deleteNoContent(config: ResolvedNene2ClientConfig, path: string, options?: JsonRequestOptions): Promise<void>;
23
+ //# sourceMappingURL=request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/client/request.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAS7D,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,yBAAyB,GAAG,OAAO,CAS3E;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,wEAAwE;IACxE,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7C,CAAC;AAwDF;;GAEG;AACH,wBAAsB,OAAO,CAAC,CAAC,EAC7B,MAAM,EAAE,yBAAyB,EACjC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC,EACvC,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,CAAC,CAAC,CASZ;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAC9B,MAAM,EAAE,yBAAyB,EACjC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC,EACvC,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,CAAC,CAAC,CAWZ;AAED;;GAEG;AACH,wBAAsB,OAAO,CAAC,CAAC,EAC7B,MAAM,EAAE,yBAAyB,EACjC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC,EACvC,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,CAAC,CAAC,CAWZ;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,yBAAyB,EACjC,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAQf"}
@@ -0,0 +1,99 @@
1
+ import { parseProblemDetailsResponse } from '../problem/guards.js';
2
+ import { Nene2ClientError } from './errors.js';
3
+ function withSignal(config, init) {
4
+ if (config.signal === undefined) {
5
+ return init;
6
+ }
7
+ return { ...init, signal: config.signal };
8
+ }
9
+ export function buildAuthHeaders(config) {
10
+ const headers = new Headers({ Accept: 'application/json' });
11
+ if (config.apiKey !== undefined) {
12
+ headers.set('X-NENE2-API-Key', config.apiKey);
13
+ }
14
+ if (config.bearer !== undefined) {
15
+ headers.set('Authorization', `Bearer ${config.bearer}`);
16
+ }
17
+ return headers;
18
+ }
19
+ function isSuccessStatus(response, options) {
20
+ if (response.ok) {
21
+ return true;
22
+ }
23
+ return options?.alsoOkStatuses?.includes(response.status) ?? false;
24
+ }
25
+ async function parseJsonBody(response, url, isValid) {
26
+ const contentType = response.headers.get('content-type') ?? '';
27
+ let body;
28
+ try {
29
+ body = await response.json();
30
+ }
31
+ catch {
32
+ const hint = contentType.includes('text/html') || contentType.includes('text/plain')
33
+ ? ' — response looks like HTML/text; check baseUrl points at NENE2 JSON API'
34
+ : '';
35
+ throw new Nene2ClientError(`NENE2 response is not valid JSON${hint}`, {
36
+ status: response.status,
37
+ url,
38
+ });
39
+ }
40
+ if (!isValid(body)) {
41
+ throw new Nene2ClientError('NENE2 response body does not match expected shape', {
42
+ status: response.status,
43
+ url,
44
+ });
45
+ }
46
+ return body;
47
+ }
48
+ async function throwOnErrorResponse(response, url, options) {
49
+ if (isSuccessStatus(response, options)) {
50
+ return;
51
+ }
52
+ const problem = await parseProblemDetailsResponse(response);
53
+ const detail = problem?.detail ?? problem?.title ?? response.statusText;
54
+ throw new Nene2ClientError(`NENE2 request failed: ${detail}`, {
55
+ status: response.status,
56
+ url,
57
+ problem,
58
+ });
59
+ }
60
+ /**
61
+ * GET JSON and validate the body with a type guard. Throws {@link Nene2ClientError} on failure.
62
+ */
63
+ export async function getJson(config, path, isValid, options) {
64
+ const url = `${config.baseUrl}${path}`;
65
+ const response = await config.fetch(url, withSignal(config, { method: 'GET', headers: buildAuthHeaders(config) }));
66
+ await throwOnErrorResponse(response, url, options);
67
+ return parseJsonBody(response, url, isValid);
68
+ }
69
+ /**
70
+ * POST JSON and validate the response body. Throws {@link Nene2ClientError} on failure.
71
+ */
72
+ export async function postJson(config, path, payload, isValid, options) {
73
+ const url = `${config.baseUrl}${path}`;
74
+ const headers = buildAuthHeaders(config);
75
+ headers.set('Content-Type', 'application/json');
76
+ const response = await config.fetch(url, withSignal(config, { method: 'POST', headers, body: JSON.stringify(payload) }));
77
+ await throwOnErrorResponse(response, url, options);
78
+ return parseJsonBody(response, url, isValid);
79
+ }
80
+ /**
81
+ * PUT JSON and validate the response body. Throws {@link Nene2ClientError} on failure.
82
+ */
83
+ export async function putJson(config, path, payload, isValid, options) {
84
+ const url = `${config.baseUrl}${path}`;
85
+ const headers = buildAuthHeaders(config);
86
+ headers.set('Content-Type', 'application/json');
87
+ const response = await config.fetch(url, withSignal(config, { method: 'PUT', headers, body: JSON.stringify(payload) }));
88
+ await throwOnErrorResponse(response, url, options);
89
+ return parseJsonBody(response, url, isValid);
90
+ }
91
+ /**
92
+ * DELETE with no response body (204). Throws {@link Nene2ClientError} on failure.
93
+ */
94
+ export async function deleteNoContent(config, path, options) {
95
+ const url = `${config.baseUrl}${path}`;
96
+ const response = await config.fetch(url, withSignal(config, { method: 'DELETE', headers: buildAuthHeaders(config) }));
97
+ await throwOnErrorResponse(response, url, options);
98
+ }
99
+ //# sourceMappingURL=request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.js","sourceRoot":"","sources":["../../src/client/request.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { ValidationError } from '../problem/types.js';
2
+ /**
3
+ * Extract RFC 9457 validation `errors` from a {@link Nene2ClientError} when present.
4
+ * Useful for mapping API validation failures to form fields (Persona A DX).
5
+ */
6
+ export declare function validationErrorsFromClientError(error: unknown): readonly ValidationError[] | undefined;
7
+ /**
8
+ * Map validation errors to a plain object keyed by field name (first error per field).
9
+ */
10
+ export declare function validationErrorsByField(errors: readonly ValidationError[]): Readonly<Record<string, string>>;
11
+ //# sourceMappingURL=validation-errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation-errors.d.ts","sourceRoot":"","sources":["../../src/client/validation-errors.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAG3D;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,KAAK,EAAE,OAAO,GACb,SAAS,eAAe,EAAE,GAAG,SAAS,CAQxC;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,SAAS,eAAe,EAAE,GACjC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAQlC"}