@adobe/spacecat-shared-project-engine-client 1.1.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +157 -13
- package/package.json +10 -4
- package/src/client.js +2 -0
- package/src/generated/types.ts +69 -76
- package/src/index.js +2 -0
- package/src/internal.js +23 -11
package/README.md
CHANGED
|
@@ -5,10 +5,7 @@ Typed integration with the Semrush **Project Engine API** (`/enterprise/projects
|
|
|
5
5
|
- generated **TypeScript** (`src/generated/types.ts`) and **Pydantic v2** (`python/serenity_project_engine/`) types,
|
|
6
6
|
- a thin **Project Engine client** (`openapi-fetch` over the generated `paths`) — IMS Bearer auth + idempotency-aware retries (see [Client](#client)),
|
|
7
7
|
- a generation-time **spec-correction overlay** (`spec/overlays/corrections.yaml`) that aligns the vendored swagger with the live API,
|
|
8
|
-
- a **Counterfact mock** for E2E tests and local dev (`npm run mock
|
|
9
|
-
|
|
10
|
-
> **Not here yet:** the IMS auth-handler move and the stateful mock store are
|
|
11
|
-
> tracked follow-ups (see LLMO-5461 / LLMO-5460).
|
|
8
|
+
- a **stateful Counterfact mock** for E2E tests and local dev (`npm run mock`, see below).
|
|
12
9
|
|
|
13
10
|
This package follows the `spacecat-shared` convention: **JS + ESM**, JSDoc-typed source,
|
|
14
11
|
`mocha` + `chai` + `c8` for tests, and `@adobe/eslint-config-helix` for lint. The scaffold's
|
|
@@ -56,22 +53,21 @@ access is restricted.
|
|
|
56
53
|
|
|
57
54
|
```
|
|
58
55
|
spec/projectengine_swagger_public.yaml (vendored, Swagger 2.0)
|
|
59
|
-
│
|
|
60
|
-
├── Counterfact ── reads v2 directly ──► mock (no conversion) [npm run mock]
|
|
61
56
|
│
|
|
62
57
|
└── swagger2openapi (v2 → 3.x) ──► build/openapi3.json
|
|
63
58
|
│
|
|
64
59
|
├── apply-overlay (corrections) ──► build/openapi3.json (in place)
|
|
65
60
|
│
|
|
61
|
+
├── Counterfact ──► mock [npm run mock]
|
|
66
62
|
├── openapi-typescript ──► src/generated/types.ts
|
|
67
63
|
└── datamodel-code-generator ──► python/serenity_project_engine/
|
|
68
64
|
```
|
|
69
65
|
|
|
70
|
-
The v2 → 3.x conversion
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
66
|
+
The v2 → 3.x conversion + overlay feeds both the type generators and the Counterfact mock.
|
|
67
|
+
`npm run generate` must be run once (and re-run after any spec refresh) before `npm run mock` —
|
|
68
|
+
`build/openapi3.json` is gitignored. The overlay is applied in place so Counterfact sees the
|
|
69
|
+
corrected paths (including `GET /v1/ai_models`, CR1) and the mock serves under the
|
|
70
|
+
`/enterprise/projects/api` base path via `--prefix`.
|
|
75
71
|
|
|
76
72
|
| Command | Does |
|
|
77
73
|
| --- | --- |
|
|
@@ -79,8 +75,8 @@ see the corrections — a known gap the stateful-mock follow-up addresses).
|
|
|
79
75
|
| `npm run spec:overlay` | apply `spec/overlays/corrections.yaml` to `build/openapi3.json` in place |
|
|
80
76
|
| `npm run generate:ts` | `openapi-typescript` → `src/generated/types.ts` |
|
|
81
77
|
| `npm run generate:pydantic` | `datamodel-code-generator` → `python/serenity_project_engine/` package |
|
|
82
|
-
| `npm run generate` | all of the above, in order |
|
|
83
|
-
| `npm run mock` | Counterfact mock on `:4010`,
|
|
78
|
+
| `npm run generate` | all of the above, in order (run before `npm run mock`) |
|
|
79
|
+
| `npm run mock` | Counterfact mock on `:4010`, corrected OAS3 artifact + `--prefix /enterprise/projects/api` |
|
|
84
80
|
|
|
85
81
|
`datamodel-code-generator` is a **Python** tool, not an npm dependency. Install it once on
|
|
86
82
|
your `PATH` before running `generate:pydantic`:
|
|
@@ -108,6 +104,154 @@ Guard tests in `test/foundation.test.js` pin CR1 and CR2 against the generated s
|
|
|
108
104
|
Semrush spec refresh that silently drops the overlay fails loudly instead of regressing the
|
|
109
105
|
generated types. `test/overlay.test.js` covers the overlay applier itself.
|
|
110
106
|
|
|
107
|
+
## Mock (stateful)
|
|
108
|
+
|
|
109
|
+
> **Full guide:** [`docs/mock-usage.md`](./docs/mock-usage.md) is the complete usage manual for
|
|
110
|
+
> humans and agents — auth, the full endpoint inventory, seeds, control routes, quota, and
|
|
111
|
+
> troubleshooting. This section is the summary.
|
|
112
|
+
|
|
113
|
+
`npm run mock` starts a **stateful** Counterfact server off the corrected OAS3 artifact
|
|
114
|
+
(`build/openapi3.json` — run `npm run generate` first). The runner serves only the modelled
|
|
115
|
+
handlers (`--serve`, no `generate`), so an unmodelled path **404s** — it does not fall back to a
|
|
116
|
+
spec-driven stub. The project spine is backed by a shared in-memory store so reads reflect prior
|
|
117
|
+
writes within a run; see [`docs/mock-usage.md`](./docs/mock-usage.md) §9 to add an endpoint.
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npm run mock # serves on :4010
|
|
121
|
+
MOCK_PORT=4032 MOCK_SEED=empty-workspace npm run mock
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Base URL: `http://localhost:<port>/enterprise/projects/api`.
|
|
125
|
+
|
|
126
|
+
**Auth:** every real route requires `Authorization: Bearer <token>` (any non-empty token — the
|
|
127
|
+
mock checks presence, not validity, like the live gateway). Missing/invalid → `401 { "detail":
|
|
128
|
+
"Not authenticated" }`. The `__*` control routes are exempt. See the manual §2.
|
|
129
|
+
|
|
130
|
+
| Var | Default | Purpose |
|
|
131
|
+
| --- | --- | --- |
|
|
132
|
+
| `MOCK_PORT` | `4010` | listen port |
|
|
133
|
+
| `MOCK_SEED` | default seed | named startup fixture; unknown values fall back to the default |
|
|
134
|
+
| `MOCK_SEED_FILE` | — | path to a JSON `Snapshot` to boot from; takes precedence over `MOCK_SEED` |
|
|
135
|
+
|
|
136
|
+
Stateful endpoints (backed by the store):
|
|
137
|
+
|
|
138
|
+
| Method + path | Behaviour |
|
|
139
|
+
| --- | --- |
|
|
140
|
+
| `GET/POST /v1/workspaces/{id}/projects` | list / create |
|
|
141
|
+
| `GET/PATCH/DELETE /v1/workspaces/{id}/projects/{project_id}` | get / update / remove (404 when missing) |
|
|
142
|
+
| `GET/DELETE /v1/workspaces/{id}/projects/{project_id}/ai_models` | list / batch-delete |
|
|
143
|
+
| `POST /v2/workspaces/{id}/projects/{project_id}/ai_models` | add (the path the real consumer uses; writes the same store collection the v1 list/delete read) |
|
|
144
|
+
| `POST /v2/workspaces/{id}/projects/{project_id}/aio/prompts/tagged` | create prompts grouped by tag name |
|
|
145
|
+
| `POST /v2/workspaces/{id}/projects/{project_id}/aio/prompts/by_tags` | list prompts (empty `tag_ids` lists all; otherwise OR-filter) |
|
|
146
|
+
| `DELETE /v2/workspaces/{id}/projects/{project_id}/aio/prompts` | batch-delete prompts by id |
|
|
147
|
+
|
|
148
|
+
### Test control routes (not part of the Project Engine API)
|
|
149
|
+
|
|
150
|
+
All under the base URL, e.g. `http://localhost:<port>/enterprise/projects/api/__dump`:
|
|
151
|
+
|
|
152
|
+
| Route | Purpose |
|
|
153
|
+
| --- | --- |
|
|
154
|
+
| `POST /__reset` | restore the store to its boot seed (or the last `/__seed`) — call between E2E cases for isolation |
|
|
155
|
+
| `POST /__seed` | replace the store with the posted `Snapshot` and make it the new reset baseline — set the mock to exactly the state a test needs |
|
|
156
|
+
| `GET /__dump` | **look inside the mock DB** — returns the current store state as JSON (every `projects:{ws}` / `ai_models:{ws}:{pr}` / `prompts:{ws}:{pr}` collection and its rows) |
|
|
157
|
+
| `POST /__quota` | set a workspace's AI-unit allocation: `{ workspaceId, projects?, prompts? }` (mirrors a user-manager transfer; `{ projects: 0, prompts: 0 }` = empty-units child). Project create / prompt write / publish then return the disguised quota **405** when exhausted |
|
|
158
|
+
| `GET /__quota?workspaceId=<ws>` | read a workspace's limits + live usage |
|
|
159
|
+
|
|
160
|
+
**Model AI-unit quota (the disguised 405).** A sub-workspace with no allocation is unlimited
|
|
161
|
+
(default). Grant one, then the metered ops 405 when exhausted — the behaviour the consumer's
|
|
162
|
+
quota handling relies on:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
# grant 1 project + 2 prompts to a workspace
|
|
166
|
+
curl -s -XPOST .../__quota -d '{"workspaceId":"<ws>","projects":1,"prompts":2}'
|
|
167
|
+
# a 2nd project create, a 3rd prompt, or publishing an empty-units child now returns 405
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Inspect what's inside:**
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
curl -s http://localhost:4010/enterprise/projects/api/__dump | jq
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Seed state that matches your DB.** `/__seed` (and a `MOCK_SEED_FILE`) take a `Snapshot`: a
|
|
177
|
+
plain JSON object keyed by `<resource>:<scope>` whose values are entity rows. The Project Engine
|
|
178
|
+
API types every id as `format: uuid`, so use the same **UUIDs** you load into Postgres
|
|
179
|
+
(`semrush_workspace_id` / project id) so the two sides line up, and mirror the real entity
|
|
180
|
+
shapes (`ProjectAIModelResponse`, `AIOPromptWithStatus`). Any caller — including the cross-repo
|
|
181
|
+
harness consuming the published client — can POST this JSON directly:
|
|
182
|
+
|
|
183
|
+
```jsonc
|
|
184
|
+
// POST /__seed
|
|
185
|
+
{
|
|
186
|
+
"projects:a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d": [
|
|
187
|
+
{ "id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e", "name": "Acme", "workspace_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" }
|
|
188
|
+
],
|
|
189
|
+
"ai_models:a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d:b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e": [
|
|
190
|
+
{ "id": "c3d4e5f6-a7b8-4c9d-8e1f-2a3b4c5d6e7f", "model": { "id": "d4e5f6a7-b8c9-4d0e-9f1a-3b4c5d6e7f80", "key": "gpt-4o", "name": "GPT-4o" }, "prompts_count": 0 }
|
|
191
|
+
],
|
|
192
|
+
"prompts:a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d:b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e": [
|
|
193
|
+
{ "id": "e5f6a7b8-c9d0-4e1f-8a2b-4c5d6e7f8091", "name": "What is Acme?", "is_new": false, "tags": [] }
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Rather than hand-write that JSON, use the **typed mock factories** (the
|
|
199
|
+
[mock factory pattern](https://dev.to/davelosert/mock-factory-pattern-in-typescript-44l9)):
|
|
200
|
+
each `createXMock(overrides?)` returns a spec-shaped entity typed against the generated
|
|
201
|
+
(overlay-corrected) component schemas, so fixtures stay in sync with the spec and `npm run
|
|
202
|
+
test:types` fails on drift (wrong/unknown/missing-required field). `buildSeed()` routes the
|
|
203
|
+
factory rows into the collection-keyed `Snapshot`. All are on the `./mock/*` subpath, so a
|
|
204
|
+
workspace caller (this repo's tests, or the cross-repo harness resolving spacecat-shared from
|
|
205
|
+
the checkout) imports them by package name:
|
|
206
|
+
|
|
207
|
+
```js
|
|
208
|
+
import { buildSeed } from '@adobe/spacecat-shared-project-engine-client/mock/seeds.js';
|
|
209
|
+
import {
|
|
210
|
+
createProjectAiModelMock,
|
|
211
|
+
createAiModelMock,
|
|
212
|
+
createPromptMock,
|
|
213
|
+
} from '@adobe/spacecat-shared-project-engine-client/mock/factories.js';
|
|
214
|
+
import { randomUUID } from 'node:crypto';
|
|
215
|
+
|
|
216
|
+
const workspaceId = randomUUID();
|
|
217
|
+
const projectId = randomUUID();
|
|
218
|
+
const snapshot = buildSeed({
|
|
219
|
+
workspaceId,
|
|
220
|
+
projects: [{
|
|
221
|
+
id: projectId,
|
|
222
|
+
name: 'Acme',
|
|
223
|
+
aiModels: [createProjectAiModelMock({ model: createAiModelMock({ name: 'GPT-4o' }) })],
|
|
224
|
+
prompts: [createPromptMock({ name: 'What is Acme?' })],
|
|
225
|
+
}],
|
|
226
|
+
});
|
|
227
|
+
await fetch(`${baseUrl}/__seed`, { method: 'POST', body: JSON.stringify(snapshot) });
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
The `./mock/*` subpath resolves against the **checked-out** package (the mock isn't in the
|
|
231
|
+
published tarball — see "Not published" below), which is the same source the harness boots the
|
|
232
|
+
mock from, so it adds no new coupling. A consumer that only has the *published* tarball has no
|
|
233
|
+
`buildSeed`; it POSTs the raw `Snapshot` JSON above instead.
|
|
234
|
+
|
|
235
|
+
`npm run test:e2e` drives the **real client** against a freshly booted mock (self-managed
|
|
236
|
+
lifecycle, `__reset` between cases). It is gated behind `MOCK_E2E=1` and lives outside the
|
|
237
|
+
default `npm test` glob, so the unit suite stays fast and keeps 100% coverage with no
|
|
238
|
+
live-server dependency. CI runs it as a dedicated `E2E (project-engine mock)` job.
|
|
239
|
+
|
|
240
|
+
> **How it runs:** the runner materializes the committed handlers from `mock/` into a
|
|
241
|
+
> gitignored `.counterfact/` tree (as `.ts`, so Counterfact's transpiler emits loadable `.cjs`)
|
|
242
|
+
> and launches with `--serve` so no spec stubs are appended onto the stateful handlers. The
|
|
243
|
+
> store, seeds, and resource ops are plain unit-tested JS in `mock/`; only the runner and
|
|
244
|
+
> the materialized handlers — which need a live server — are excluded from coverage.
|
|
245
|
+
|
|
246
|
+
> **Not published.** The `mock/` tree sits outside `src/` and outside the package's `files`
|
|
247
|
+
> allowlist, so nothing mock-related is in the published tarball — client consumers install
|
|
248
|
+
> only `src/` (the typed client + generated types), and `counterfact` stays a `devDependency`.
|
|
249
|
+
> The mock is booted from source via `npm run mock` (or `npm run test:e2e`) inside the
|
|
250
|
+
> monorepo / e2e harness, which is why it never needs to ship. The `./mock/*` entry in
|
|
251
|
+
> `exports` makes those source files importable by package name **when spacecat-shared is
|
|
252
|
+
> resolved from a checkout** (the workspace case); it intentionally has no effect for a
|
|
253
|
+
> tarball-only consumer, which never imports the mock.
|
|
254
|
+
|
|
111
255
|
## Committed vs generated
|
|
112
256
|
|
|
113
257
|
- **Committed:** the vendored spec (`spec/projectengine_swagger_public.yaml`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/spacecat-shared-project-engine-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Shared modules of the Spacecat Services - Semrush Project Engine client and generated types",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
".": {
|
|
10
10
|
"types": "./src/index.d.ts",
|
|
11
11
|
"default": "./src/index.js"
|
|
12
|
-
}
|
|
12
|
+
},
|
|
13
|
+
"./mock/*": "./mock/*"
|
|
13
14
|
},
|
|
14
15
|
"files": [
|
|
15
16
|
"src"
|
|
@@ -28,7 +29,11 @@
|
|
|
28
29
|
"generate:ts": "openapi-typescript build/openapi3.json --output src/generated/types.ts",
|
|
29
30
|
"generate:pydantic": "datamodel-codegen --input build/openapi3.json --input-file-type openapi --output python/serenity_project_engine/ --output-model-type pydantic_v2.BaseModel",
|
|
30
31
|
"generate": "npm run spec:convert && npm run spec:overlay && npm run generate:ts && npm run generate:pydantic",
|
|
31
|
-
"mock": "
|
|
32
|
+
"mock": "node mock/run.js",
|
|
33
|
+
"docker:build": "docker build -t ghcr.io/adobe/spacecat-shared-project-engine-client-mock:local .",
|
|
34
|
+
"docker:run": "docker run --rm -p 127.0.0.1:8443:8443 ghcr.io/adobe/spacecat-shared-project-engine-client-mock:local",
|
|
35
|
+
"test:e2e": "MOCK_E2E=1 mocha --no-package --reporter spec --timeout 60000 test/e2e/**/*.e2e.js",
|
|
36
|
+
"test:types": "tsc -p tsconfig.json"
|
|
32
37
|
},
|
|
33
38
|
"mocha": {
|
|
34
39
|
"reporter": "mocha-multi-reporters",
|
|
@@ -60,6 +65,7 @@
|
|
|
60
65
|
"mocha-multi-reporters": "1.5.1",
|
|
61
66
|
"openapi-typescript": "7.13.0",
|
|
62
67
|
"sinon": "22.0.0",
|
|
63
|
-
"swagger2openapi": "7.0.8"
|
|
68
|
+
"swagger2openapi": "7.0.8",
|
|
69
|
+
"typescript": "6.0.3"
|
|
64
70
|
}
|
|
65
71
|
}
|
package/src/client.js
CHANGED
package/src/generated/types.ts
CHANGED
|
@@ -554,68 +554,6 @@ export interface paths {
|
|
|
554
554
|
patch?: never;
|
|
555
555
|
trace?: never;
|
|
556
556
|
};
|
|
557
|
-
"/v1/workspaces/{id}/projects/{project_id}/aio/init_status": {
|
|
558
|
-
parameters: {
|
|
559
|
-
query?: never;
|
|
560
|
-
header?: never;
|
|
561
|
-
path?: never;
|
|
562
|
-
cookie?: never;
|
|
563
|
-
};
|
|
564
|
-
/**
|
|
565
|
-
* Check whether an AIO project is initialized
|
|
566
|
-
* @description Check whether AIO project initialization has finished for workspace and project with given IDs.
|
|
567
|
-
*/
|
|
568
|
-
get: {
|
|
569
|
-
parameters: {
|
|
570
|
-
query?: never;
|
|
571
|
-
header?: never;
|
|
572
|
-
path: {
|
|
573
|
-
/** @description Workspace ID */
|
|
574
|
-
id: string;
|
|
575
|
-
/** @description Project ID */
|
|
576
|
-
project_id: string;
|
|
577
|
-
};
|
|
578
|
-
cookie?: never;
|
|
579
|
-
};
|
|
580
|
-
requestBody?: never;
|
|
581
|
-
responses: {
|
|
582
|
-
/** @description OK */
|
|
583
|
-
200: {
|
|
584
|
-
headers: {
|
|
585
|
-
[name: string]: unknown;
|
|
586
|
-
};
|
|
587
|
-
content: {
|
|
588
|
-
"application/json": components["schemas"]["model.AIOProjectInitializedResponse"];
|
|
589
|
-
};
|
|
590
|
-
};
|
|
591
|
-
/** @description Bad Request */
|
|
592
|
-
400: {
|
|
593
|
-
headers: {
|
|
594
|
-
[name: string]: unknown;
|
|
595
|
-
};
|
|
596
|
-
content: {
|
|
597
|
-
"application/json": components["schemas"]["http_server.BasicResponse"];
|
|
598
|
-
};
|
|
599
|
-
};
|
|
600
|
-
/** @description Internal Server Error */
|
|
601
|
-
500: {
|
|
602
|
-
headers: {
|
|
603
|
-
[name: string]: unknown;
|
|
604
|
-
};
|
|
605
|
-
content: {
|
|
606
|
-
"application/json": components["schemas"]["http_server.BasicResponse"];
|
|
607
|
-
};
|
|
608
|
-
};
|
|
609
|
-
};
|
|
610
|
-
};
|
|
611
|
-
put?: never;
|
|
612
|
-
post?: never;
|
|
613
|
-
delete?: never;
|
|
614
|
-
options?: never;
|
|
615
|
-
head?: never;
|
|
616
|
-
patch?: never;
|
|
617
|
-
trace?: never;
|
|
618
|
-
};
|
|
619
557
|
"/v1/workspaces/{id}/projects/{project_id}/apply_interactive_ignore_issue_rules/{crawl_configuration_id}": {
|
|
620
558
|
parameters: {
|
|
621
559
|
query?: never;
|
|
@@ -1644,6 +1582,26 @@ export interface paths {
|
|
|
1644
1582
|
patch?: never;
|
|
1645
1583
|
trace?: never;
|
|
1646
1584
|
};
|
|
1585
|
+
"/v2/workspaces/{id}/projects/{project_id}/aio/init_status": {
|
|
1586
|
+
parameters: {
|
|
1587
|
+
query?: never;
|
|
1588
|
+
header?: never;
|
|
1589
|
+
path?: never;
|
|
1590
|
+
cookie?: never;
|
|
1591
|
+
};
|
|
1592
|
+
/**
|
|
1593
|
+
* AIO project initialization status
|
|
1594
|
+
* @description Check whether AIO project initialization has finished for the workspace and project.
|
|
1595
|
+
*/
|
|
1596
|
+
get: operations["aio-get-project-init-status-v2"];
|
|
1597
|
+
put?: never;
|
|
1598
|
+
post?: never;
|
|
1599
|
+
delete?: never;
|
|
1600
|
+
options?: never;
|
|
1601
|
+
head?: never;
|
|
1602
|
+
patch?: never;
|
|
1603
|
+
trace?: never;
|
|
1604
|
+
};
|
|
1647
1605
|
}
|
|
1648
1606
|
export type webhooks = Record<string, never>;
|
|
1649
1607
|
export interface components {
|
|
@@ -1661,7 +1619,7 @@ export interface components {
|
|
|
1661
1619
|
};
|
|
1662
1620
|
"model.AIModelResponse": {
|
|
1663
1621
|
icon?: string;
|
|
1664
|
-
id
|
|
1622
|
+
id: string;
|
|
1665
1623
|
key?: string;
|
|
1666
1624
|
name?: string;
|
|
1667
1625
|
};
|
|
@@ -1678,12 +1636,14 @@ export interface components {
|
|
|
1678
1636
|
color?: string;
|
|
1679
1637
|
domain?: string;
|
|
1680
1638
|
favorite?: boolean;
|
|
1681
|
-
id
|
|
1639
|
+
id: string;
|
|
1682
1640
|
main_brand?: boolean;
|
|
1683
1641
|
product_names?: string[];
|
|
1684
1642
|
products_count?: number;
|
|
1685
1643
|
project_id?: string;
|
|
1686
1644
|
rejected_brand_aliases?: string[];
|
|
1645
|
+
primary_url?: string;
|
|
1646
|
+
root_domain?: string;
|
|
1687
1647
|
};
|
|
1688
1648
|
"model.AIOProduct": {
|
|
1689
1649
|
aliases?: string[];
|
|
@@ -1700,10 +1660,10 @@ export interface components {
|
|
|
1700
1660
|
initialized?: boolean;
|
|
1701
1661
|
};
|
|
1702
1662
|
"model.AIOPromptWithStatus": {
|
|
1703
|
-
id
|
|
1663
|
+
id: string;
|
|
1704
1664
|
is_new?: boolean;
|
|
1705
|
-
name
|
|
1706
|
-
tags
|
|
1665
|
+
name: string;
|
|
1666
|
+
tags: components["schemas"]["model.AIOTag"][];
|
|
1707
1667
|
};
|
|
1708
1668
|
"model.AIOPromptsListRequest": {
|
|
1709
1669
|
limit?: number;
|
|
@@ -1754,6 +1714,7 @@ export interface components {
|
|
|
1754
1714
|
products_count?: number;
|
|
1755
1715
|
prompts_count?: number;
|
|
1756
1716
|
segments_count?: number;
|
|
1717
|
+
primary_url?: string;
|
|
1757
1718
|
};
|
|
1758
1719
|
"model.AdvancedCrawlingConsentRequest": {
|
|
1759
1720
|
accepted: boolean;
|
|
@@ -1787,7 +1748,7 @@ export interface components {
|
|
|
1787
1748
|
"model.BrandURL": {
|
|
1788
1749
|
benchmark_id?: string;
|
|
1789
1750
|
created_at?: string;
|
|
1790
|
-
id
|
|
1751
|
+
id: string;
|
|
1791
1752
|
project_id?: string;
|
|
1792
1753
|
type?: string;
|
|
1793
1754
|
updated_at?: string;
|
|
@@ -2145,9 +2106,9 @@ export interface components {
|
|
|
2145
2106
|
total?: number;
|
|
2146
2107
|
};
|
|
2147
2108
|
"model.ProjectAIModelResponse": {
|
|
2148
|
-
id
|
|
2149
|
-
model
|
|
2150
|
-
prompts_count
|
|
2109
|
+
id: string;
|
|
2110
|
+
model: components["schemas"]["model.AIModelResponse"];
|
|
2111
|
+
prompts_count: number;
|
|
2151
2112
|
};
|
|
2152
2113
|
"model.ProjectListResponse": {
|
|
2153
2114
|
items?: components["schemas"]["model.ProjectResponse"][];
|
|
@@ -2193,10 +2154,10 @@ export interface components {
|
|
|
2193
2154
|
domain?: string;
|
|
2194
2155
|
draft_id?: string;
|
|
2195
2156
|
favourite?: boolean;
|
|
2196
|
-
id
|
|
2157
|
+
id: string;
|
|
2197
2158
|
is_draft?: boolean;
|
|
2198
2159
|
live_id?: string;
|
|
2199
|
-
name
|
|
2160
|
+
name: string;
|
|
2200
2161
|
/** @description enum: draft, publishing, initial_publish_failed, live, live_with_unpublished_updates */
|
|
2201
2162
|
publish_status?: string;
|
|
2202
2163
|
published_at?: string;
|
|
@@ -6561,8 +6522,8 @@ export interface operations {
|
|
|
6561
6522
|
};
|
|
6562
6523
|
requestBody: components["requestBodies"]["model.CreateProjectAIModelRequest"];
|
|
6563
6524
|
responses: {
|
|
6564
|
-
/** @description
|
|
6565
|
-
|
|
6525
|
+
/** @description Created */
|
|
6526
|
+
201: {
|
|
6566
6527
|
headers: {
|
|
6567
6528
|
[name: string]: unknown;
|
|
6568
6529
|
};
|
|
@@ -8057,7 +8018,7 @@ export interface operations {
|
|
|
8057
8018
|
[name: string]: unknown;
|
|
8058
8019
|
};
|
|
8059
8020
|
content: {
|
|
8060
|
-
"application/json": components["schemas"]["model.TreeNodeResponse"];
|
|
8021
|
+
"application/json": components["schemas"]["model.TreeNodeResponse"][];
|
|
8061
8022
|
};
|
|
8062
8023
|
};
|
|
8063
8024
|
/** @description Unauthorized */
|
|
@@ -8370,4 +8331,36 @@ export interface operations {
|
|
|
8370
8331
|
};
|
|
8371
8332
|
};
|
|
8372
8333
|
};
|
|
8334
|
+
"aio-get-project-init-status-v2": {
|
|
8335
|
+
parameters: {
|
|
8336
|
+
query?: never;
|
|
8337
|
+
header?: never;
|
|
8338
|
+
path: {
|
|
8339
|
+
id: string;
|
|
8340
|
+
project_id: string;
|
|
8341
|
+
};
|
|
8342
|
+
cookie?: never;
|
|
8343
|
+
};
|
|
8344
|
+
requestBody?: never;
|
|
8345
|
+
responses: {
|
|
8346
|
+
/** @description OK */
|
|
8347
|
+
200: {
|
|
8348
|
+
headers: {
|
|
8349
|
+
[name: string]: unknown;
|
|
8350
|
+
};
|
|
8351
|
+
content: {
|
|
8352
|
+
"application/json": components["schemas"]["model.AIOProjectInitializedResponse"];
|
|
8353
|
+
};
|
|
8354
|
+
};
|
|
8355
|
+
/** @description Unauthorized */
|
|
8356
|
+
401: {
|
|
8357
|
+
headers: {
|
|
8358
|
+
[name: string]: unknown;
|
|
8359
|
+
};
|
|
8360
|
+
content: {
|
|
8361
|
+
"application/json": components["schemas"]["http_server.BasicResponse"];
|
|
8362
|
+
};
|
|
8363
|
+
};
|
|
8364
|
+
};
|
|
8365
|
+
};
|
|
8373
8366
|
}
|
package/src/index.js
CHANGED
package/src/internal.js
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
// @ts-check
|
|
14
|
+
|
|
13
15
|
/**
|
|
14
16
|
* Framework-agnostic building blocks for the Project Engine client.
|
|
15
17
|
* Deliberately free of `openapi-fetch` and generated-type imports so they can be
|
|
@@ -25,7 +27,7 @@ const IDEMPOTENT_METHODS = new Set(['GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS']);
|
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* Resolves the HTTP method for a fetch call the same way the platform does.
|
|
28
|
-
* @param {
|
|
30
|
+
* @param {Request | string | URL} input the `fetch` input (a Request, URL, or URL string)
|
|
29
31
|
* @param {RequestInit} [init]
|
|
30
32
|
* @returns {string} the upper-cased method
|
|
31
33
|
*/
|
|
@@ -122,14 +124,23 @@ export function nextRetryDelayMs(completedAttempt, baseDelayMs, response) {
|
|
|
122
124
|
return Math.min(delay, MAX_RETRY_DELAY_MS);
|
|
123
125
|
}
|
|
124
126
|
|
|
127
|
+
/**
|
|
128
|
+
* @typedef {object} RetryInfo
|
|
129
|
+
* @property {number} attempt the 1-based number of the retry about to be made
|
|
130
|
+
* @property {number} delayMs the wait before this retry
|
|
131
|
+
* @property {string} method the HTTP method
|
|
132
|
+
* @property {number} [status] the retryable response status that triggered the retry, if any
|
|
133
|
+
* @property {Error} [error] the network error that triggered the retry, if any
|
|
134
|
+
*/
|
|
135
|
+
|
|
125
136
|
/**
|
|
126
137
|
* Invokes a best-effort {@link OnRetry} hook, swallowing both synchronous throws and asynchronous
|
|
127
138
|
* rejections so a broken observability callback can never break the retry loop or the request.
|
|
128
139
|
* The hook fires fire-and-forget (never awaited), so it cannot delay a retry. Its own failures are
|
|
129
140
|
* deliberately silent (no signal is emitted) — surfacing them would itself need an observability
|
|
130
141
|
* channel; observability must never affect the request outcome.
|
|
131
|
-
* @param {OnRetry}
|
|
132
|
-
* @param {
|
|
142
|
+
* @param {OnRetry | undefined} onRetry
|
|
143
|
+
* @param {RetryInfo} info
|
|
133
144
|
*/
|
|
134
145
|
function notifyRetry(onRetry, info) {
|
|
135
146
|
if (!onRetry) {
|
|
@@ -139,7 +150,7 @@ function notifyRetry(onRetry, info) {
|
|
|
139
150
|
const result = onRetry(info);
|
|
140
151
|
// An async onRetry returns a promise; sink its rejection here so a rejecting hook can't escape
|
|
141
152
|
// as an unhandled promise rejection (which crashes the process in Node 18+). Not awaited.
|
|
142
|
-
if (result
|
|
153
|
+
if (result instanceof Promise) {
|
|
143
154
|
result.catch(() => {});
|
|
144
155
|
}
|
|
145
156
|
} catch {
|
|
@@ -149,12 +160,7 @@ function notifyRetry(onRetry, info) {
|
|
|
149
160
|
|
|
150
161
|
/**
|
|
151
162
|
* @callback OnRetry
|
|
152
|
-
* @param {
|
|
153
|
-
* @param {number} info.attempt the 1-based number of the retry about to be made
|
|
154
|
-
* @param {number} info.delayMs the wait before this retry
|
|
155
|
-
* @param {string} info.method the HTTP method
|
|
156
|
-
* @param {number} [info.status] the retryable response status that triggered the retry, if any
|
|
157
|
-
* @param {Error} [info.error] the network error that triggered the retry, if any
|
|
163
|
+
* @param {RetryInfo} info
|
|
158
164
|
* @returns {void | Promise<void>} may be async; the return is not awaited (fire-and-forget)
|
|
159
165
|
*/
|
|
160
166
|
|
|
@@ -197,7 +203,13 @@ export function createRetryingFetch(baseFetch, maxRetries, baseDelayMs, onRetry)
|
|
|
197
203
|
for (let attempt = 0; attempt <= attempts; attempt += 1) {
|
|
198
204
|
if (attempt > 0) {
|
|
199
205
|
notifyRetry(onRetry, {
|
|
200
|
-
attempt,
|
|
206
|
+
attempt,
|
|
207
|
+
delayMs: nextDelayMs,
|
|
208
|
+
method,
|
|
209
|
+
status: lastResponse?.status,
|
|
210
|
+
// lastError is a caught throw (unknown); pass it only when it's a real Error. The
|
|
211
|
+
// original value is still rethrown unchanged below if retries are exhausted.
|
|
212
|
+
error: lastError instanceof Error ? lastError : undefined,
|
|
201
213
|
});
|
|
202
214
|
// eslint-disable-next-line no-await-in-loop
|
|
203
215
|
await sleep(nextDelayMs);
|