@w6w/sdk 0.3.0 → 0.4.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 (84) hide show
  1. package/dist/src/client.d.ts +28 -0
  2. package/dist/src/client.d.ts.map +1 -1
  3. package/dist/src/client.js +29 -0
  4. package/dist/src/client.js.map +1 -1
  5. package/dist/src/console/api-calls.d.ts +80 -0
  6. package/dist/src/console/api-calls.d.ts.map +1 -0
  7. package/dist/src/console/api-calls.js +69 -0
  8. package/dist/src/console/api-calls.js.map +1 -0
  9. package/dist/src/console/apps.d.ts +689 -0
  10. package/dist/src/console/apps.d.ts.map +1 -0
  11. package/dist/src/console/apps.js +402 -0
  12. package/dist/src/console/apps.js.map +1 -0
  13. package/dist/src/console/auth.d.ts +241 -0
  14. package/dist/src/console/auth.d.ts.map +1 -0
  15. package/dist/src/console/auth.js +173 -0
  16. package/dist/src/console/auth.js.map +1 -0
  17. package/dist/src/console/connections.d.ts +188 -0
  18. package/dist/src/console/connections.d.ts.map +1 -0
  19. package/dist/src/console/connections.js +182 -0
  20. package/dist/src/console/connections.js.map +1 -0
  21. package/dist/src/console/dashboard.d.ts +102 -0
  22. package/dist/src/console/dashboard.d.ts.map +1 -0
  23. package/dist/src/console/dashboard.js +59 -0
  24. package/dist/src/console/dashboard.js.map +1 -0
  25. package/dist/src/console/endpoints.d.ts +264 -0
  26. package/dist/src/console/endpoints.d.ts.map +1 -0
  27. package/dist/src/console/endpoints.js +164 -0
  28. package/dist/src/console/endpoints.js.map +1 -0
  29. package/dist/src/console/functions.d.ts +165 -0
  30. package/dist/src/console/functions.d.ts.map +1 -0
  31. package/dist/src/console/functions.js +117 -0
  32. package/dist/src/console/functions.js.map +1 -0
  33. package/dist/src/console/mod.d.ts +131 -0
  34. package/dist/src/console/mod.d.ts.map +1 -0
  35. package/dist/src/console/mod.js +121 -0
  36. package/dist/src/console/mod.js.map +1 -0
  37. package/dist/src/console/projects.d.ts +107 -0
  38. package/dist/src/console/projects.d.ts.map +1 -0
  39. package/dist/src/console/projects.js +97 -0
  40. package/dist/src/console/projects.js.map +1 -0
  41. package/dist/src/console/reliability.d.ts +149 -0
  42. package/dist/src/console/reliability.d.ts.map +1 -0
  43. package/dist/src/console/reliability.js +56 -0
  44. package/dist/src/console/reliability.js.map +1 -0
  45. package/dist/src/console/saved-tests.d.ts +198 -0
  46. package/dist/src/console/saved-tests.d.ts.map +1 -0
  47. package/dist/src/console/saved-tests.js +172 -0
  48. package/dist/src/console/saved-tests.js.map +1 -0
  49. package/dist/src/console/schedules.d.ts +127 -0
  50. package/dist/src/console/schedules.d.ts.map +1 -0
  51. package/dist/src/console/schedules.js +118 -0
  52. package/dist/src/console/schedules.js.map +1 -0
  53. package/dist/src/console/step-tests.d.ts +159 -0
  54. package/dist/src/console/step-tests.d.ts.map +1 -0
  55. package/dist/src/console/step-tests.js +129 -0
  56. package/dist/src/console/step-tests.js.map +1 -0
  57. package/dist/src/console/subscriptions.d.ts +199 -0
  58. package/dist/src/console/subscriptions.d.ts.map +1 -0
  59. package/dist/src/console/subscriptions.js +191 -0
  60. package/dist/src/console/subscriptions.js.map +1 -0
  61. package/dist/src/console/tenant-oauth-apps.d.ts +188 -0
  62. package/dist/src/console/tenant-oauth-apps.d.ts.map +1 -0
  63. package/dist/src/console/tenant-oauth-apps.js +152 -0
  64. package/dist/src/console/tenant-oauth-apps.js.map +1 -0
  65. package/dist/src/console/tokens.d.ts +159 -0
  66. package/dist/src/console/tokens.d.ts.map +1 -0
  67. package/dist/src/console/tokens.js +145 -0
  68. package/dist/src/console/tokens.js.map +1 -0
  69. package/dist/src/console/vault.d.ts +188 -0
  70. package/dist/src/console/vault.d.ts.map +1 -0
  71. package/dist/src/console/vault.js +164 -0
  72. package/dist/src/console/vault.js.map +1 -0
  73. package/dist/src/console/workflows.d.ts +243 -0
  74. package/dist/src/console/workflows.d.ts.map +1 -0
  75. package/dist/src/console/workflows.js +205 -0
  76. package/dist/src/console/workflows.js.map +1 -0
  77. package/dist/src/http.d.ts +32 -2
  78. package/dist/src/http.d.ts.map +1 -1
  79. package/dist/src/http.js +11 -2
  80. package/dist/src/http.js.map +1 -1
  81. package/dist/src/types.d.ts +2 -2
  82. package/dist/src/types.d.ts.map +1 -1
  83. package/dist/src/version.js +1 -1
  84. package/package.json +5 -1
@@ -0,0 +1,164 @@
1
+ /**
2
+ * `client.console.vault.*` — the write-only secret store (list, get, create,
3
+ * update, delete, seal), relocated from the studio's own API client.
4
+ *
5
+ * **Studio-internal, not the published partner contract.** This namespace
6
+ * lives under the `console` subpath export (`@w6w/sdk/console`), excluded from
7
+ * `endpoints.json` and from the root barrel (`mod.ts`) — see `docs/console.md`
8
+ * and HITL-1 in this task's contract. `@w6w/sdk`'s instance-state mechanism
9
+ * pin still applies here exactly as it does to every other namespace
10
+ * (`docs/implementation.md` §MECHANISM PIN — instance state, never globals):
11
+ * this class holds no state of its own beyond the injected host, so two
12
+ * clients in one process never share a credential.
13
+ *
14
+ * **Write-only, no-plaintext-in-responses is the invariant this whole domain
15
+ * rests on** (`packages/server/packages/api/admin/vault.ts:1-21`'s own header
16
+ * comment: "plaintext values NEVER appear in a response"). `list`/`get` never
17
+ * carry `value` — by the server's wire contract itself, not by client
18
+ * discipline — which is why {@linkcode VaultSecretSummary} has no `value`
19
+ * field. `create`/`update` forward `value` verbatim to *write* it, and it is
20
+ * simply never echoed back. `seal` is the one deliberate exception, and even
21
+ * there it never returns the plaintext: it answers with a ciphertext envelope
22
+ * (`SecretValue`) — an encrypt-only oracle with no matching decrypt route, so
23
+ * an authed caller learns nothing they didn't already provide.
24
+ *
25
+ * Six methods relocate from the studio's own `// Vault` comment block
26
+ * (`packages/studio/src/api/client.ts:257-278` on the current merged tree) —
27
+ * field-for-field, not redesigned.
28
+ *
29
+ * `value` is opaque — forwarded verbatim on `create`/`update`/`seal`, never
30
+ * inspected, logged, or transformed. This is a security-adjacent discipline,
31
+ * not a style note: the whole point of this field is a caller-opaque secret
32
+ * payload.
33
+ *
34
+ * None of these methods take a `project` scoping parameter — vault secrets
35
+ * are subject-scoped like `vars`, not project-scoped — so `VaultHost` needs
36
+ * only the transport, mirroring `VarsHost`/`ConnectionsHost`, not
37
+ * `DocumentsHost`.
38
+ *
39
+ * @module
40
+ */
41
+ import { path } from "../http.js";
42
+ import { unwrap } from "../types.js";
43
+ /**
44
+ * The `console.vault` namespace on a `W6wClient`.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * const secrets = await client.console.vault.list();
49
+ * const created = await client.console.vault.create({ name: "openai_key", value: "sk-…" });
50
+ * await client.console.vault.update(created.id, { description: "Prod key" });
51
+ * const sealed = await client.console.vault.seal("sk-live-…");
52
+ * await client.console.vault.delete(created.id);
53
+ * ```
54
+ */
55
+ export class VaultApi {
56
+ #host;
57
+ /**
58
+ * @param host - The client this namespace issues requests through.
59
+ */
60
+ constructor(host) {
61
+ this.#host = host;
62
+ }
63
+ /**
64
+ * List the account's vault secrets.
65
+ *
66
+ * @returns The secrets, unwrapped from the `secrets` envelope. Never
67
+ * carries `value`.
68
+ */
69
+ async list() {
70
+ const res = await this.#host.request({ method: "GET", path: "/vault" });
71
+ return unwrap(res, "secrets");
72
+ }
73
+ /**
74
+ * Fetch one secret's metadata.
75
+ *
76
+ * **Zero studio callers today** — built for SDK completeness only. No 404
77
+ * special-casing — propagates as `ApiError` like every other "get" in this
78
+ * package except `console.schedules.get`, which has its own documented,
79
+ * unrelated exception.
80
+ *
81
+ * @param id - The `sec_…` id.
82
+ * @returns The secret, unwrapped from the `secret` envelope. Never carries `value`.
83
+ * @throws {ApiError} `404 unknown_secret` when there is no such id.
84
+ */
85
+ async get(id) {
86
+ const res = await this.#host.request({ method: "GET", path: path `/vault/${id}` });
87
+ return unwrap(res, "secret");
88
+ }
89
+ /**
90
+ * Create a secret.
91
+ *
92
+ * `value` is opaque — forwarded verbatim, never inspected, logged, or
93
+ * transformed.
94
+ *
95
+ * @param body - `name` (unique, matches `^[a-z_][a-z0-9_]*$`), `value` (the
96
+ * plaintext to store), and an optional `description`.
97
+ * @returns The created secret, unwrapped from the `secret` envelope (the
98
+ * server answers `201`). Never carries `value`.
99
+ * @throws {ApiError} `400 invalid_name`/`invalid_value`/`invalid_description`; `409 secret_exists`.
100
+ */
101
+ async create(body) {
102
+ const res = await this.#host.request({ method: "POST", path: "/vault", body });
103
+ return unwrap(res, "secret");
104
+ }
105
+ /**
106
+ * Update a secret: replace its value and/or its description.
107
+ *
108
+ * `value` is opaque — forwarded verbatim, never inspected, logged, or
109
+ * transformed, same discipline as {@linkcode create}.
110
+ *
111
+ * @param id - The `sec_…` id.
112
+ * @param body - `value` and/or `description`, forwarded verbatim.
113
+ * @returns The updated secret, unwrapped from the `secret` envelope. Never carries `value`.
114
+ * @throws {ApiError} `400 invalid_value`/`invalid_description`; `404 unknown_secret`.
115
+ */
116
+ async update(id, body) {
117
+ const res = await this.#host.request({
118
+ method: "PATCH",
119
+ path: path `/vault/${id}`,
120
+ body,
121
+ });
122
+ return unwrap(res, "secret");
123
+ }
124
+ /**
125
+ * Delete a secret.
126
+ *
127
+ * Returns nothing: the server's `{ok: true}` carries no information a
128
+ * caller can use, mirroring `console.projects.delete`/
129
+ * `console.schedules.delete`/`console.connections.delete` in this same
130
+ * package (NOT `console.apps.delete`, which is the one documented
131
+ * exception) — vault delete has no such value to preserve.
132
+ *
133
+ * @param id - The `sec_…` id.
134
+ * @throws {ApiError} `404 unknown_secret` when there is no such id.
135
+ */
136
+ async delete(id) {
137
+ await this.#host.request({ method: "DELETE", path: path `/vault/${id}` });
138
+ }
139
+ /**
140
+ * Encrypt an ad-hoc secret value into an at-rest ciphertext envelope,
141
+ * without storing it.
142
+ *
143
+ * **An encrypt-only oracle — there is no matching decrypt route.** The
144
+ * server's own header comment states the security property directly: "an
145
+ * authed caller learns nothing they didn't provide." `value` is opaque —
146
+ * forwarded verbatim, never inspected, logged, or transformed. **No
147
+ * envelope key to peel on the request side** — the body IS `{ value }` —
148
+ * but the *response* IS enveloped under `sealed`, unlike `console.
149
+ * connections.test`'s/`console.reliability.list`'s whole-body responses.
150
+ *
151
+ * @param value - The plaintext to seal. Never logged, never echoed back.
152
+ * @returns The ciphertext envelope, unwrapped from the `sealed` envelope.
153
+ * @throws {ApiError} `400 invalid_value` when `value` is empty.
154
+ */
155
+ async seal(value) {
156
+ const res = await this.#host.request({
157
+ method: "POST",
158
+ path: "/vault/seal",
159
+ body: { value },
160
+ });
161
+ return unwrap(res, "sealed");
162
+ }
163
+ }
164
+ //# sourceMappingURL=vault.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault.js","sourceRoot":"","sources":["../../../src/console/vault.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,EAAqB,IAAI,EAAuB,MAAM,YAAY,CAAC;AAC1E,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAkDrC;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,QAAQ;IACV,KAAK,CAAY;IAE1B;;OAEG;IACH,YAAY,IAAe;QACzB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACpB,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,IAAI;QACR,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAU,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QACjF,OAAO,MAAM,CAAuB,GAAG,EAAE,SAAS,CAAC,CAAC;IACtD,CAAC;IAED;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,GAAG,CAAC,EAAU;QAClB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAU,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAA,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC;QAC3F,OAAO,MAAM,CAAqB,GAAG,EAAE,QAAQ,CAAC,CAAC;IACnD,CAAC;IAED;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,MAAM,CACV,IAA2D;QAE3D,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAU,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QACxF,OAAO,MAAM,CAAqB,GAAG,EAAE,QAAQ,CAAC,CAAC;IACnD,CAAC;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,MAAM,CACV,EAAU,EACV,IAA8C;QAE9C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAU;YAC5C,MAAM,EAAE,OAAO;YACf,IAAI,EAAE,IAAI,CAAA,UAAU,EAAE,EAAE;YACxB,IAAI;SACL,CAAC,CAAC;QACH,OAAO,MAAM,CAAqB,GAAG,EAAE,QAAQ,CAAC,CAAC;IACnD,CAAC;IAED;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,MAAM,CAAC,EAAU;QACrB,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAA,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC;IACpF,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACH,KAAK,CAAC,IAAI,CAAC,KAAa;QACtB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAU;YAC5C,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,aAAa;YACnB,IAAI,EAAE,EAAE,KAAK,EAAE;SAChB,CAAC,CAAC;QACH,OAAO,MAAM,CAAc,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC5C,CAAC;CACF"}
@@ -0,0 +1,243 @@
1
+ /**
2
+ * `client.console.workflows.*` — the workflow definition/lifecycle and run
3
+ * surface (`get`, `upsert`, `archive`, `delete`, `listRuns`, `getRun`),
4
+ * relocated from the studio's own API client.
5
+ *
6
+ * **Studio-internal, not the published partner contract.** This namespace
7
+ * lives under the `console` subpath export (`@w6w/sdk/console`), excluded from
8
+ * `endpoints.json` and from the root barrel (`mod.ts`) — see `docs/console.md`
9
+ * and HITL-1 in this task's contract. `@w6w/sdk`'s instance-state mechanism
10
+ * pin still applies here exactly as it does to every other namespace
11
+ * (`docs/implementation.md` §MECHANISM PIN — instance state, never globals):
12
+ * this class holds no state of its own beyond the injected host, so two
13
+ * clients in one process never share a credential.
14
+ *
15
+ * **Console placement is the same precedent as `console.connections`'s
16
+ * `HITL-10`, not a fresh judgment call — see `HITL.md` and `plan.md`'s SP2.3
17
+ * section for the full investigation.** `endpoints.json`'s `outOfScope` array
18
+ * names "all write operations on connections and workflows" together, with the
19
+ * identical "in this version" framing HITL-10 already resolved for
20
+ * connections. The server routes here (`admin/workflows.ts`) are, like
21
+ * connections, plain stateless handlers over an already-resolved request
22
+ * body/precondition header — no interactive flow the SDK method itself would
23
+ * need to conduct. Console placement is safe for studio's own use; it does not
24
+ * by itself justify promotion to the partner surface (cli/python lockstep; no
25
+ * documented partner story exists yet either) — that promotion, if it ever
26
+ * happens, is a dedicated future project.
27
+ *
28
+ * **`list` and `run` are NOT part of this domain.** Both already exist on the
29
+ * BASE namespace (`client.workflows.list()`/`client.workflows.run()`,
30
+ * `../workflows.ts`) and studio reuses them verbatim — adding a duplicate
31
+ * `list`/`run` under `console.workflows` would give a caller two ways to reach
32
+ * the same route, exactly the anti-duplication defect this project's own
33
+ * rules flag. **No poll loop lives here either**: `../workflows.ts`'s own
34
+ * module doc pins "no client-side polling" for the whole package, and
35
+ * `getRun` below is deliberately a single request with no retry — the caller
36
+ * (studio's `lib/workflow-poll.ts`) owns the loop.
37
+ *
38
+ * Five of six methods relocate from the studio's `// Workflows` comment block
39
+ * (`packages/studio/src/api/client.ts:254-331`): `getWorkflow`→`get`,
40
+ * `createWorkflow`→`upsert`, `archiveWorkflow`→`archive`,
41
+ * `deleteWorkflow`→`delete`, `listRuns`→`listRuns`. `getRun` is genuinely NEW
42
+ * here — `client.ts`'s own `getRun` method is dead code today (zero callers),
43
+ * but this task gives it a real consumer: `runWorkflow`'s poll loop, ported
44
+ * into studio's `lib/workflow-poll.ts`, calls it.
45
+ *
46
+ * None of the six methods take a client-side default-project fallback — they
47
+ * are addressed by `wf_…`/`run_…` id (plus an optional explicit `?project=`
48
+ * on `upsert`), so `WorkflowsHost` needs only the transport, mirroring
49
+ * `SchedulesHost`/`ProjectsHost`, not the BASE `WorkflowsHost` (`../workflows.ts`),
50
+ * which additionally reads `config.project` as `list()`'s default.
51
+ *
52
+ * @module
53
+ */
54
+ import { type HttpResponse, type RequestOptions } from "../http.ts";
55
+ import { type RunStatus, type StepError } from "../types.ts";
56
+ /**
57
+ * The slice of `W6wClient` this namespace needs: the transport, and nothing
58
+ * else. Structural rather than a concrete client type, so the namespace stays
59
+ * independently constructible in a test and this module never imports the
60
+ * client back — mirrors `SchedulesHost` in `./schedules.ts`.
61
+ */
62
+ export interface WorkflowsHost {
63
+ /** Perform one request. */
64
+ request<T>(options: RequestOptions): Promise<HttpResponse<T>>;
65
+ }
66
+ /**
67
+ * A workflow run's full state, as `GET /runs/:id` returns it.
68
+ *
69
+ * Field-for-field, relocated from `packages/studio/src/api/types.ts:84-102`,
70
+ * with ONE deliberate deviation from studio's local shape: `error` and `steps`
71
+ * stay OPAQUE (`unknown`/`Record<string, unknown>`), matching this package's
72
+ * own documented philosophy for `RunResult` (`../types.ts` — "a client that
73
+ * modelled their internals would be wrong for someone"). Studio's local type
74
+ * typed `error` as a structured `{code,message,…}` and `steps` as
75
+ * `Record<string, StepExecution>`; that richer typing does not carry over
76
+ * here, on purpose, for consistency with every other run-shaped type in this
77
+ * package.
78
+ *
79
+ * Defined LOCALLY in this file, not in the shared `../types.ts`: it is
80
+ * consumed only by console methods, never by base `run()`/`list()`, mirroring
81
+ * where `Schedule`/`Project` live (inside their own `console/*.ts` files).
82
+ */
83
+ export interface RunState {
84
+ /** Server-issued run handle, `run_…`. */
85
+ runId: string;
86
+ /** The workflow this run belongs to. */
87
+ workflowId: string;
88
+ /** Where the run has got to. */
89
+ status: RunStatus;
90
+ /** Variables the run was started with. Opaque pass-through. */
91
+ variables: Record<string, unknown>;
92
+ /** Per-step state, keyed by step id. Opaque pass-through. */
93
+ steps: Record<string, unknown>;
94
+ /** The run's output when it finished; absent while it is still going. */
95
+ output?: unknown;
96
+ /** The run-level error when it failed; absent otherwise. Data, never a raised exception. Opaque pass-through. */
97
+ error?: unknown;
98
+ /** Per-step failures, when the server reports them. */
99
+ stepErrors?: StepError[];
100
+ }
101
+ /**
102
+ * One row of `listRuns`'s result — a run's summary, not its full state.
103
+ *
104
+ * Field-for-field, relocated from `packages/studio/src/api/types.ts:84-102`
105
+ * (the summary subset). Defined LOCALLY, same reasoning as {@linkcode RunState}.
106
+ */
107
+ export interface RunSummary {
108
+ /** Server-issued run handle, `run_…`. */
109
+ runId: string;
110
+ /** Where the run has got to. */
111
+ status: RunStatus;
112
+ /** What triggered this run, e.g. `"manual"`, `"schedule"`, `"webhook"`. */
113
+ trigger: string;
114
+ /** ISO-8601 timestamp. */
115
+ startedAt: string;
116
+ /** ISO-8601 timestamp, or `null` while the run is still going. */
117
+ finishedAt: string | null;
118
+ }
119
+ /**
120
+ * The `console.workflows` namespace on a `W6wClient`.
121
+ *
122
+ * @example
123
+ * ```ts
124
+ * const { workflow, updatedAt } = await client.console.workflows.get("wf_1");
125
+ * const saved = await client.console.workflows.upsert(workflow, { ifUnmodifiedSince: updatedAt });
126
+ * const runs = await client.console.workflows.listRuns("wf_1");
127
+ * const run = await client.console.workflows.getRun(runs[0].runId);
128
+ * await client.console.workflows.archive("wf_1");
129
+ * await client.console.workflows.delete("wf_1");
130
+ * ```
131
+ */
132
+ export declare class WorkflowsApi {
133
+ #private;
134
+ /**
135
+ * @param host - The client this namespace issues requests through.
136
+ */
137
+ constructor(host: WorkflowsHost);
138
+ /**
139
+ * Fetch one workflow's stored definition.
140
+ *
141
+ * `workflow` is the merged definition (the stored definition overlaid with
142
+ * `status` and `tags`) — an arbitrary JSON object, never a fixed shape, kept
143
+ * as `Record<string, unknown>` exactly as `client.ts` typed it. `updatedAt`
144
+ * is TOP-LEVEL, never inside `workflow` — it is the optimistic-concurrency
145
+ * token a save echoes back via {@linkcode upsert}'s `ifUnmodifiedSince`.
146
+ *
147
+ * No 404 special-casing — propagates as `ApiError` like every other "get" in
148
+ * this package except `console.schedules.get`'s documented, unrelated
149
+ * exception.
150
+ *
151
+ * @param id - The `wf_…` id.
152
+ * @returns The whole response body — no envelope key to peel.
153
+ * @throws {ApiError} `404 unknown_workflow` when there is no such id.
154
+ */
155
+ get(id: string): Promise<{
156
+ workflow: Record<string, unknown>;
157
+ sourceRef: string | null;
158
+ updatedAt: string;
159
+ }>;
160
+ /**
161
+ * Register (create or overwrite) a workflow definition.
162
+ *
163
+ * `definition` is the whole definition, sent verbatim as the request body —
164
+ * not wrapped in an envelope key. `options.ifUnmodifiedSince` is the
165
+ * optional optimistic-concurrency precondition: pass the exact `updatedAt`
166
+ * this client last saw (from {@linkcode get}, or a prior `upsert`'s own
167
+ * `updatedAt`) and the server rejects the write with `409 workflow_stale` if
168
+ * the stored row has moved on since. Omitted, the save is last-write-wins
169
+ * and the header is never sent at all — never as an empty or `"null"`
170
+ * string, mirroring the server's own conditional exactly
171
+ * (`admin/workflows.ts`).
172
+ *
173
+ * @param definition - The whole workflow definition, forwarded verbatim.
174
+ * @param options - `project` scopes the write; `ifUnmodifiedSince` is the concurrency precondition.
175
+ * @returns The new/updated workflow's id and name, whether a schedule was (re)applied, and the new `updatedAt`.
176
+ * @throws {ApiError} `400 invalid_workflow`/`invalid_body`/`invalid_precondition`.
177
+ * @throws {ApiError} `409 workflow_conflict` — another `(tenant,subject)` owns this id. NOT recoverable by reloading.
178
+ * @throws {ApiError} `409 workflow_stale` — the precondition didn't match the stored `updatedAt`. IS recoverable by reloading and re-saving.
179
+ */
180
+ upsert(definition: unknown, options?: {
181
+ project?: string;
182
+ ifUnmodifiedSince?: string | null;
183
+ }): Promise<{
184
+ workflow: {
185
+ id: string;
186
+ name: string;
187
+ };
188
+ scheduled: boolean;
189
+ updatedAt: string;
190
+ }>;
191
+ /**
192
+ * Archive a workflow. One-way — no unarchive route exists anywhere on this
193
+ * domain.
194
+ *
195
+ * Idempotent: archiving an already-archived workflow re-returns it
196
+ * unchanged rather than erroring.
197
+ *
198
+ * @param id - The `wf_…` id.
199
+ * @returns The archived workflow — no envelope key to peel.
200
+ * @throws {ApiError} `404 unknown_workflow` when there is no such id.
201
+ */
202
+ archive(id: string): Promise<{
203
+ workflow: Record<string, unknown>;
204
+ }>;
205
+ /**
206
+ * Delete a workflow. Cascade-deletes its runs/schedules/subscriptions
207
+ * server-side; nothing for the caller to do about that.
208
+ *
209
+ * The server requires archive-then-delete — deleting a workflow whose status
210
+ * isn't `"archived"` yet answers `409 workflow_not_archived`, which this
211
+ * method does NOT catch. That is a real signal the caller must handle;
212
+ * enforcing archive-then-delete is not this method's job.
213
+ *
214
+ * Returns nothing: the server's `{ok: true}` carries no information a
215
+ * caller can use.
216
+ *
217
+ * @param id - The `wf_…` id.
218
+ * @throws {ApiError} `404 unknown_workflow` when there is no such id.
219
+ * @throws {ApiError} `409 workflow_not_archived` when the workflow exists but is not archived yet.
220
+ */
221
+ delete(id: string): Promise<void>;
222
+ /**
223
+ * List a workflow's runs.
224
+ *
225
+ * @param id - The `wf_…` id.
226
+ * @returns The runs, unwrapped from the `runs` envelope.
227
+ * @throws {ApiError} `404 unknown_workflow` — an ownership gate: the workflow must belong to the caller.
228
+ */
229
+ listRuns(id: string): Promise<RunSummary[]>;
230
+ /**
231
+ * Fetch one run's full state.
232
+ *
233
+ * **One request, one answer — no polling, no `wait`, no retry.** The caller
234
+ * (studio's `lib/workflow-poll.ts`) owns the loop; see this module's header
235
+ * and `../workflows.ts`'s own "no client-side polling" pin.
236
+ *
237
+ * @param runId - The `run_…` id.
238
+ * @returns The run, unwrapped from the `run` envelope.
239
+ * @throws {ApiError} `404 unknown_run` — covers both "no such run" and "not your workflow"; the caller cannot and does not need to distinguish them.
240
+ */
241
+ getRun(runId: string): Promise<RunState>;
242
+ }
243
+ //# sourceMappingURL=workflows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflows.d.ts","sourceRoot":"","sources":["../../../src/console/workflows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAEH,OAAO,EAAE,KAAK,YAAY,EAAQ,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAC1E,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAU,MAAM,aAAa,CAAC;AAKrE;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,2BAA2B;IAC3B,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;CAC/D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,QAAQ;IACvB,yCAAyC;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,gCAAgC;IAChC,MAAM,EAAE,SAAS,CAAC;IAClB,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,yEAAyE;IACzE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,iHAAiH;IACjH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,uDAAuD;IACvD,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,yCAAyC;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,MAAM,EAAE,SAAS,CAAC;IAClB,2EAA2E;IAC3E,OAAO,EAAE,MAAM,CAAC;IAChB,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,YAAY;;IAGvB;;OAEG;gBACS,IAAI,EAAE,aAAa;IAI/B;;;;;;;;;;;;;;;;OAgBG;IACG,GAAG,CACP,EAAE,EAAE,MAAM,GACT,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAU9F;;;;;;;;;;;;;;;;;;;OAmBG;IACG,MAAM,CACV,UAAU,EAAE,OAAO,EACnB,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAChE,OAAO,CAAC;QAAE,QAAQ,EAAE;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,SAAS,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAe7F;;;;;;;;;;OAUG;IACG,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;IAQzE;;;;;;;;;;;;;;;OAeG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvC;;;;;;OAMG;IACG,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAQjD;;;;;;;;;;OAUG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;CAO/C"}
@@ -0,0 +1,205 @@
1
+ /**
2
+ * `client.console.workflows.*` — the workflow definition/lifecycle and run
3
+ * surface (`get`, `upsert`, `archive`, `delete`, `listRuns`, `getRun`),
4
+ * relocated from the studio's own API client.
5
+ *
6
+ * **Studio-internal, not the published partner contract.** This namespace
7
+ * lives under the `console` subpath export (`@w6w/sdk/console`), excluded from
8
+ * `endpoints.json` and from the root barrel (`mod.ts`) — see `docs/console.md`
9
+ * and HITL-1 in this task's contract. `@w6w/sdk`'s instance-state mechanism
10
+ * pin still applies here exactly as it does to every other namespace
11
+ * (`docs/implementation.md` §MECHANISM PIN — instance state, never globals):
12
+ * this class holds no state of its own beyond the injected host, so two
13
+ * clients in one process never share a credential.
14
+ *
15
+ * **Console placement is the same precedent as `console.connections`'s
16
+ * `HITL-10`, not a fresh judgment call — see `HITL.md` and `plan.md`'s SP2.3
17
+ * section for the full investigation.** `endpoints.json`'s `outOfScope` array
18
+ * names "all write operations on connections and workflows" together, with the
19
+ * identical "in this version" framing HITL-10 already resolved for
20
+ * connections. The server routes here (`admin/workflows.ts`) are, like
21
+ * connections, plain stateless handlers over an already-resolved request
22
+ * body/precondition header — no interactive flow the SDK method itself would
23
+ * need to conduct. Console placement is safe for studio's own use; it does not
24
+ * by itself justify promotion to the partner surface (cli/python lockstep; no
25
+ * documented partner story exists yet either) — that promotion, if it ever
26
+ * happens, is a dedicated future project.
27
+ *
28
+ * **`list` and `run` are NOT part of this domain.** Both already exist on the
29
+ * BASE namespace (`client.workflows.list()`/`client.workflows.run()`,
30
+ * `../workflows.ts`) and studio reuses them verbatim — adding a duplicate
31
+ * `list`/`run` under `console.workflows` would give a caller two ways to reach
32
+ * the same route, exactly the anti-duplication defect this project's own
33
+ * rules flag. **No poll loop lives here either**: `../workflows.ts`'s own
34
+ * module doc pins "no client-side polling" for the whole package, and
35
+ * `getRun` below is deliberately a single request with no retry — the caller
36
+ * (studio's `lib/workflow-poll.ts`) owns the loop.
37
+ *
38
+ * Five of six methods relocate from the studio's `// Workflows` comment block
39
+ * (`packages/studio/src/api/client.ts:254-331`): `getWorkflow`→`get`,
40
+ * `createWorkflow`→`upsert`, `archiveWorkflow`→`archive`,
41
+ * `deleteWorkflow`→`delete`, `listRuns`→`listRuns`. `getRun` is genuinely NEW
42
+ * here — `client.ts`'s own `getRun` method is dead code today (zero callers),
43
+ * but this task gives it a real consumer: `runWorkflow`'s poll loop, ported
44
+ * into studio's `lib/workflow-poll.ts`, calls it.
45
+ *
46
+ * None of the six methods take a client-side default-project fallback — they
47
+ * are addressed by `wf_…`/`run_…` id (plus an optional explicit `?project=`
48
+ * on `upsert`), so `WorkflowsHost` needs only the transport, mirroring
49
+ * `SchedulesHost`/`ProjectsHost`, not the BASE `WorkflowsHost` (`../workflows.ts`),
50
+ * which additionally reads `config.project` as `list()`'s default.
51
+ *
52
+ * @module
53
+ */
54
+ import { path } from "../http.js";
55
+ import { unwrap } from "../types.js";
56
+ /** The precondition header `upsert` sends when `ifUnmodifiedSince` is given. */
57
+ const PRECONDITION_HEADER = "x-w6w-if-unmodified-since";
58
+ /**
59
+ * The `console.workflows` namespace on a `W6wClient`.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * const { workflow, updatedAt } = await client.console.workflows.get("wf_1");
64
+ * const saved = await client.console.workflows.upsert(workflow, { ifUnmodifiedSince: updatedAt });
65
+ * const runs = await client.console.workflows.listRuns("wf_1");
66
+ * const run = await client.console.workflows.getRun(runs[0].runId);
67
+ * await client.console.workflows.archive("wf_1");
68
+ * await client.console.workflows.delete("wf_1");
69
+ * ```
70
+ */
71
+ export class WorkflowsApi {
72
+ #host;
73
+ /**
74
+ * @param host - The client this namespace issues requests through.
75
+ */
76
+ constructor(host) {
77
+ this.#host = host;
78
+ }
79
+ /**
80
+ * Fetch one workflow's stored definition.
81
+ *
82
+ * `workflow` is the merged definition (the stored definition overlaid with
83
+ * `status` and `tags`) — an arbitrary JSON object, never a fixed shape, kept
84
+ * as `Record<string, unknown>` exactly as `client.ts` typed it. `updatedAt`
85
+ * is TOP-LEVEL, never inside `workflow` — it is the optimistic-concurrency
86
+ * token a save echoes back via {@linkcode upsert}'s `ifUnmodifiedSince`.
87
+ *
88
+ * No 404 special-casing — propagates as `ApiError` like every other "get" in
89
+ * this package except `console.schedules.get`'s documented, unrelated
90
+ * exception.
91
+ *
92
+ * @param id - The `wf_…` id.
93
+ * @returns The whole response body — no envelope key to peel.
94
+ * @throws {ApiError} `404 unknown_workflow` when there is no such id.
95
+ */
96
+ async get(id) {
97
+ const res = await this.#host.request({
98
+ method: "GET",
99
+ path: path `/workflows/${id}`,
100
+ });
101
+ return res.body;
102
+ }
103
+ /**
104
+ * Register (create or overwrite) a workflow definition.
105
+ *
106
+ * `definition` is the whole definition, sent verbatim as the request body —
107
+ * not wrapped in an envelope key. `options.ifUnmodifiedSince` is the
108
+ * optional optimistic-concurrency precondition: pass the exact `updatedAt`
109
+ * this client last saw (from {@linkcode get}, or a prior `upsert`'s own
110
+ * `updatedAt`) and the server rejects the write with `409 workflow_stale` if
111
+ * the stored row has moved on since. Omitted, the save is last-write-wins
112
+ * and the header is never sent at all — never as an empty or `"null"`
113
+ * string, mirroring the server's own conditional exactly
114
+ * (`admin/workflows.ts`).
115
+ *
116
+ * @param definition - The whole workflow definition, forwarded verbatim.
117
+ * @param options - `project` scopes the write; `ifUnmodifiedSince` is the concurrency precondition.
118
+ * @returns The new/updated workflow's id and name, whether a schedule was (re)applied, and the new `updatedAt`.
119
+ * @throws {ApiError} `400 invalid_workflow`/`invalid_body`/`invalid_precondition`.
120
+ * @throws {ApiError} `409 workflow_conflict` — another `(tenant,subject)` owns this id. NOT recoverable by reloading.
121
+ * @throws {ApiError} `409 workflow_stale` — the precondition didn't match the stored `updatedAt`. IS recoverable by reloading and re-saving.
122
+ */
123
+ async upsert(definition, options) {
124
+ const res = await this.#host.request({
125
+ method: "POST",
126
+ path: "/workflows",
127
+ query: { project: options?.project },
128
+ body: definition,
129
+ ...(options?.ifUnmodifiedSince
130
+ ? { headers: { [PRECONDITION_HEADER]: options.ifUnmodifiedSince } }
131
+ : {}),
132
+ });
133
+ return res.body;
134
+ }
135
+ /**
136
+ * Archive a workflow. One-way — no unarchive route exists anywhere on this
137
+ * domain.
138
+ *
139
+ * Idempotent: archiving an already-archived workflow re-returns it
140
+ * unchanged rather than erroring.
141
+ *
142
+ * @param id - The `wf_…` id.
143
+ * @returns The archived workflow — no envelope key to peel.
144
+ * @throws {ApiError} `404 unknown_workflow` when there is no such id.
145
+ */
146
+ async archive(id) {
147
+ const res = await this.#host.request({
148
+ method: "POST",
149
+ path: path `/workflows/${id}/archive`,
150
+ });
151
+ return res.body;
152
+ }
153
+ /**
154
+ * Delete a workflow. Cascade-deletes its runs/schedules/subscriptions
155
+ * server-side; nothing for the caller to do about that.
156
+ *
157
+ * The server requires archive-then-delete — deleting a workflow whose status
158
+ * isn't `"archived"` yet answers `409 workflow_not_archived`, which this
159
+ * method does NOT catch. That is a real signal the caller must handle;
160
+ * enforcing archive-then-delete is not this method's job.
161
+ *
162
+ * Returns nothing: the server's `{ok: true}` carries no information a
163
+ * caller can use.
164
+ *
165
+ * @param id - The `wf_…` id.
166
+ * @throws {ApiError} `404 unknown_workflow` when there is no such id.
167
+ * @throws {ApiError} `409 workflow_not_archived` when the workflow exists but is not archived yet.
168
+ */
169
+ async delete(id) {
170
+ await this.#host.request({ method: "DELETE", path: path `/workflows/${id}` });
171
+ }
172
+ /**
173
+ * List a workflow's runs.
174
+ *
175
+ * @param id - The `wf_…` id.
176
+ * @returns The runs, unwrapped from the `runs` envelope.
177
+ * @throws {ApiError} `404 unknown_workflow` — an ownership gate: the workflow must belong to the caller.
178
+ */
179
+ async listRuns(id) {
180
+ const res = await this.#host.request({
181
+ method: "GET",
182
+ path: path `/workflows/${id}/runs`,
183
+ });
184
+ return unwrap(res, "runs");
185
+ }
186
+ /**
187
+ * Fetch one run's full state.
188
+ *
189
+ * **One request, one answer — no polling, no `wait`, no retry.** The caller
190
+ * (studio's `lib/workflow-poll.ts`) owns the loop; see this module's header
191
+ * and `../workflows.ts`'s own "no client-side polling" pin.
192
+ *
193
+ * @param runId - The `run_…` id.
194
+ * @returns The run, unwrapped from the `run` envelope.
195
+ * @throws {ApiError} `404 unknown_run` — covers both "no such run" and "not your workflow"; the caller cannot and does not need to distinguish them.
196
+ */
197
+ async getRun(runId) {
198
+ const res = await this.#host.request({
199
+ method: "GET",
200
+ path: path `/runs/${runId}`,
201
+ });
202
+ return unwrap(res, "run");
203
+ }
204
+ }
205
+ //# sourceMappingURL=workflows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflows.js","sourceRoot":"","sources":["../../../src/console/workflows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAEH,OAAO,EAAqB,IAAI,EAAuB,MAAM,YAAY,CAAC;AAC1E,OAAO,EAAkC,MAAM,EAAE,MAAM,aAAa,CAAC;AAErE,gFAAgF;AAChF,MAAM,mBAAmB,GAAG,2BAA2B,CAAC;AAoExD;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,YAAY;IACd,KAAK,CAAgB;IAE9B;;OAEG;IACH,YAAY,IAAmB;QAC7B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACpB,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,KAAK,CAAC,GAAG,CACP,EAAU;QAEV,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAElC;YACA,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,IAAI,CAAA,cAAc,EAAE,EAAE;SAC7B,CAAC,CAAC;QACH,OAAO,GAAG,CAAC,IAAI,CAAC;IAClB,CAAC;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,KAAK,CAAC,MAAM,CACV,UAAmB,EACnB,OAAiE;QAEjE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAElC;YACA,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,YAAY;YAClB,KAAK,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE;YACpC,IAAI,EAAE,UAAU;YAChB,GAAG,CAAC,OAAO,EAAE,iBAAiB;gBAC5B,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC,iBAAiB,EAAE,EAAE;gBACnE,CAAC,CAAC,EAAE,CAAC;SACR,CAAC,CAAC;QACH,OAAO,GAAG,CAAC,IAAI,CAAC;IAClB,CAAC;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,OAAO,CAAC,EAAU;QACtB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAwC;YAC1E,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAA,cAAc,EAAE,UAAU;SACrC,CAAC,CAAC;QACH,OAAO,GAAG,CAAC,IAAI,CAAC;IAClB,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACH,KAAK,CAAC,MAAM,CAAC,EAAU;QACrB,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAA,cAAc,EAAE,EAAE,EAAE,CAAC,CAAC;IACxF,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,QAAQ,CAAC,EAAU;QACvB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAU;YAC5C,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,IAAI,CAAA,cAAc,EAAE,OAAO;SAClC,CAAC,CAAC;QACH,OAAO,MAAM,CAAe,GAAG,EAAE,MAAM,CAAC,CAAC;IAC3C,CAAC;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,MAAM,CAAC,KAAa;QACxB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAU;YAC5C,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,IAAI,CAAA,SAAS,KAAK,EAAE;SAC3B,CAAC,CAAC;QACH,OAAO,MAAM,CAAW,GAAG,EAAE,KAAK,CAAC,CAAC;IACtC,CAAC;CACF"}