primitive-admin 1.1.0-alpha.4 → 1.1.0-alpha.41
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 +325 -31
- package/assets/skill/skills/primitive-platform/SKILL.md +281 -0
- package/dist/bin/primitive.d.ts +2 -0
- package/dist/bin/primitive.js +268 -16
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/admins.d.ts +2 -0
- package/dist/src/commands/admins.js +123 -18
- package/dist/src/commands/admins.js.map +1 -1
- package/dist/src/commands/analytics.d.ts +2 -0
- package/dist/src/commands/analytics.js +464 -55
- package/dist/src/commands/analytics.js.map +1 -1
- package/dist/src/commands/apps.d.ts +2 -0
- package/dist/src/commands/apps.js +55 -13
- package/dist/src/commands/apps.js.map +1 -1
- package/dist/src/commands/auth.d.ts +2 -0
- package/dist/src/commands/auth.js +177 -7
- package/dist/src/commands/auth.js.map +1 -1
- package/dist/src/commands/blob-buckets.d.ts +2 -0
- package/dist/src/commands/blob-buckets.js +422 -0
- package/dist/src/commands/blob-buckets.js.map +1 -0
- package/dist/src/commands/catalog.d.ts +2 -0
- package/dist/src/commands/catalog.js +34 -35
- package/dist/src/commands/catalog.js.map +1 -1
- package/dist/src/commands/collection-type-configs.d.ts +2 -0
- package/dist/src/commands/collection-type-configs.js +178 -0
- package/dist/src/commands/collection-type-configs.js.map +1 -0
- package/dist/src/commands/collections.d.ts +2 -0
- package/dist/src/commands/collections.js +515 -0
- package/dist/src/commands/collections.js.map +1 -0
- package/dist/src/commands/comparisons.d.ts +2 -0
- package/dist/src/commands/comparisons.js +6 -6
- package/dist/src/commands/comparisons.js.map +1 -1
- package/dist/src/commands/cron-triggers.d.ts +2 -0
- package/dist/src/commands/cron-triggers.js +357 -0
- package/dist/src/commands/cron-triggers.js.map +1 -0
- package/dist/src/commands/database-types.d.ts +2 -0
- package/dist/src/commands/database-types.js +471 -0
- package/dist/src/commands/database-types.js.map +1 -0
- package/dist/src/commands/databases.d.ts +2 -0
- package/dist/src/commands/databases.js +1462 -83
- package/dist/src/commands/databases.js.map +1 -1
- package/dist/src/commands/documents.d.ts +2 -0
- package/dist/src/commands/documents.js +518 -11
- package/dist/src/commands/documents.js.map +1 -1
- package/dist/src/commands/email-templates.d.ts +2 -0
- package/dist/src/commands/email-templates.js +281 -0
- package/dist/src/commands/email-templates.js.map +1 -0
- package/dist/src/commands/env.d.ts +12 -0
- package/dist/src/commands/env.js +260 -0
- package/dist/src/commands/env.js.map +1 -0
- package/dist/src/commands/group-type-configs.d.ts +2 -0
- package/dist/src/commands/group-type-configs.js +181 -0
- package/dist/src/commands/group-type-configs.js.map +1 -0
- package/dist/src/commands/groups.d.ts +2 -0
- package/dist/src/commands/groups.js +48 -45
- package/dist/src/commands/groups.js.map +1 -1
- package/dist/src/commands/guides.d.ts +159 -0
- package/dist/src/commands/guides.js +449 -35
- package/dist/src/commands/guides.js.map +1 -1
- package/dist/src/commands/init.d.ts +17 -0
- package/dist/src/commands/init.js +811 -201
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/integrations.d.ts +2 -0
- package/dist/src/commands/integrations.js +533 -46
- package/dist/src/commands/integrations.js.map +1 -1
- package/dist/src/commands/llm.d.ts +2 -0
- package/dist/src/commands/llm.js +4 -2
- package/dist/src/commands/llm.js.map +1 -1
- package/dist/src/commands/metadata.d.ts +2 -0
- package/dist/src/commands/metadata.js +194 -0
- package/dist/src/commands/metadata.js.map +1 -0
- package/dist/src/commands/prompts.d.ts +2 -0
- package/dist/src/commands/prompts.js +50 -52
- package/dist/src/commands/prompts.js.map +1 -1
- package/dist/src/commands/rule-sets.d.ts +3 -0
- package/dist/src/commands/rule-sets.js +388 -0
- package/dist/src/commands/rule-sets.js.map +1 -0
- package/dist/src/commands/scripts.d.ts +2 -0
- package/dist/src/commands/scripts.js +667 -0
- package/dist/src/commands/scripts.js.map +1 -0
- package/dist/src/commands/secrets.d.ts +2 -0
- package/dist/src/commands/secrets.js +108 -0
- package/dist/src/commands/secrets.js.map +1 -0
- package/dist/src/commands/skill.d.ts +2 -0
- package/dist/src/commands/skill.js +29 -0
- package/dist/src/commands/skill.js.map +1 -0
- package/dist/src/commands/sync.d.ts +175 -0
- package/dist/src/commands/sync.js +4412 -233
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/tokens.d.ts +2 -0
- package/dist/src/commands/tokens.js +122 -19
- package/dist/src/commands/tokens.js.map +1 -1
- package/dist/src/commands/users.d.ts +2 -0
- package/dist/src/commands/users.js +440 -22
- package/dist/src/commands/users.js.map +1 -1
- package/dist/src/commands/waitlist.d.ts +2 -0
- package/dist/src/commands/waitlist.js +10 -10
- package/dist/src/commands/waitlist.js.map +1 -1
- package/dist/src/commands/webhooks.d.ts +2 -0
- package/dist/src/commands/webhooks.js +391 -0
- package/dist/src/commands/webhooks.js.map +1 -0
- package/dist/src/commands/workflows.d.ts +49 -0
- package/dist/src/commands/workflows.js +1009 -123
- package/dist/src/commands/workflows.js.map +1 -1
- package/dist/src/lib/api-client.d.ts +1283 -0
- package/dist/src/lib/api-client.js +1129 -83
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/auth-flow.d.ts +8 -0
- package/dist/src/lib/block-layout.d.ts +160 -0
- package/dist/src/lib/block-layout.js +451 -0
- package/dist/src/lib/block-layout.js.map +1 -0
- package/dist/src/lib/cli-manifest.d.ts +60 -0
- package/dist/src/lib/cli-manifest.js +70 -0
- package/dist/src/lib/cli-manifest.js.map +1 -0
- package/dist/src/lib/config.d.ts +53 -0
- package/dist/src/lib/config.js +92 -53
- package/dist/src/lib/config.js.map +1 -1
- package/dist/src/lib/confirm-prompt.d.ts +83 -0
- package/dist/src/lib/confirm-prompt.js +110 -0
- package/dist/src/lib/confirm-prompt.js.map +1 -0
- package/dist/src/lib/constants.d.ts +2 -0
- package/dist/src/lib/constants.js +3 -0
- package/dist/src/lib/constants.js.map +1 -0
- package/dist/src/lib/crash-handlers.d.ts +20 -0
- package/dist/src/lib/crash-handlers.js +49 -0
- package/dist/src/lib/crash-handlers.js.map +1 -0
- package/dist/src/lib/credentials-store.d.ts +79 -0
- package/dist/src/lib/credentials-store.js +307 -0
- package/dist/src/lib/credentials-store.js.map +1 -0
- package/dist/src/lib/csv.d.ts +48 -0
- package/dist/src/lib/csv.js +177 -0
- package/dist/src/lib/csv.js.map +1 -0
- package/dist/src/lib/db-codegen/dbFingerprint.d.ts +10 -0
- package/dist/src/lib/db-codegen/dbFingerprint.js +17 -0
- package/dist/src/lib/db-codegen/dbFingerprint.js.map +1 -0
- package/dist/src/lib/db-codegen/dbGenerator.d.ts +111 -0
- package/dist/src/lib/db-codegen/dbGenerator.js +371 -0
- package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -0
- package/dist/src/lib/db-codegen/dbNaming.d.ts +45 -0
- package/dist/src/lib/db-codegen/dbNaming.js +104 -0
- package/dist/src/lib/db-codegen/dbNaming.js.map +1 -0
- package/dist/src/lib/db-codegen/dbTemplates.d.ts +97 -0
- package/dist/src/lib/db-codegen/dbTemplates.js +159 -0
- package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -0
- package/dist/src/lib/db-codegen/dbTsTypes.d.ts +78 -0
- package/dist/src/lib/db-codegen/dbTsTypes.js +112 -0
- package/dist/src/lib/db-codegen/dbTsTypes.js.map +1 -0
- package/dist/src/lib/env-resolver.d.ts +62 -0
- package/dist/src/lib/env-resolver.js +121 -0
- package/dist/src/lib/env-resolver.js.map +1 -0
- package/dist/src/lib/fetch.d.ts +5 -0
- package/dist/src/lib/generated-allowlist.d.ts +28 -0
- package/dist/src/lib/generated-allowlist.js +220 -0
- package/dist/src/lib/generated-allowlist.js.map +1 -0
- package/dist/src/lib/init-config.d.ts +59 -0
- package/dist/src/lib/init-config.js +113 -0
- package/dist/src/lib/init-config.js.map +1 -0
- package/dist/src/lib/migration-nag.d.ts +49 -0
- package/dist/src/lib/migration-nag.js +163 -0
- package/dist/src/lib/migration-nag.js.map +1 -0
- package/dist/src/lib/output.d.ts +86 -0
- package/dist/src/lib/output.js +150 -8
- package/dist/src/lib/output.js.map +1 -1
- package/dist/src/lib/paginate.d.ts +33 -0
- package/dist/src/lib/paginate.js +42 -0
- package/dist/src/lib/paginate.js.map +1 -0
- package/dist/src/lib/project-config.d.ts +97 -0
- package/dist/src/lib/project-config.js +209 -0
- package/dist/src/lib/project-config.js.map +1 -0
- package/dist/src/lib/query-operators.d.ts +43 -0
- package/dist/src/lib/query-operators.js +80 -0
- package/dist/src/lib/query-operators.js.map +1 -0
- package/dist/src/lib/refresh-admin-credentials.d.ts +65 -0
- package/dist/src/lib/refresh-admin-credentials.js +103 -0
- package/dist/src/lib/refresh-admin-credentials.js.map +1 -0
- package/dist/src/lib/resolve-platform.d.ts +45 -0
- package/dist/src/lib/resolve-platform.js +43 -0
- package/dist/src/lib/resolve-platform.js.map +1 -0
- package/dist/src/lib/skill-installer.d.ts +23 -0
- package/dist/src/lib/skill-installer.js +135 -0
- package/dist/src/lib/skill-installer.js.map +1 -0
- package/dist/src/lib/snapshots.d.ts +99 -0
- package/dist/src/lib/snapshots.js +357 -0
- package/dist/src/lib/snapshots.js.map +1 -0
- package/dist/src/lib/sync-paths.d.ts +72 -0
- package/dist/src/lib/sync-paths.js +130 -0
- package/dist/src/lib/sync-paths.js.map +1 -0
- package/dist/src/lib/template.d.ts +97 -0
- package/dist/src/lib/template.js +336 -62
- package/dist/src/lib/template.js.map +1 -1
- package/dist/src/lib/token-inject.d.ts +56 -0
- package/dist/src/lib/token-inject.js +204 -0
- package/dist/src/lib/token-inject.js.map +1 -0
- package/dist/src/lib/toml-database-config.d.ts +152 -0
- package/dist/src/lib/toml-database-config.js +600 -0
- package/dist/src/lib/toml-database-config.js.map +1 -0
- package/dist/src/lib/toml-metadata-config.d.ts +139 -0
- package/dist/src/lib/toml-metadata-config.js +427 -0
- package/dist/src/lib/toml-metadata-config.js.map +1 -0
- package/dist/src/lib/toml-params-validator.d.ts +129 -0
- package/dist/src/lib/toml-params-validator.js +298 -0
- package/dist/src/lib/toml-params-validator.js.map +1 -0
- package/dist/src/lib/version-check.d.ts +10 -0
- package/dist/src/lib/version-check.js +172 -0
- package/dist/src/lib/version-check.js.map +1 -0
- package/dist/src/lib/workflow-apply.d.ts +66 -0
- package/dist/src/lib/workflow-apply.js +117 -0
- package/dist/src/lib/workflow-apply.js.map +1 -0
- package/dist/src/lib/workflow-fragments.d.ts +41 -0
- package/dist/src/lib/workflow-fragments.js +121 -0
- package/dist/src/lib/workflow-fragments.js.map +1 -0
- package/dist/src/lib/workflow-toml-validator.d.ts +95 -0
- package/dist/src/lib/workflow-toml-validator.js +323 -0
- package/dist/src/lib/workflow-toml-validator.js.map +1 -0
- package/dist/src/types/index.d.ts +520 -0
- package/dist/src/validators.d.ts +64 -0
- package/dist/src/validators.js +63 -0
- package/dist/src/validators.js.map +1 -0
- package/package.json +18 -4
|
@@ -1,25 +1,167 @@
|
|
|
1
1
|
import { loadCredentials, saveCredentials, isTokenExpiringSoon, } from "./config.js";
|
|
2
2
|
import { fetchWithTLS } from "./fetch.js";
|
|
3
|
+
import { paginateAll } from "./paginate.js";
|
|
4
|
+
import { RefreshError, refreshAdminCredentials, } from "./refresh-admin-credentials.js";
|
|
3
5
|
export class ApiError extends Error {
|
|
4
6
|
statusCode;
|
|
5
7
|
code;
|
|
6
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Structured `details` payload from `corsErrorResponse`.
|
|
10
|
+
*
|
|
11
|
+
* The server's error envelope can emit `details` as either:
|
|
12
|
+
* - an array of validation issues (e.g. `[{ path, message }, ...]`) —
|
|
13
|
+
* this is what most legacy endpoints produce, and what sync.ts walks
|
|
14
|
+
* via `Array.isArray(err.details)` / `for (const detail of ...)`.
|
|
15
|
+
* - a record of structured offender fields (e.g. `{ refs, operations,
|
|
16
|
+
* opCount, line, column, ... }`) — emitted by the issue #666 schema
|
|
17
|
+
* gate and consumed by the typed exception subclasses below.
|
|
18
|
+
*
|
|
19
|
+
* Callers must narrow before use: `Array.isArray(err.details)` for the
|
|
20
|
+
* legacy shape, otherwise treat as `Record<string, any>`.
|
|
21
|
+
*/
|
|
22
|
+
details;
|
|
23
|
+
constructor(message, statusCode, code, details) {
|
|
7
24
|
super(message);
|
|
8
25
|
this.statusCode = statusCode;
|
|
9
26
|
this.code = code;
|
|
10
27
|
this.name = "ApiError";
|
|
28
|
+
if (details !== undefined) {
|
|
29
|
+
this.details = details;
|
|
30
|
+
}
|
|
11
31
|
}
|
|
12
32
|
}
|
|
13
33
|
export class ConflictError extends ApiError {
|
|
14
34
|
serverModifiedAt;
|
|
15
35
|
expectedModifiedAt;
|
|
16
|
-
constructor(message, serverModifiedAt, expectedModifiedAt) {
|
|
17
|
-
super(message, 409, "CONFLICT");
|
|
36
|
+
constructor(message, serverModifiedAt, expectedModifiedAt, details) {
|
|
37
|
+
super(message, 409, "CONFLICT", details);
|
|
18
38
|
this.serverModifiedAt = serverModifiedAt;
|
|
19
39
|
this.expectedModifiedAt = expectedModifiedAt;
|
|
20
40
|
this.name = "ConflictError";
|
|
21
41
|
}
|
|
22
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Extract a human-readable error message + structured fields from a non-OK
|
|
45
|
+
* HTTP response body. Single source of truth used by every error-handler call
|
|
46
|
+
* site in this file (see issue #684).
|
|
47
|
+
*/
|
|
48
|
+
export function parseErrorResponse(response, text, path) {
|
|
49
|
+
// Empty body → fall back to status code.
|
|
50
|
+
if (!text) {
|
|
51
|
+
return { message: `HTTP ${response.status}` };
|
|
52
|
+
}
|
|
53
|
+
let errorData;
|
|
54
|
+
try {
|
|
55
|
+
errorData = JSON.parse(text);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Non-JSON body. Surface the existing `<!DOCTYPE` special-case (an HTML
|
|
59
|
+
// 404 page from hitting the wrong path) so we don't regress the helpful
|
|
60
|
+
// "API endpoint not found" message at api-client.ts:343.
|
|
61
|
+
if (text.includes("<!DOCTYPE")) {
|
|
62
|
+
const where = path ? `: ${path}` : "";
|
|
63
|
+
return {
|
|
64
|
+
message: `API endpoint not found${where}. Make sure the server is running.`,
|
|
65
|
+
htmlNotFound: true,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// Other non-JSON bodies (e.g. plain-text 502 from a proxy) — surface the
|
|
69
|
+
// raw text so the operator at least sees what the server returned.
|
|
70
|
+
return { message: text };
|
|
71
|
+
}
|
|
72
|
+
// Server's standard envelope uses `error`; ConflictError + integrations
|
|
73
|
+
// proxy use `message`. Prefer `error` (more common), fall back to `message`.
|
|
74
|
+
const message = (typeof errorData?.error === "string" && errorData.error) ||
|
|
75
|
+
(typeof errorData?.message === "string" && errorData.message) ||
|
|
76
|
+
`HTTP ${response.status}`;
|
|
77
|
+
// Per issue #666 addendum A1, `code` may be at the top level or nested
|
|
78
|
+
// under `details.code` when the server's bespoke envelope didn't flatten.
|
|
79
|
+
const code = (typeof errorData?.code === "string" ? errorData.code : undefined) ??
|
|
80
|
+
(typeof errorData?.details?.code === "string"
|
|
81
|
+
? errorData.details.code
|
|
82
|
+
: undefined);
|
|
83
|
+
// Accept either an array (legacy) or a plain object (#666 schema gate).
|
|
84
|
+
const details = Array.isArray(errorData?.details)
|
|
85
|
+
? errorData.details
|
|
86
|
+
: errorData?.details && typeof errorData.details === "object"
|
|
87
|
+
? errorData.details
|
|
88
|
+
: undefined;
|
|
89
|
+
return { message, code, details, raw: errorData };
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Typed exception classes for the database-schema feature (issue #666).
|
|
93
|
+
* Each maps 1:1 to a server `code` value emitted from the op-edit or
|
|
94
|
+
* schema-edit gate. They all extend ApiError so existing catch-all paths
|
|
95
|
+
* continue to work; specialized catch blocks can branch on `instanceof`.
|
|
96
|
+
*
|
|
97
|
+
* Per round-2 addendum A1, `details` is always preserved so callers can
|
|
98
|
+
* extract structured offender lists (refs[], operations[], etc.).
|
|
99
|
+
*/
|
|
100
|
+
export class SchemaRequiredError extends ApiError {
|
|
101
|
+
constructor(message, details) {
|
|
102
|
+
super(message, 422, "SCHEMA_REQUIRED", details);
|
|
103
|
+
this.name = "SchemaRequiredError";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function detailsRecord(details) {
|
|
107
|
+
return details && !Array.isArray(details) && typeof details === "object"
|
|
108
|
+
? details
|
|
109
|
+
: undefined;
|
|
110
|
+
}
|
|
111
|
+
export class OperationRefError extends ApiError {
|
|
112
|
+
constructor(message, details) {
|
|
113
|
+
super(message, 422, "OPERATION_REFERENCES_UNDEFINED", details);
|
|
114
|
+
this.name = "OperationRefError";
|
|
115
|
+
}
|
|
116
|
+
get refs() {
|
|
117
|
+
const d = detailsRecord(this.details);
|
|
118
|
+
return Array.isArray(d?.refs) ? d.refs : [];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
export class SchemaBreaksOpsError extends ApiError {
|
|
122
|
+
constructor(message, details) {
|
|
123
|
+
super(message, 422, "SCHEMA_BREAKS_OPERATIONS", details);
|
|
124
|
+
this.name = "SchemaBreaksOpsError";
|
|
125
|
+
}
|
|
126
|
+
get operations() {
|
|
127
|
+
const d = detailsRecord(this.details);
|
|
128
|
+
return Array.isArray(d?.operations) ? d.operations : [];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
export class SchemaHasUncheckableOpsError extends ApiError {
|
|
132
|
+
constructor(message, details) {
|
|
133
|
+
super(message, 422, "SCHEMA_HAS_UNCHECKABLE_OPS", details);
|
|
134
|
+
this.name = "SchemaHasUncheckableOpsError";
|
|
135
|
+
}
|
|
136
|
+
get operations() {
|
|
137
|
+
const d = detailsRecord(this.details);
|
|
138
|
+
return Array.isArray(d?.operations) ? d.operations : [];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
export class TomlParseError extends ApiError {
|
|
142
|
+
constructor(message, details) {
|
|
143
|
+
super(message, 400, "TOML_PARSE_ERROR", details);
|
|
144
|
+
this.name = "TomlParseError";
|
|
145
|
+
}
|
|
146
|
+
get line() {
|
|
147
|
+
const d = detailsRecord(this.details);
|
|
148
|
+
return typeof d?.line === "number" ? d.line : undefined;
|
|
149
|
+
}
|
|
150
|
+
get column() {
|
|
151
|
+
const d = detailsRecord(this.details);
|
|
152
|
+
return typeof d?.column === "number" ? d.column : undefined;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
export class OpsExistError extends ApiError {
|
|
156
|
+
constructor(message, details) {
|
|
157
|
+
super(message, 409, "OPS_EXIST", details);
|
|
158
|
+
this.name = "OpsExistError";
|
|
159
|
+
}
|
|
160
|
+
get opCount() {
|
|
161
|
+
const d = detailsRecord(this.details);
|
|
162
|
+
return typeof d?.opCount === "number" ? d.opCount : 0;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
23
165
|
export class ApiClient {
|
|
24
166
|
credentials = null;
|
|
25
167
|
constructor() {
|
|
@@ -39,39 +181,24 @@ export class ApiClient {
|
|
|
39
181
|
return this.credentials;
|
|
40
182
|
}
|
|
41
183
|
async refreshToken() {
|
|
42
|
-
if (!this.credentials
|
|
43
|
-
throw new ApiError("
|
|
184
|
+
if (!this.credentials) {
|
|
185
|
+
throw new ApiError("Not logged in. Run 'primitive login' first.", 401);
|
|
44
186
|
}
|
|
45
|
-
const url = `${this.credentials.serverUrl}/admin/api/auth/refresh`;
|
|
46
187
|
try {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
};
|
|
50
|
-
if (this.credentials.globalAdminAppId) {
|
|
51
|
-
headers["X-Global-Admin-App-Id"] = this.credentials.globalAdminAppId;
|
|
52
|
-
}
|
|
53
|
-
const response = await fetchWithTLS(url, {
|
|
54
|
-
method: "POST",
|
|
55
|
-
headers,
|
|
56
|
-
body: JSON.stringify({ refreshToken: this.credentials.refreshToken }),
|
|
57
|
-
});
|
|
58
|
-
if (!response.ok) {
|
|
59
|
-
throw new ApiError("Token refresh failed. Please login again.", 401);
|
|
60
|
-
}
|
|
61
|
-
const data = await response.json();
|
|
62
|
-
// Update credentials with new tokens
|
|
63
|
-
this.credentials = {
|
|
64
|
-
...this.credentials,
|
|
65
|
-
accessToken: data.accessToken || data.token,
|
|
66
|
-
refreshToken: data.refreshToken || this.credentials.refreshToken,
|
|
67
|
-
expiresAt: data.expiresAt,
|
|
68
|
-
};
|
|
188
|
+
const updated = await refreshAdminCredentials(this.credentials);
|
|
189
|
+
this.credentials = updated;
|
|
69
190
|
saveCredentials(this.credentials);
|
|
70
191
|
}
|
|
71
|
-
catch (
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
|
|
192
|
+
catch (err) {
|
|
193
|
+
if (err instanceof RefreshError) {
|
|
194
|
+
// Preserve historical behavior: ApiClient surfaces refresh failures
|
|
195
|
+
// as 401s regardless of whether the underlying cause was a network
|
|
196
|
+
// error or a server-side rejection. Callers that need a finer
|
|
197
|
+
// distinction (e.g. `primitive token`) consume RefreshError directly
|
|
198
|
+
// from refresh-admin-credentials.ts instead of going through here.
|
|
199
|
+
throw new ApiError("Token refresh failed. Please login again.", 401);
|
|
200
|
+
}
|
|
201
|
+
throw err;
|
|
75
202
|
}
|
|
76
203
|
}
|
|
77
204
|
async request(path, options = {}) {
|
|
@@ -92,20 +219,42 @@ export class ApiClient {
|
|
|
92
219
|
});
|
|
93
220
|
const text = await response.text();
|
|
94
221
|
if (!response.ok) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
222
|
+
const parsed = parseErrorResponse(response, text, path);
|
|
223
|
+
// Preserve the `<!DOCTYPE` → 404 ApiError shape (status forced to 404).
|
|
224
|
+
if (parsed.htmlNotFound) {
|
|
225
|
+
throw new ApiError(parsed.message, 404);
|
|
98
226
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
227
|
+
// Narrow details to the record shape for the issue #666 typed-exception
|
|
228
|
+
// dispatch below. Conflict metadata may live under `details.*` (canonical
|
|
229
|
+
// location per A1) or on the top-level envelope (legacy).
|
|
230
|
+
const detailsRecord = parsed.details && !Array.isArray(parsed.details)
|
|
231
|
+
? parsed.details
|
|
232
|
+
: undefined;
|
|
233
|
+
const serverModifiedAt = detailsRecord?.serverModifiedAt ?? parsed.raw?.serverModifiedAt;
|
|
234
|
+
const expectedModifiedAt = detailsRecord?.expectedModifiedAt ?? parsed.raw?.expectedModifiedAt;
|
|
235
|
+
// Typed exceptions for the schema-feature (issue #666).
|
|
236
|
+
if (response.status === 409 && parsed.code === "CONFLICT") {
|
|
237
|
+
throw new ConflictError(parsed.message, serverModifiedAt, expectedModifiedAt, detailsRecord);
|
|
238
|
+
}
|
|
239
|
+
if (response.status === 409 && parsed.code === "OPS_EXIST") {
|
|
240
|
+
throw new OpsExistError(parsed.message, detailsRecord);
|
|
241
|
+
}
|
|
242
|
+
if (response.status === 400 && parsed.code === "TOML_PARSE_ERROR") {
|
|
243
|
+
throw new TomlParseError(parsed.message, detailsRecord);
|
|
244
|
+
}
|
|
245
|
+
if (response.status === 422 && parsed.code === "SCHEMA_REQUIRED") {
|
|
246
|
+
throw new SchemaRequiredError(parsed.message, detailsRecord);
|
|
247
|
+
}
|
|
248
|
+
if (response.status === 422 && parsed.code === "OPERATION_REFERENCES_UNDEFINED") {
|
|
249
|
+
throw new OperationRefError(parsed.message, detailsRecord);
|
|
250
|
+
}
|
|
251
|
+
if (response.status === 422 && parsed.code === "SCHEMA_BREAKS_OPERATIONS") {
|
|
252
|
+
throw new SchemaBreaksOpsError(parsed.message, detailsRecord);
|
|
104
253
|
}
|
|
105
|
-
if (response.status ===
|
|
106
|
-
throw new
|
|
254
|
+
if (response.status === 422 && parsed.code === "SCHEMA_HAS_UNCHECKABLE_OPS") {
|
|
255
|
+
throw new SchemaHasUncheckableOpsError(parsed.message, detailsRecord);
|
|
107
256
|
}
|
|
108
|
-
throw new ApiError(
|
|
257
|
+
throw new ApiError(parsed.message, response.status, parsed.code, parsed.details);
|
|
109
258
|
}
|
|
110
259
|
return text ? JSON.parse(text) : null;
|
|
111
260
|
}
|
|
@@ -155,7 +304,18 @@ export class ApiClient {
|
|
|
155
304
|
// APPS
|
|
156
305
|
// ============================================
|
|
157
306
|
async listApps() {
|
|
158
|
-
|
|
307
|
+
// The server paginates cursor-style (default pageSize=25, cap=100). Historically
|
|
308
|
+
// this method issued a single un-paginated GET and silently truncated to the
|
|
309
|
+
// first page, which broke `primitive use <app-id>` and `primitive apps list`
|
|
310
|
+
// for admins with more than 25 apps (see #436). Iterate every page.
|
|
311
|
+
const apps = await paginateAll(async (cursor) => {
|
|
312
|
+
const qs = new URLSearchParams({ limit: "100" });
|
|
313
|
+
if (cursor)
|
|
314
|
+
qs.set("cursor", cursor);
|
|
315
|
+
const resp = await this.get(`/admin/api/admins/me/apps?${qs.toString()}`);
|
|
316
|
+
return { items: Array.isArray(resp.apps) ? resp.apps : [], nextCursor: resp.nextCursor };
|
|
317
|
+
});
|
|
318
|
+
return { apps };
|
|
159
319
|
}
|
|
160
320
|
async createApp(data) {
|
|
161
321
|
return this.post("/admin/api/apps", data);
|
|
@@ -181,8 +341,32 @@ export class ApiClient {
|
|
|
181
341
|
async addUserByEmail(appId, data) {
|
|
182
342
|
return this.post(`/admin/api/apps/${appId}/users/add-by-email`, data);
|
|
183
343
|
}
|
|
184
|
-
async
|
|
185
|
-
return this.
|
|
344
|
+
async mintTestJwt(appId, userId, role) {
|
|
345
|
+
return this.post(`/admin/api/apps/${appId}/users/${userId}/mint-test-jwt`, role ? { role } : {});
|
|
346
|
+
}
|
|
347
|
+
async rebuildUserSearchText(appId) {
|
|
348
|
+
return this.post(`/admin/api/apps/${appId}/users/rebuild-search-text`, {});
|
|
349
|
+
}
|
|
350
|
+
async listUsers(appId, params) {
|
|
351
|
+
const query = new URLSearchParams();
|
|
352
|
+
if (params?.name)
|
|
353
|
+
query.set("name", params.name);
|
|
354
|
+
if (params?.email)
|
|
355
|
+
query.set("email", params.email);
|
|
356
|
+
if (params?.userId)
|
|
357
|
+
query.set("userId", params.userId);
|
|
358
|
+
if (params?.limit)
|
|
359
|
+
query.set("limit", String(params.limit));
|
|
360
|
+
if (params?.cursor)
|
|
361
|
+
query.set("cursor", params.cursor);
|
|
362
|
+
const path = query.toString()
|
|
363
|
+
? `/app/${appId}/api/users?${query.toString()}`
|
|
364
|
+
: `/app/${appId}/api/users`;
|
|
365
|
+
const result = await this.get(path);
|
|
366
|
+
return {
|
|
367
|
+
items: result?.items ?? (Array.isArray(result) ? result : []),
|
|
368
|
+
nextCursor: result?.nextCursor ?? null,
|
|
369
|
+
};
|
|
186
370
|
}
|
|
187
371
|
async removeUser(appId, userId) {
|
|
188
372
|
return this.delete(`/app/${appId}/api/users/${userId}`);
|
|
@@ -196,14 +380,30 @@ export class ApiClient {
|
|
|
196
380
|
async transferAdminOwnership(appId, adminId) {
|
|
197
381
|
return this.post(`/admin/api/apps/${appId}/admins/transfer-ownership`, { adminId });
|
|
198
382
|
}
|
|
383
|
+
// App-level console admin management
|
|
384
|
+
async listAppAdmins(appId) {
|
|
385
|
+
return this.get(`/admin/api/apps/${appId}/admins`);
|
|
386
|
+
}
|
|
387
|
+
async addAppAdmin(appId, data) {
|
|
388
|
+
return this.post(`/admin/api/apps/${appId}/admins`, data);
|
|
389
|
+
}
|
|
390
|
+
async removeAppAdmin(appId, adminId) {
|
|
391
|
+
return this.delete(`/admin/api/apps/${appId}/admins/${adminId}`);
|
|
392
|
+
}
|
|
393
|
+
async listAppAdminInvitations(appId) {
|
|
394
|
+
return this.get(`/admin/api/apps/${appId}/admin-invitations`);
|
|
395
|
+
}
|
|
396
|
+
async deleteAppAdminInvitation(appId, invitationId) {
|
|
397
|
+
return this.delete(`/admin/api/apps/${appId}/admin-invitations/${invitationId}`);
|
|
398
|
+
}
|
|
199
399
|
async transferDocumentOwnership(appId, documentId, newOwnerId) {
|
|
200
400
|
return this.post(`/app/${appId}/api/documents/${documentId}/permissions/transfer`, { newOwnerId });
|
|
201
401
|
}
|
|
202
402
|
// ============================================
|
|
203
403
|
// INVITATIONS
|
|
204
404
|
// ============================================
|
|
205
|
-
async listInvitations(appId) {
|
|
206
|
-
return this.get(`/app/${appId}/api/invitations
|
|
405
|
+
async listInvitations(appId, params) {
|
|
406
|
+
return this.get(`/app/${appId}/api/invitations`, params);
|
|
207
407
|
}
|
|
208
408
|
async createInvitation(appId, data) {
|
|
209
409
|
return this.post(`/app/${appId}/api/invitations`, data);
|
|
@@ -228,6 +428,19 @@ export class ApiClient {
|
|
|
228
428
|
return this.delete(`/app/${appId}/api/waitlist/${waitlistId}`);
|
|
229
429
|
}
|
|
230
430
|
// ============================================
|
|
431
|
+
// DEFERRED GRANTS
|
|
432
|
+
// ============================================
|
|
433
|
+
async listDeferredGrants(appId, params) {
|
|
434
|
+
const result = await this.get(`/app/${appId}/api/deferred-grants`, params);
|
|
435
|
+
return {
|
|
436
|
+
grants: result?.grants ?? [],
|
|
437
|
+
nextCursor: result?.nextCursor ?? null,
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
async revokeDeferredGrant(appId, deferredId, type) {
|
|
441
|
+
return this.delete(`/app/${appId}/api/deferred-grants/${deferredId}?type=${type}`);
|
|
442
|
+
}
|
|
443
|
+
// ============================================
|
|
231
444
|
// INTEGRATIONS
|
|
232
445
|
// ============================================
|
|
233
446
|
async listIntegrations(appId, params) {
|
|
@@ -260,6 +473,10 @@ export class ApiClient {
|
|
|
260
473
|
const result = await this.get(`/admin/api/apps/${appId}/integrations/${integrationId}/logs`, params);
|
|
261
474
|
return result?.items ?? [];
|
|
262
475
|
}
|
|
476
|
+
async listWorkflowRunIntegrationLogs(appId, runId, params) {
|
|
477
|
+
const result = await this.get(`/admin/api/apps/${appId}/workflows/runs/${runId}/integration-logs`, params);
|
|
478
|
+
return result?.items ?? [];
|
|
479
|
+
}
|
|
263
480
|
async listIntegrationSecrets(appId, integrationId, params) {
|
|
264
481
|
const result = await this.get(`/admin/api/apps/${appId}/integrations/${integrationId}/secrets`, params);
|
|
265
482
|
return result?.items ?? [];
|
|
@@ -274,6 +491,113 @@ export class ApiClient {
|
|
|
274
491
|
});
|
|
275
492
|
}
|
|
276
493
|
// ============================================
|
|
494
|
+
// APP SECRETS
|
|
495
|
+
// ============================================
|
|
496
|
+
async listAppSecrets(appId) {
|
|
497
|
+
const result = await this.get(`/admin/api/apps/${appId}/secrets`);
|
|
498
|
+
return result?.items ?? [];
|
|
499
|
+
}
|
|
500
|
+
async createAppSecret(appId, payload) {
|
|
501
|
+
return this.post(`/admin/api/apps/${appId}/secrets`, payload);
|
|
502
|
+
}
|
|
503
|
+
async updateAppSecret(appId, secretId, payload) {
|
|
504
|
+
return this.put(`/admin/api/apps/${appId}/secrets/${secretId}`, payload);
|
|
505
|
+
}
|
|
506
|
+
async upsertAppSecret(appId, key, payload) {
|
|
507
|
+
return this.put(`/admin/api/apps/${appId}/secrets/by-key/${key}`, payload);
|
|
508
|
+
}
|
|
509
|
+
async deleteAppSecret(appId, secretId) {
|
|
510
|
+
return this.delete(`/admin/api/apps/${appId}/secrets/${secretId}`);
|
|
511
|
+
}
|
|
512
|
+
// ============================================
|
|
513
|
+
// WEBHOOKS
|
|
514
|
+
// ============================================
|
|
515
|
+
async listWebhooks(appId, params) {
|
|
516
|
+
const result = await this.get(`/admin/api/apps/${appId}/webhooks`, params);
|
|
517
|
+
return {
|
|
518
|
+
items: result?.items ?? [],
|
|
519
|
+
nextCursor: result?.nextCursor ?? null,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
async getWebhook(appId, webhookId) {
|
|
523
|
+
return this.get(`/admin/api/apps/${appId}/webhooks/${webhookId}`);
|
|
524
|
+
}
|
|
525
|
+
async createWebhook(appId, payload) {
|
|
526
|
+
return this.post(`/admin/api/apps/${appId}/webhooks`, payload);
|
|
527
|
+
}
|
|
528
|
+
async updateWebhook(appId, webhookId, payload, expectedModifiedAt) {
|
|
529
|
+
const body = expectedModifiedAt ? { ...payload, expectedModifiedAt } : payload;
|
|
530
|
+
return this.patch(`/admin/api/apps/${appId}/webhooks/${webhookId}`, body);
|
|
531
|
+
}
|
|
532
|
+
async deleteWebhook(appId, webhookId) {
|
|
533
|
+
return this.delete(`/admin/api/apps/${appId}/webhooks/${webhookId}`);
|
|
534
|
+
}
|
|
535
|
+
async rotateWebhookSecret(appId, webhookId, payload) {
|
|
536
|
+
return this.post(`/admin/api/apps/${appId}/webhooks/${webhookId}/rotate-secret`, payload);
|
|
537
|
+
}
|
|
538
|
+
async listWebhookEvents(appId, webhookId, params) {
|
|
539
|
+
const result = await this.get(`/admin/api/apps/${appId}/webhooks/${webhookId}/events`, params);
|
|
540
|
+
return result;
|
|
541
|
+
}
|
|
542
|
+
async testWebhook(appId, webhookId, payload) {
|
|
543
|
+
return this.post(`/admin/api/apps/${appId}/webhooks/${webhookId}/test`, payload || {});
|
|
544
|
+
}
|
|
545
|
+
// ============================================
|
|
546
|
+
// CRON TRIGGERS
|
|
547
|
+
// ============================================
|
|
548
|
+
async listCronTriggers(appId) {
|
|
549
|
+
const result = await this.get(`/app/${appId}/api/cron-triggers`);
|
|
550
|
+
return { items: result?.items ?? [] };
|
|
551
|
+
}
|
|
552
|
+
async getCronTrigger(appId, triggerId) {
|
|
553
|
+
return this.get(`/app/${appId}/api/cron-triggers/${triggerId}`);
|
|
554
|
+
}
|
|
555
|
+
async createCronTrigger(appId, payload) {
|
|
556
|
+
return this.post(`/app/${appId}/api/cron-triggers`, payload);
|
|
557
|
+
}
|
|
558
|
+
async updateCronTrigger(appId, triggerId, payload) {
|
|
559
|
+
return this.put(`/app/${appId}/api/cron-triggers/${triggerId}`, payload);
|
|
560
|
+
}
|
|
561
|
+
async deleteCronTrigger(appId, triggerId) {
|
|
562
|
+
return this.delete(`/app/${appId}/api/cron-triggers/${triggerId}`);
|
|
563
|
+
}
|
|
564
|
+
async pauseCronTrigger(appId, triggerId) {
|
|
565
|
+
return this.post(`/app/${appId}/api/cron-triggers/${triggerId}/pause`, {});
|
|
566
|
+
}
|
|
567
|
+
async resumeCronTrigger(appId, triggerId) {
|
|
568
|
+
return this.post(`/app/${appId}/api/cron-triggers/${triggerId}/resume`, {});
|
|
569
|
+
}
|
|
570
|
+
async testCronTrigger(appId, triggerId) {
|
|
571
|
+
return this.post(`/app/${appId}/api/cron-triggers/${triggerId}/test`, {});
|
|
572
|
+
}
|
|
573
|
+
// ============================================
|
|
574
|
+
// ITERATIONS (iterate-users introspection / reset — #1209)
|
|
575
|
+
// ============================================
|
|
576
|
+
async listIterations(appId) {
|
|
577
|
+
const result = await this.get(`/app/${appId}/api/iterations`);
|
|
578
|
+
return { items: result?.items ?? [] };
|
|
579
|
+
}
|
|
580
|
+
async getIteration(appId, iterationName) {
|
|
581
|
+
return this.get(`/app/${appId}/api/iterations/${encodeURIComponent(iterationName)}`);
|
|
582
|
+
}
|
|
583
|
+
async resetIteration(appId, iterationName) {
|
|
584
|
+
return this.post(`/app/${appId}/api/iterations/${encodeURIComponent(iterationName)}/reset`, {});
|
|
585
|
+
}
|
|
586
|
+
// ============================================
|
|
587
|
+
// RESOURCE METADATA (values — issue #1352)
|
|
588
|
+
// ============================================
|
|
589
|
+
async readResourceMetadata(appId, resourceType, resourceId, category) {
|
|
590
|
+
return this.get(`/app/${appId}/api/resources/${encodeURIComponent(resourceType)}/${encodeURIComponent(resourceId)}/metadata/${encodeURIComponent(category)}`);
|
|
591
|
+
}
|
|
592
|
+
async writeResourceMetadata(appId, resourceType, resourceId, category, data) {
|
|
593
|
+
return this.put(`/app/${appId}/api/resources/${encodeURIComponent(resourceType)}/${encodeURIComponent(resourceId)}/metadata/${encodeURIComponent(category)}`, { data });
|
|
594
|
+
}
|
|
595
|
+
async batchReadResourceMetadata(appId, requests) {
|
|
596
|
+
return this.post(`/app/${appId}/api/resources/metadata/batch`, {
|
|
597
|
+
requests,
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
// ============================================
|
|
277
601
|
// PROMPTS
|
|
278
602
|
// ============================================
|
|
279
603
|
async listPrompts(appId, params) {
|
|
@@ -325,6 +649,55 @@ export class ApiClient {
|
|
|
325
649
|
async duplicatePromptConfig(appId, promptId, configId, payload) {
|
|
326
650
|
return this.post(`/admin/api/apps/${appId}/prompts/${promptId}/configs/${configId}/duplicate`, payload || {});
|
|
327
651
|
}
|
|
652
|
+
// ============================================
|
|
653
|
+
// SCRIPTS (Rhai transforms) — #1000 (prompt-model convergence)
|
|
654
|
+
// ============================================
|
|
655
|
+
//
|
|
656
|
+
// The `Script` model is the authoring surface for `script` workflow
|
|
657
|
+
// steps. A `Script` is a HEADER (name/description/activeConfigId); the
|
|
658
|
+
// Rhai body lives on versioned `ScriptConfig` rows resolved LIVE at run
|
|
659
|
+
// time. CLI `sync push` reads `transforms/*.rhai` files and reconciles
|
|
660
|
+
// them: a new file → create script (mints a default config); a changed
|
|
661
|
+
// file → create a new config + activate it (zero fan-out — referencing
|
|
662
|
+
// workflows pick up the new body on their next run).
|
|
663
|
+
async listScripts(appId) {
|
|
664
|
+
const result = await this.get(`/admin/api/apps/${appId}/scripts`);
|
|
665
|
+
return { items: result?.items ?? [] };
|
|
666
|
+
}
|
|
667
|
+
async getScript(appId, scriptId) {
|
|
668
|
+
return this.get(`/admin/api/apps/${appId}/scripts/${scriptId}`);
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* List a script's versioned configs (the `ScriptConfig` rows). Each config
|
|
672
|
+
* is a distinct block version: `configId` is the stable version selector,
|
|
673
|
+
* `contentHash` is its content identity, and `status` reports whether it is
|
|
674
|
+
* `active`/`draft`/`archived`. The script header's `activeConfigId` names
|
|
675
|
+
* the live version. Used by `scripts configs list` and by the script test
|
|
676
|
+
* commands to run against a specific pinned version.
|
|
677
|
+
*/
|
|
678
|
+
async listScriptConfigs(appId, scriptId) {
|
|
679
|
+
const result = await this.get(`/admin/api/apps/${appId}/scripts/${scriptId}/configs`);
|
|
680
|
+
return { items: result?.items ?? [] };
|
|
681
|
+
}
|
|
682
|
+
async createScript(appId, payload) {
|
|
683
|
+
// Mints the script header + a default `active` config carrying the body.
|
|
684
|
+
return this.post(`/admin/api/apps/${appId}/scripts`, payload);
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Push a new body for an existing script by creating a new `ScriptConfig`
|
|
688
|
+
* and activating it (the prompt-model "edit → activate" flow). Referencing
|
|
689
|
+
* workflows pick up the new body on their next run with no fan-out. The
|
|
690
|
+
* config name is unique per script, so we mint a timestamped name.
|
|
691
|
+
*/
|
|
692
|
+
async pushScriptBody(appId, scriptId, body) {
|
|
693
|
+
const configName = `sync-${Date.now()}`;
|
|
694
|
+
const config = await this.post(`/admin/api/apps/${appId}/scripts/${scriptId}/configs`, { configName, body });
|
|
695
|
+
await this.post(`/admin/api/apps/${appId}/scripts/${scriptId}/configs/${config.configId}/activate`, {});
|
|
696
|
+
return config;
|
|
697
|
+
}
|
|
698
|
+
async deleteScript(appId, scriptId) {
|
|
699
|
+
return this.delete(`/admin/api/apps/${appId}/scripts/${scriptId}`);
|
|
700
|
+
}
|
|
328
701
|
async getPromptSchema(appId, promptId) {
|
|
329
702
|
return this.get(`/admin/api/apps/${appId}/prompts/${promptId}/schema`);
|
|
330
703
|
}
|
|
@@ -415,14 +788,8 @@ export class ApiClient {
|
|
|
415
788
|
});
|
|
416
789
|
const text = await response.text();
|
|
417
790
|
if (!response.ok) {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
errorData = JSON.parse(text);
|
|
421
|
-
}
|
|
422
|
-
catch {
|
|
423
|
-
errorData = { message: text || `HTTP ${response.status}` };
|
|
424
|
-
}
|
|
425
|
-
throw new ApiError(errorData.message || `HTTP ${response.status}`, response.status, errorData.code);
|
|
791
|
+
const parsed = parseErrorResponse(response, text, url);
|
|
792
|
+
throw new ApiError(parsed.message, response.status, parsed.code, parsed.details);
|
|
426
793
|
}
|
|
427
794
|
return text ? JSON.parse(text) : null;
|
|
428
795
|
}
|
|
@@ -442,14 +809,8 @@ export class ApiClient {
|
|
|
442
809
|
});
|
|
443
810
|
if (!response.ok) {
|
|
444
811
|
const text = await response.text();
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
errorData = JSON.parse(text);
|
|
448
|
-
}
|
|
449
|
-
catch {
|
|
450
|
-
errorData = { message: text || `HTTP ${response.status}` };
|
|
451
|
-
}
|
|
452
|
-
throw new ApiError(errorData.message || `HTTP ${response.status}`, response.status, errorData.code);
|
|
812
|
+
const parsed = parseErrorResponse(response, text, url);
|
|
813
|
+
throw new ApiError(parsed.message, response.status, parsed.code, parsed.details);
|
|
453
814
|
}
|
|
454
815
|
const arrayBuffer = await response.arrayBuffer();
|
|
455
816
|
return {
|
|
@@ -519,6 +880,16 @@ export class ApiClient {
|
|
|
519
880
|
async getWorkflowRunStatus(appId, workflowId, runId) {
|
|
520
881
|
return this.get(`/admin/api/apps/${appId}/workflows/${workflowId}/runs/${runId}/status`);
|
|
521
882
|
}
|
|
883
|
+
async getWorkflowStepRuns(appId, workflowId, runId) {
|
|
884
|
+
const result = await this.get(`/admin/api/apps/${appId}/workflows/${workflowId}/runs/${runId}/steps`);
|
|
885
|
+
return { items: result?.items ?? [] };
|
|
886
|
+
}
|
|
887
|
+
async getWorkflowAnalytics(appId, params) {
|
|
888
|
+
return this.get(`/app/${appId}/api/analytics/workflows/overview`, params);
|
|
889
|
+
}
|
|
890
|
+
async getTopWorkflows(appId, params) {
|
|
891
|
+
return this.get(`/app/${appId}/api/analytics/workflows/top`, params);
|
|
892
|
+
}
|
|
522
893
|
// ============================================
|
|
523
894
|
// WORKFLOW CONFIGURATIONS
|
|
524
895
|
// ============================================
|
|
@@ -547,21 +918,54 @@ export class ApiClient {
|
|
|
547
918
|
// ============================================
|
|
548
919
|
// ANALYTICS
|
|
549
920
|
// ============================================
|
|
550
|
-
async getAnalyticsOverview(appId, params) {
|
|
551
|
-
return this.get(`/app/${appId}/api/analytics/overview`, params);
|
|
552
|
-
}
|
|
553
921
|
async getAnalyticsTopUsers(appId, params) {
|
|
554
922
|
return this.get(`/app/${appId}/api/analytics/users/top`, params);
|
|
555
923
|
}
|
|
556
|
-
async getAnalyticsUserTimeline(appId, userUlid, params) {
|
|
557
|
-
return this.get(`/app/${appId}/api/analytics/users/${userUlid}/timeline`, params);
|
|
558
|
-
}
|
|
559
|
-
async getAnalyticsUserEvents(appId, userUlid, params) {
|
|
560
|
-
return this.get(`/app/${appId}/api/analytics/users/${userUlid}/events`, params);
|
|
561
|
-
}
|
|
562
924
|
async getAnalyticsIntegrationMetrics(appId, params) {
|
|
563
925
|
return this.get(`/app/${appId}/api/analytics/integrations`, params);
|
|
564
926
|
}
|
|
927
|
+
async getAnalyticsOverviewDau(appId) {
|
|
928
|
+
return this.get(`/app/${appId}/api/analytics/overview/dau`);
|
|
929
|
+
}
|
|
930
|
+
async getAnalyticsOverviewWau(appId) {
|
|
931
|
+
return this.get(`/app/${appId}/api/analytics/overview/wau`);
|
|
932
|
+
}
|
|
933
|
+
async getAnalyticsOverviewMau(appId) {
|
|
934
|
+
return this.get(`/app/${appId}/api/analytics/overview/mau`);
|
|
935
|
+
}
|
|
936
|
+
async getAnalyticsOverviewGrowth(appId, params) {
|
|
937
|
+
return this.get(`/app/${appId}/api/analytics/overview/growth`, params);
|
|
938
|
+
}
|
|
939
|
+
async getAnalyticsDailyActive(appId, params) {
|
|
940
|
+
return this.get(`/app/${appId}/api/analytics/daily-active`, params);
|
|
941
|
+
}
|
|
942
|
+
async getAnalyticsRollingActive(appId, params) {
|
|
943
|
+
return this.get(`/app/${appId}/api/analytics/rolling-active`, params);
|
|
944
|
+
}
|
|
945
|
+
async getAnalyticsCohortRetention(appId) {
|
|
946
|
+
return this.get(`/app/${appId}/api/analytics/cohort-retention`);
|
|
947
|
+
}
|
|
948
|
+
async getAnalyticsUserSearch(appId, params) {
|
|
949
|
+
return this.get(`/app/${appId}/api/analytics/users/search`, params);
|
|
950
|
+
}
|
|
951
|
+
async getAnalyticsUserDetail(appId, userUlid) {
|
|
952
|
+
return this.get(`/app/${appId}/api/analytics/users/${userUlid}/detail`);
|
|
953
|
+
}
|
|
954
|
+
async getAnalyticsUserSnapshot(appId, userUlid) {
|
|
955
|
+
return this.get(`/app/${appId}/api/analytics/users/${userUlid}/snapshot`);
|
|
956
|
+
}
|
|
957
|
+
async getAnalyticsEvents(appId, params) {
|
|
958
|
+
return this.get(`/app/${appId}/api/analytics/events`, params);
|
|
959
|
+
}
|
|
960
|
+
async getAnalyticsEventsGrouped(appId, params) {
|
|
961
|
+
return this.get(`/app/${appId}/api/analytics/events/grouped`, params);
|
|
962
|
+
}
|
|
963
|
+
async getAnalyticsTopWorkflows(appId, params) {
|
|
964
|
+
return this.get(`/app/${appId}/api/analytics/workflows/top`, params);
|
|
965
|
+
}
|
|
966
|
+
async getAnalyticsTopPrompts(appId, params) {
|
|
967
|
+
return this.get(`/app/${appId}/api/analytics/prompts/top`, params);
|
|
968
|
+
}
|
|
565
969
|
// ============================================
|
|
566
970
|
// SUPER ADMIN: ADMINS
|
|
567
971
|
// ============================================
|
|
@@ -670,6 +1074,24 @@ export class ApiClient {
|
|
|
670
1074
|
return this.get(`/admin/api/apps/${appId}/comparisons/${group}`);
|
|
671
1075
|
}
|
|
672
1076
|
// ============================================
|
|
1077
|
+
// EMAIL TEMPLATES
|
|
1078
|
+
// ============================================
|
|
1079
|
+
async listEmailTemplates(appId) {
|
|
1080
|
+
return this.get(`/admin/api/apps/${appId}/email-templates`);
|
|
1081
|
+
}
|
|
1082
|
+
async getEmailTemplate(appId, emailType) {
|
|
1083
|
+
return this.get(`/admin/api/apps/${appId}/email-templates/${emailType}`);
|
|
1084
|
+
}
|
|
1085
|
+
async upsertEmailTemplate(appId, emailType, payload) {
|
|
1086
|
+
return this.put(`/admin/api/apps/${appId}/email-templates/${emailType}`, payload);
|
|
1087
|
+
}
|
|
1088
|
+
async deleteEmailTemplate(appId, emailType) {
|
|
1089
|
+
return this.delete(`/admin/api/apps/${appId}/email-templates/${emailType}`);
|
|
1090
|
+
}
|
|
1091
|
+
async testEmailTemplate(appId, emailType, payload) {
|
|
1092
|
+
return this.post(`/admin/api/apps/${appId}/email-templates/${emailType}/test`, payload || {});
|
|
1093
|
+
}
|
|
1094
|
+
// ============================================
|
|
673
1095
|
// ACCESS TOKENS
|
|
674
1096
|
// ============================================
|
|
675
1097
|
async createToken(appId, data) {
|
|
@@ -697,32 +1119,268 @@ export class ApiClient {
|
|
|
697
1119
|
async getDatabase(appId, databaseId) {
|
|
698
1120
|
return this.get(`/app/${appId}/api/databases/${databaseId}`);
|
|
699
1121
|
}
|
|
1122
|
+
async updateDatabase(appId, databaseId, data) {
|
|
1123
|
+
return this.patch(`/app/${appId}/api/databases/${databaseId}`, data);
|
|
1124
|
+
}
|
|
700
1125
|
async deleteDatabase(appId, databaseId) {
|
|
701
1126
|
return this.delete(`/app/${appId}/api/databases/${databaseId}`);
|
|
702
1127
|
}
|
|
1128
|
+
/**
|
|
1129
|
+
* Read a database's CEL context dict.
|
|
1130
|
+
*
|
|
1131
|
+
* The HTTP path stays `/metadata` because the wire field name is still
|
|
1132
|
+
* `metadata`; only the client/CLI-facing helper names were reframed.
|
|
1133
|
+
*/
|
|
1134
|
+
async getDatabaseCelContext(appId, databaseId) {
|
|
1135
|
+
return this.get(`/app/${appId}/api/databases/${databaseId}/metadata`);
|
|
1136
|
+
}
|
|
1137
|
+
/** Update a database's CEL context dict (merge with existing). */
|
|
1138
|
+
async updateDatabaseCelContext(appId, databaseId, celContext) {
|
|
1139
|
+
return this.patch(`/app/${appId}/api/databases/${databaseId}/metadata`, celContext);
|
|
1140
|
+
}
|
|
1141
|
+
/** @deprecated Use {@link getDatabaseCelContext} instead. */
|
|
1142
|
+
async getDatabaseMetadata(appId, databaseId) {
|
|
1143
|
+
return this.getDatabaseCelContext(appId, databaseId);
|
|
1144
|
+
}
|
|
1145
|
+
/** @deprecated Use {@link updateDatabaseCelContext} instead. */
|
|
1146
|
+
async updateDatabaseMetadata(appId, databaseId, metadata) {
|
|
1147
|
+
return this.updateDatabaseCelContext(appId, databaseId, metadata);
|
|
1148
|
+
}
|
|
703
1149
|
// ============================================
|
|
704
1150
|
// DATABASE PERMISSIONS
|
|
705
1151
|
// ============================================
|
|
706
1152
|
async listDatabasePermissions(appId, databaseId) {
|
|
707
1153
|
return this.get(`/app/${appId}/api/databases/${databaseId}/permissions`);
|
|
708
1154
|
}
|
|
1155
|
+
async addDatabaseManager(appId, databaseId, userId) {
|
|
1156
|
+
return this.put(`/app/${appId}/api/databases/${databaseId}/permissions`, {
|
|
1157
|
+
userId,
|
|
1158
|
+
permission: "manager",
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
async removeDatabaseManager(appId, databaseId, userId) {
|
|
1162
|
+
return this.delete(`/app/${appId}/api/databases/${databaseId}/permissions/${userId}`);
|
|
1163
|
+
}
|
|
1164
|
+
/** @deprecated Use {@link addDatabaseManager} instead. */
|
|
709
1165
|
async grantDatabasePermission(appId, databaseId, data) {
|
|
710
|
-
return this.
|
|
1166
|
+
return this.addDatabaseManager(appId, databaseId, data.userId);
|
|
711
1167
|
}
|
|
1168
|
+
/** @deprecated Use {@link removeDatabaseManager} instead. */
|
|
712
1169
|
async revokeDatabasePermission(appId, databaseId, userId) {
|
|
713
1170
|
return this.delete(`/app/${appId}/api/databases/${databaseId}/permissions/${userId}`);
|
|
714
1171
|
}
|
|
715
1172
|
// ============================================
|
|
716
|
-
// DATABASE
|
|
1173
|
+
// DATABASE RECORDS & SCHEMA
|
|
1174
|
+
// ============================================
|
|
1175
|
+
async listDatabaseModels(appId, databaseId) {
|
|
1176
|
+
return this.get(`/app/${appId}/api/databases/${databaseId}/records/models`);
|
|
1177
|
+
}
|
|
1178
|
+
async describeDatabaseModel(appId, databaseId, modelName) {
|
|
1179
|
+
return this.get(`/app/${appId}/api/databases/${databaseId}/records/describe`, { modelName });
|
|
1180
|
+
}
|
|
1181
|
+
async queryDatabaseRecords(appId, databaseId, modelName, queryOptions) {
|
|
1182
|
+
const body = { modelName };
|
|
1183
|
+
if (queryOptions?.filter)
|
|
1184
|
+
body.filter = queryOptions.filter;
|
|
1185
|
+
const options = {};
|
|
1186
|
+
if (queryOptions?.limit)
|
|
1187
|
+
options.limit = queryOptions.limit;
|
|
1188
|
+
if (queryOptions?.cursor)
|
|
1189
|
+
options.uniqueStartKey = queryOptions.cursor;
|
|
1190
|
+
if (Object.keys(options).length > 0)
|
|
1191
|
+
body.options = options;
|
|
1192
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/admin-data/query`, body);
|
|
1193
|
+
}
|
|
1194
|
+
// ============================================
|
|
1195
|
+
// DATABASE OPERATIONS (registered operations)
|
|
1196
|
+
// ============================================
|
|
1197
|
+
async listDatabaseOperations(appId, databaseId) {
|
|
1198
|
+
return this.get(`/app/${appId}/api/databases/${databaseId}/operations`);
|
|
1199
|
+
}
|
|
1200
|
+
async executeDatabaseOperation(appId, databaseId, operationName, data, token, options) {
|
|
1201
|
+
const path = `/app/${appId}/api/databases/${databaseId}/operations/${encodeURIComponent(operationName)}/execute`;
|
|
1202
|
+
const extraHeaders = {};
|
|
1203
|
+
if (options?.timing) {
|
|
1204
|
+
extraHeaders["X-Timing"] = "true";
|
|
1205
|
+
}
|
|
1206
|
+
if (token) {
|
|
1207
|
+
return this.requestWithToken(path, token, {
|
|
1208
|
+
method: "POST",
|
|
1209
|
+
body: JSON.stringify(data || {}),
|
|
1210
|
+
headers: extraHeaders,
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
return this.request(path, {
|
|
1214
|
+
method: "POST",
|
|
1215
|
+
body: JSON.stringify(data || {}),
|
|
1216
|
+
headers: extraHeaders,
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Execute a registered batch (bulk) database operation. Posts a chunk of
|
|
1221
|
+
* items to the canonical `operations/:name/batch` endpoint (the same one the
|
|
1222
|
+
* client library's `executeBatch` uses — NOT the deprecated `import-bulk`
|
|
1223
|
+
* alias). Used by `databases import-csv`.
|
|
1224
|
+
*
|
|
1225
|
+
* @returns `{ imported, failed }` — DO-level write outcome counts. Per-item
|
|
1226
|
+
* validation/access failures abort the whole chunk with a 4xx (thrown).
|
|
1227
|
+
*/
|
|
1228
|
+
async executeBatch(appId, databaseId, operationName, batch) {
|
|
1229
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/operations/${encodeURIComponent(operationName)}/batch`, { batch });
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Make a request using a specific JWT token instead of the CLI's credentials.
|
|
1233
|
+
* Used for executing operations as a different user.
|
|
1234
|
+
*/
|
|
1235
|
+
async requestWithToken(path, token, options = {}) {
|
|
1236
|
+
const credentials = await this.ensureAuthenticated();
|
|
1237
|
+
const url = `${credentials.serverUrl}${path}`;
|
|
1238
|
+
const headers = {
|
|
1239
|
+
Authorization: `Bearer ${token}`,
|
|
1240
|
+
"Content-Type": "application/json",
|
|
1241
|
+
...(options.headers || {}),
|
|
1242
|
+
};
|
|
1243
|
+
const response = await fetchWithTLS(url, { ...options, headers });
|
|
1244
|
+
const text = await response.text();
|
|
1245
|
+
if (!response.ok) {
|
|
1246
|
+
const parsed = parseErrorResponse(response, text, path);
|
|
1247
|
+
throw new ApiError(parsed.message, response.status, parsed.code, parsed.details);
|
|
1248
|
+
}
|
|
1249
|
+
return text ? JSON.parse(text) : null;
|
|
1250
|
+
}
|
|
1251
|
+
// ============================================
|
|
1252
|
+
// DATABASE INDEXES
|
|
717
1253
|
// ============================================
|
|
718
|
-
async
|
|
719
|
-
return this.get(`/app/${appId}/api/databases/${databaseId}/
|
|
1254
|
+
async listDatabaseIndexes(appId, databaseId, modelName) {
|
|
1255
|
+
return this.get(`/app/${appId}/api/databases/${databaseId}/records/indexes`, modelName ? { modelName } : undefined);
|
|
720
1256
|
}
|
|
721
|
-
async
|
|
722
|
-
return this.post(`/app/${appId}/api/databases/${databaseId}/
|
|
1257
|
+
async registerDatabaseIndex(appId, databaseId, data) {
|
|
1258
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/records/index/register`, data);
|
|
723
1259
|
}
|
|
724
|
-
async
|
|
725
|
-
return this.
|
|
1260
|
+
async dropDatabaseIndex(appId, databaseId, data) {
|
|
1261
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/records/index/drop`, data);
|
|
1262
|
+
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Issue #974: back-provision an existing database instance's schema-declared
|
|
1265
|
+
* unique indexes by reading its type schema and registering any declared-but-
|
|
1266
|
+
* missing unique index. Idempotent.
|
|
1267
|
+
*/
|
|
1268
|
+
async reindexDatabaseFromSchema(appId, databaseId) {
|
|
1269
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/reindex`, {});
|
|
1270
|
+
}
|
|
1271
|
+
// ============================================
|
|
1272
|
+
// DATABASE TYPE CONFIGS
|
|
1273
|
+
// ============================================
|
|
1274
|
+
async listDatabaseTypeConfigs(appId) {
|
|
1275
|
+
return this.get(`/app/${appId}/api/databases/types`);
|
|
1276
|
+
}
|
|
1277
|
+
async getDatabaseTypeConfig(appId, databaseType) {
|
|
1278
|
+
return this.get(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}`);
|
|
1279
|
+
}
|
|
1280
|
+
async createDatabaseTypeConfig(appId, data) {
|
|
1281
|
+
return this.post(`/app/${appId}/api/databases/types`, data);
|
|
1282
|
+
}
|
|
1283
|
+
async updateDatabaseTypeConfig(appId, databaseType, data, expectedModifiedAt, options) {
|
|
1284
|
+
const body = expectedModifiedAt ? { ...data, expectedModifiedAt } : data;
|
|
1285
|
+
const qs = new URLSearchParams();
|
|
1286
|
+
if (options?.dryRun)
|
|
1287
|
+
qs.set("dryRun", "true");
|
|
1288
|
+
if (options?.acceptWarnings)
|
|
1289
|
+
qs.set("acceptWarnings", "true");
|
|
1290
|
+
const query = qs.toString();
|
|
1291
|
+
const path = `/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}${query ? `?${query}` : ""}`;
|
|
1292
|
+
return this.patch(path, body);
|
|
1293
|
+
}
|
|
1294
|
+
async deleteDatabaseTypeConfig(appId, databaseType, options) {
|
|
1295
|
+
const query = options?.force ? "?force=true" : "";
|
|
1296
|
+
return this.delete(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}${query}`);
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Issue #666 Phase 3: ask the server to scaffold a TOML schema from
|
|
1300
|
+
* existing ops + DO field introspection. Read-only — does NOT persist.
|
|
1301
|
+
*/
|
|
1302
|
+
async scaffoldDatabaseTypeSchema(appId, databaseType) {
|
|
1303
|
+
return this.post(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/schema:scaffold`, {});
|
|
1304
|
+
}
|
|
1305
|
+
// ============================================
|
|
1306
|
+
// DATABASE TYPE OPERATIONS
|
|
1307
|
+
// ============================================
|
|
1308
|
+
async listDatabaseTypeOperations(appId, databaseType) {
|
|
1309
|
+
return this.get(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/operations`);
|
|
1310
|
+
}
|
|
1311
|
+
async getDatabaseTypeOperation(appId, databaseType, name) {
|
|
1312
|
+
return this.get(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/operations/${encodeURIComponent(name)}`);
|
|
1313
|
+
}
|
|
1314
|
+
async createDatabaseTypeOperation(appId, databaseType, data,
|
|
1315
|
+
// Issue #813 (1A): when `dryRun` is set the server runs the op-edit gate
|
|
1316
|
+
// without persisting — used by `sync push --dry-run` to surface gate
|
|
1317
|
+
// failures the real push would hit. `schemaOverride` lets the gate run
|
|
1318
|
+
// against the schema the SAME push is about to land (so an op that depends
|
|
1319
|
+
// on a new field isn't falsely rejected against the stale stored schema).
|
|
1320
|
+
//
|
|
1321
|
+
// Issue #915 (defect a, follow-up): for a FRESH db-type the parent config
|
|
1322
|
+
// does not exist on the server yet, so the server's "access required"
|
|
1323
|
+
// check has no `defaultAccess` to fall back on. Thread the TOML-declared
|
|
1324
|
+
// type-level `defaultAccess` so an op that omits `access` (intending to
|
|
1325
|
+
// inherit it after the type is created) isn't falsely rejected during the
|
|
1326
|
+
// dry-run. Server only trusts this on the fresh-type dry-run path.
|
|
1327
|
+
options) {
|
|
1328
|
+
const query = options?.dryRun ? "?dryRun=true" : "";
|
|
1329
|
+
let body = data;
|
|
1330
|
+
if (options?.dryRun && options.schemaOverride !== undefined) {
|
|
1331
|
+
body = { ...body, schemaOverride: options.schemaOverride };
|
|
1332
|
+
}
|
|
1333
|
+
if (options?.dryRun && options.defaultAccess !== undefined) {
|
|
1334
|
+
body = { ...body, defaultAccess: options.defaultAccess };
|
|
1335
|
+
}
|
|
1336
|
+
return this.post(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/operations${query}`, body);
|
|
1337
|
+
}
|
|
1338
|
+
async updateDatabaseTypeOperation(appId, databaseType, name, data, expectedModifiedAt,
|
|
1339
|
+
// Issue #813 (1A): dry-run runs the op-edit gate without persisting.
|
|
1340
|
+
// `schemaOverride` gates against the schema the same push is landing.
|
|
1341
|
+
options) {
|
|
1342
|
+
let body = expectedModifiedAt ? { ...data, expectedModifiedAt } : { ...data };
|
|
1343
|
+
if (options?.dryRun && options.schemaOverride !== undefined) {
|
|
1344
|
+
body = { ...body, schemaOverride: options.schemaOverride };
|
|
1345
|
+
}
|
|
1346
|
+
const query = options?.dryRun ? "?dryRun=true" : "";
|
|
1347
|
+
return this.patch(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/operations/${encodeURIComponent(name)}${query}`, body);
|
|
1348
|
+
}
|
|
1349
|
+
async deleteDatabaseTypeOperation(appId, databaseType, name) {
|
|
1350
|
+
return this.delete(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/operations/${encodeURIComponent(name)}`);
|
|
1351
|
+
}
|
|
1352
|
+
// ============================================
|
|
1353
|
+
// DATABASE TYPE SUBSCRIPTIONS (#740 / #803)
|
|
1354
|
+
// ============================================
|
|
1355
|
+
//
|
|
1356
|
+
// Transparent passthroughs against the server's type-scoped subscription
|
|
1357
|
+
// routes (GET/POST/PUT/DELETE `/databases/types/:type/subscriptions[/:key]`).
|
|
1358
|
+
// The wire format is authoritative in the controller
|
|
1359
|
+
// (`database-type-subscriptions-controller.ts`): create requires
|
|
1360
|
+
// `subscriptionKey`, `displayName`, `modelName`, `filter` (CEL), `access`
|
|
1361
|
+
// (CEL); optional `description`, `select` (string[]), `emit`
|
|
1362
|
+
// (enter/update/leave subset), `params` (object, ≤5 entries), `status`.
|
|
1363
|
+
//
|
|
1364
|
+
// The list endpoint wraps results in `{ items: [...] }` (unlike operations,
|
|
1365
|
+
// which returns a bare array). We unwrap here so callers get an array,
|
|
1366
|
+
// matching `listDatabaseTypeOperations`.
|
|
1367
|
+
async listDatabaseTypeSubscriptions(appId, databaseType) {
|
|
1368
|
+
const result = await this.get(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/subscriptions`);
|
|
1369
|
+
if (Array.isArray(result))
|
|
1370
|
+
return result;
|
|
1371
|
+
return Array.isArray(result?.items) ? result.items : [];
|
|
1372
|
+
}
|
|
1373
|
+
async getDatabaseTypeSubscription(appId, databaseType, subscriptionKey) {
|
|
1374
|
+
return this.get(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/subscriptions/${encodeURIComponent(subscriptionKey)}`);
|
|
1375
|
+
}
|
|
1376
|
+
async createDatabaseTypeSubscription(appId, databaseType, data) {
|
|
1377
|
+
return this.post(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/subscriptions`, data);
|
|
1378
|
+
}
|
|
1379
|
+
async updateDatabaseTypeSubscription(appId, databaseType, subscriptionKey, data) {
|
|
1380
|
+
return this.put(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/subscriptions/${encodeURIComponent(subscriptionKey)}`, data);
|
|
1381
|
+
}
|
|
1382
|
+
async deleteDatabaseTypeSubscription(appId, databaseType, subscriptionKey) {
|
|
1383
|
+
return this.delete(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/subscriptions/${encodeURIComponent(subscriptionKey)}`);
|
|
726
1384
|
}
|
|
727
1385
|
// ============================================
|
|
728
1386
|
// GROUPS
|
|
@@ -754,8 +1412,11 @@ export class ApiClient {
|
|
|
754
1412
|
async removeGroupMember(appId, groupType, groupId, userId) {
|
|
755
1413
|
return this.delete(`/app/${appId}/api/groups/${groupType}/${groupId}/members/${userId}`);
|
|
756
1414
|
}
|
|
757
|
-
|
|
758
|
-
|
|
1415
|
+
// ============================================
|
|
1416
|
+
// GROUP RESOURCE LISTINGS
|
|
1417
|
+
// ============================================
|
|
1418
|
+
async listGroupDocuments(appId, groupType, groupId) {
|
|
1419
|
+
return this.get(`/app/${appId}/api/groups/${groupType}/${groupId}/documents`);
|
|
759
1420
|
}
|
|
760
1421
|
// ============================================
|
|
761
1422
|
// USER MEMBERSHIPS
|
|
@@ -763,6 +1424,391 @@ export class ApiClient {
|
|
|
763
1424
|
async listUserMemberships(appId, userId) {
|
|
764
1425
|
return this.get(`/app/${appId}/api/users/${userId}/memberships`);
|
|
765
1426
|
}
|
|
1427
|
+
// ============================================
|
|
1428
|
+
// DOCUMENT GROUP PERMISSIONS
|
|
1429
|
+
// ============================================
|
|
1430
|
+
async listDocumentGroupPermissions(appId, documentId) {
|
|
1431
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/group-permissions`);
|
|
1432
|
+
}
|
|
1433
|
+
async grantDocumentGroupPermission(appId, documentId, data) {
|
|
1434
|
+
return this.post(`/app/${appId}/api/documents/${documentId}/group-permissions`, data);
|
|
1435
|
+
}
|
|
1436
|
+
async revokeDocumentGroupPermission(appId, documentId, groupType, groupId) {
|
|
1437
|
+
return this.delete(`/app/${appId}/api/documents/${documentId}/group-permissions/${groupType}/${groupId}`);
|
|
1438
|
+
}
|
|
1439
|
+
// ============================================
|
|
1440
|
+
// COLLECTIONS
|
|
1441
|
+
// ============================================
|
|
1442
|
+
async listCollections(appId, params) {
|
|
1443
|
+
const qs = new URLSearchParams();
|
|
1444
|
+
if (params?.limit)
|
|
1445
|
+
qs.set("limit", String(params.limit));
|
|
1446
|
+
if (params?.cursor)
|
|
1447
|
+
qs.set("cursor", params.cursor);
|
|
1448
|
+
const q = qs.toString();
|
|
1449
|
+
const result = await this.get(`/app/${appId}/api/collections${q ? `?${q}` : ""}`);
|
|
1450
|
+
return {
|
|
1451
|
+
items: result?.items ?? [],
|
|
1452
|
+
nextCursor: result?.cursor ?? null,
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
async listAllCollections(appId, params) {
|
|
1456
|
+
const qs = new URLSearchParams();
|
|
1457
|
+
if (params?.limit)
|
|
1458
|
+
qs.set("limit", String(params.limit));
|
|
1459
|
+
if (params?.cursor)
|
|
1460
|
+
qs.set("cursor", params.cursor);
|
|
1461
|
+
const q = qs.toString();
|
|
1462
|
+
const result = await this.get(`/app/${appId}/api/admin/collections${q ? `?${q}` : ""}`);
|
|
1463
|
+
return {
|
|
1464
|
+
items: result?.items ?? [],
|
|
1465
|
+
nextCursor: result?.cursor ?? null,
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
async createCollection(appId, data) {
|
|
1469
|
+
return this.post(`/app/${appId}/api/collections`, data);
|
|
1470
|
+
}
|
|
1471
|
+
async getCollection(appId, collectionId) {
|
|
1472
|
+
return this.get(`/app/${appId}/api/collections/${collectionId}`);
|
|
1473
|
+
}
|
|
1474
|
+
async updateCollection(appId, collectionId, data) {
|
|
1475
|
+
return this.patch(`/app/${appId}/api/collections/${collectionId}`, data);
|
|
1476
|
+
}
|
|
1477
|
+
async deleteCollection(appId, collectionId) {
|
|
1478
|
+
return this.delete(`/app/${appId}/api/collections/${collectionId}`);
|
|
1479
|
+
}
|
|
1480
|
+
// ============================================
|
|
1481
|
+
// COLLECTION DOCUMENTS
|
|
1482
|
+
// ============================================
|
|
1483
|
+
async listCollectionDocuments(appId, collectionId, params) {
|
|
1484
|
+
const qs = new URLSearchParams();
|
|
1485
|
+
if (params?.limit)
|
|
1486
|
+
qs.set("limit", String(params.limit));
|
|
1487
|
+
if (params?.cursor)
|
|
1488
|
+
qs.set("cursor", params.cursor);
|
|
1489
|
+
const q = qs.toString();
|
|
1490
|
+
const result = await this.get(`/app/${appId}/api/collections/${collectionId}/documents${q ? `?${q}` : ""}`);
|
|
1491
|
+
return {
|
|
1492
|
+
items: result?.items ?? [],
|
|
1493
|
+
nextCursor: result?.cursor ?? null,
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
async addCollectionDocument(appId, collectionId, data) {
|
|
1497
|
+
return this.post(`/app/${appId}/api/collections/${collectionId}/documents`, data);
|
|
1498
|
+
}
|
|
1499
|
+
async removeCollectionDocument(appId, collectionId, documentId) {
|
|
1500
|
+
return this.delete(`/app/${appId}/api/collections/${collectionId}/documents/${documentId}`);
|
|
1501
|
+
}
|
|
1502
|
+
async listCollectionsForDocument(appId, documentId, params) {
|
|
1503
|
+
const qs = new URLSearchParams();
|
|
1504
|
+
if (params?.limit)
|
|
1505
|
+
qs.set("limit", String(params.limit));
|
|
1506
|
+
if (params?.cursor)
|
|
1507
|
+
qs.set("cursor", params.cursor);
|
|
1508
|
+
const q = qs.toString();
|
|
1509
|
+
const result = await this.get(`/app/${appId}/api/documents/${documentId}/collections${q ? `?${q}` : ""}`);
|
|
1510
|
+
return {
|
|
1511
|
+
items: result?.items ?? [],
|
|
1512
|
+
nextCursor: result?.cursor ?? null,
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
// ============================================
|
|
1516
|
+
// COLLECTION ACCESS (GROUPS + MEMBERS)
|
|
1517
|
+
// ============================================
|
|
1518
|
+
async getCollectionAccess(appId, collectionId) {
|
|
1519
|
+
return this.get(`/app/${appId}/api/collections/${collectionId}/access`);
|
|
1520
|
+
}
|
|
1521
|
+
async grantCollectionGroupPermission(appId, collectionId, data) {
|
|
1522
|
+
return this.post(`/app/${appId}/api/collections/${collectionId}/group-permissions`, data);
|
|
1523
|
+
}
|
|
1524
|
+
async revokeCollectionGroupPermission(appId, collectionId, groupType, groupId) {
|
|
1525
|
+
return this.delete(`/app/${appId}/api/collections/${collectionId}/group-permissions/${groupType}/${groupId}`);
|
|
1526
|
+
}
|
|
1527
|
+
async addCollectionMember(appId, collectionId, data) {
|
|
1528
|
+
return this.post(`/app/${appId}/api/collections/${collectionId}/members`, data);
|
|
1529
|
+
}
|
|
1530
|
+
async removeCollectionMember(appId, collectionId, userId) {
|
|
1531
|
+
return this.delete(`/app/${appId}/api/collections/${collectionId}/members/${userId}`);
|
|
1532
|
+
}
|
|
1533
|
+
// ============================================
|
|
1534
|
+
// COLLECTION TYPE CONFIGS
|
|
1535
|
+
// ============================================
|
|
1536
|
+
async listCollectionTypeConfigs(appId) {
|
|
1537
|
+
return this.get(`/app/${appId}/api/collection-type-configs`);
|
|
1538
|
+
}
|
|
1539
|
+
async getCollectionTypeConfig(appId, collectionType) {
|
|
1540
|
+
return this.get(`/app/${appId}/api/collection-type-configs/${collectionType}`);
|
|
1541
|
+
}
|
|
1542
|
+
async createCollectionTypeConfig(appId, data) {
|
|
1543
|
+
return this.post(`/app/${appId}/api/collection-type-configs`, data);
|
|
1544
|
+
}
|
|
1545
|
+
async updateCollectionTypeConfig(appId, collectionType, data, expectedModifiedAt) {
|
|
1546
|
+
const body = expectedModifiedAt ? { ...data, expectedModifiedAt } : data;
|
|
1547
|
+
return this.patch(`/app/${appId}/api/collection-type-configs/${collectionType}`, body);
|
|
1548
|
+
}
|
|
1549
|
+
async deleteCollectionTypeConfig(appId, collectionType) {
|
|
1550
|
+
return this.delete(`/app/${appId}/api/collection-type-configs/${collectionType}`);
|
|
1551
|
+
}
|
|
1552
|
+
// ============================================
|
|
1553
|
+
// METADATA CATEGORY CONFIGS (issue #1304, P-B)
|
|
1554
|
+
// ============================================
|
|
1555
|
+
/**
|
|
1556
|
+
* List all metadata category configs for an app. The route returns
|
|
1557
|
+
* `{ configs: [...] }`; unwrap to a bare array to match the other
|
|
1558
|
+
* `list*Configs` helpers.
|
|
1559
|
+
*/
|
|
1560
|
+
async listMetadataCategoryConfigs(appId) {
|
|
1561
|
+
const res = await this.get(`/app/${appId}/api/metadata-categories`);
|
|
1562
|
+
return Array.isArray(res?.configs) ? res.configs : [];
|
|
1563
|
+
}
|
|
1564
|
+
async getMetadataCategoryConfig(appId, resourceType, category) {
|
|
1565
|
+
return this.get(`/app/${appId}/api/metadata-categories/${encodeURIComponent(resourceType)}/${encodeURIComponent(category)}`);
|
|
1566
|
+
}
|
|
1567
|
+
/**
|
|
1568
|
+
* Create or replace a metadata category config (idempotent upsert via the
|
|
1569
|
+
* path-addressed PUT route). `resourceType` / `category` identify the config;
|
|
1570
|
+
* the body carries `schema` / `readRule` / `writeRule` / `description`.
|
|
1571
|
+
*/
|
|
1572
|
+
async upsertMetadataCategoryConfig(appId, resourceType, category, data) {
|
|
1573
|
+
return this.put(`/app/${appId}/api/metadata-categories/${encodeURIComponent(resourceType)}/${encodeURIComponent(category)}`, data);
|
|
1574
|
+
}
|
|
1575
|
+
// ============================================
|
|
1576
|
+
// ACCESS RULE SETS
|
|
1577
|
+
// ============================================
|
|
1578
|
+
async listRuleSets(appId, params) {
|
|
1579
|
+
return this.get(`/app/${appId}/api/rule-sets`, params);
|
|
1580
|
+
}
|
|
1581
|
+
async createRuleSet(appId, data) {
|
|
1582
|
+
return this.post(`/app/${appId}/api/rule-sets`, data);
|
|
1583
|
+
}
|
|
1584
|
+
async getRuleSet(appId, ruleSetId) {
|
|
1585
|
+
return this.get(`/app/${appId}/api/rule-sets/${ruleSetId}`);
|
|
1586
|
+
}
|
|
1587
|
+
async updateRuleSet(appId, ruleSetId, data, expectedModifiedAt) {
|
|
1588
|
+
const body = expectedModifiedAt ? { ...data, expectedModifiedAt } : data;
|
|
1589
|
+
return this.patch(`/app/${appId}/api/rule-sets/${ruleSetId}`, body);
|
|
1590
|
+
}
|
|
1591
|
+
async deleteRuleSet(appId, ruleSetId) {
|
|
1592
|
+
return this.delete(`/app/${appId}/api/rule-sets/${ruleSetId}`);
|
|
1593
|
+
}
|
|
1594
|
+
async getRuleSetSchema(appId) {
|
|
1595
|
+
return this.get(`/app/${appId}/api/rule-sets/schema`);
|
|
1596
|
+
}
|
|
1597
|
+
async getRuleSetResourceTypes(appId) {
|
|
1598
|
+
return this.get(`/app/${appId}/api/rule-sets/resource-types`);
|
|
1599
|
+
}
|
|
1600
|
+
async testRuleSet(appId, ruleSetId, data) {
|
|
1601
|
+
return this.post(`/app/${appId}/api/rule-sets/${ruleSetId}/test`, data);
|
|
1602
|
+
}
|
|
1603
|
+
async debugRuleSet(appId, data) {
|
|
1604
|
+
return this.post(`/app/${appId}/api/rule-sets/debug`, data);
|
|
1605
|
+
}
|
|
1606
|
+
// ============================================
|
|
1607
|
+
// GROUP TYPE CONFIGS
|
|
1608
|
+
// ============================================
|
|
1609
|
+
async listGroupTypeConfigs(appId) {
|
|
1610
|
+
return this.get(`/app/${appId}/api/group-type-configs`);
|
|
1611
|
+
}
|
|
1612
|
+
async getGroupTypeConfig(appId, groupType) {
|
|
1613
|
+
return this.get(`/app/${appId}/api/group-type-configs/${groupType}`);
|
|
1614
|
+
}
|
|
1615
|
+
async createGroupTypeConfig(appId, data) {
|
|
1616
|
+
return this.post(`/app/${appId}/api/group-type-configs`, data);
|
|
1617
|
+
}
|
|
1618
|
+
async updateGroupTypeConfig(appId, groupType, data, expectedModifiedAt) {
|
|
1619
|
+
const body = expectedModifiedAt ? { ...data, expectedModifiedAt } : data;
|
|
1620
|
+
return this.patch(`/app/${appId}/api/group-type-configs/${groupType}`, body);
|
|
1621
|
+
}
|
|
1622
|
+
async deleteGroupTypeConfig(appId, groupType) {
|
|
1623
|
+
return this.delete(`/app/${appId}/api/group-type-configs/${groupType}`);
|
|
1624
|
+
}
|
|
1625
|
+
// ============================================
|
|
1626
|
+
// DOCUMENT EXPORT / IMPORT
|
|
1627
|
+
// ============================================
|
|
1628
|
+
async exportDocumentState(appId, documentId) {
|
|
1629
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/export/state`);
|
|
1630
|
+
}
|
|
1631
|
+
async importDocumentState(appId, documentId, stateBase64) {
|
|
1632
|
+
return this.post(`/app/${appId}/api/documents/${documentId}/import/state`, { state: stateBase64 });
|
|
1633
|
+
}
|
|
1634
|
+
async getDocument(appId, documentId) {
|
|
1635
|
+
return this.get(`/app/${appId}/api/documents/${documentId}`);
|
|
1636
|
+
}
|
|
1637
|
+
async createDocument(appId, data) {
|
|
1638
|
+
return this.post(`/app/${appId}/api/documents`, data);
|
|
1639
|
+
}
|
|
1640
|
+
async listDocumentPermissions(appId, documentId) {
|
|
1641
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/permissions`);
|
|
1642
|
+
}
|
|
1643
|
+
async grantDocumentPermission(appId, documentId, permissions) {
|
|
1644
|
+
return this.put(`/app/${appId}/api/documents/${documentId}/permissions`, { permissions });
|
|
1645
|
+
}
|
|
1646
|
+
async listDocumentInvitations(appId, documentId) {
|
|
1647
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/invitations`);
|
|
1648
|
+
}
|
|
1649
|
+
async listDocumentBlobs(appId, documentId) {
|
|
1650
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/blobs`);
|
|
1651
|
+
}
|
|
1652
|
+
async downloadBlob(appId, documentId, blobId) {
|
|
1653
|
+
const credentials = await this.ensureAuthenticated();
|
|
1654
|
+
const url = `${credentials.serverUrl}/app/${appId}/api/documents/${documentId}/blobs/${blobId}/download`;
|
|
1655
|
+
const headers = {
|
|
1656
|
+
Authorization: `Bearer ${credentials.accessToken}`,
|
|
1657
|
+
};
|
|
1658
|
+
if (credentials.globalAdminAppId) {
|
|
1659
|
+
headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
|
|
1660
|
+
}
|
|
1661
|
+
const response = await fetchWithTLS(url, { headers });
|
|
1662
|
+
if (!response.ok) {
|
|
1663
|
+
throw new ApiError(`Failed to download blob: ${response.statusText}`, response.status);
|
|
1664
|
+
}
|
|
1665
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
1666
|
+
return Buffer.from(arrayBuffer);
|
|
1667
|
+
}
|
|
1668
|
+
async uploadBlob(appId, documentId, blobId, data, meta) {
|
|
1669
|
+
const credentials = await this.ensureAuthenticated();
|
|
1670
|
+
const url = `${credentials.serverUrl}/app/${appId}/api/documents/${documentId}/blobs/${blobId}`;
|
|
1671
|
+
const headers = {
|
|
1672
|
+
Authorization: `Bearer ${credentials.accessToken}`,
|
|
1673
|
+
"Content-Type": meta.contentType,
|
|
1674
|
+
"X-Blob-Filename": encodeURIComponent(meta.filename),
|
|
1675
|
+
"X-Blob-Size": String(data.length),
|
|
1676
|
+
"X-Blob-Sha256": meta.sha256,
|
|
1677
|
+
};
|
|
1678
|
+
if (credentials.globalAdminAppId) {
|
|
1679
|
+
headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
|
|
1680
|
+
}
|
|
1681
|
+
const response = await fetchWithTLS(url, {
|
|
1682
|
+
method: "PUT",
|
|
1683
|
+
headers,
|
|
1684
|
+
body: data,
|
|
1685
|
+
});
|
|
1686
|
+
if (!response.ok) {
|
|
1687
|
+
const text = await response.text();
|
|
1688
|
+
throw new ApiError(`Failed to upload blob: ${text}`, response.status);
|
|
1689
|
+
}
|
|
1690
|
+
return response.json();
|
|
1691
|
+
}
|
|
1692
|
+
async listDocumentAliases(appId, documentId) {
|
|
1693
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/aliases`);
|
|
1694
|
+
}
|
|
1695
|
+
async setDocumentAlias(appId, aliasScope, aliasKey, documentId, ownerUserId, mustNotExist) {
|
|
1696
|
+
return this.put(`/app/${appId}/api/document-aliases/${aliasScope}/${encodeURIComponent(aliasKey)}`, {
|
|
1697
|
+
documentId,
|
|
1698
|
+
userId: ownerUserId,
|
|
1699
|
+
mustNotExist,
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1702
|
+
async findUserByEmail(appId, email) {
|
|
1703
|
+
try {
|
|
1704
|
+
const result = await this.get(`/app/${appId}/api/users`, { email });
|
|
1705
|
+
const items = result?.items || (Array.isArray(result) ? result : []);
|
|
1706
|
+
return items[0] || null;
|
|
1707
|
+
}
|
|
1708
|
+
catch {
|
|
1709
|
+
return null;
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
async listAdminDocuments(appId, userId) {
|
|
1713
|
+
const result = await this.get(`/admin/api/apps/${appId}/documents`, { userId });
|
|
1714
|
+
return result?.documents || result || [];
|
|
1715
|
+
}
|
|
1716
|
+
// ============================================
|
|
1717
|
+
// DATABASE EXPORT / IMPORT
|
|
1718
|
+
// ============================================
|
|
1719
|
+
async saveDatabaseRecord(appId, databaseId, modelName, id, data) {
|
|
1720
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/admin-data/save`, {
|
|
1721
|
+
modelName,
|
|
1722
|
+
id,
|
|
1723
|
+
data,
|
|
1724
|
+
});
|
|
1725
|
+
}
|
|
1726
|
+
async batchDatabaseRecords(appId, databaseId, operations) {
|
|
1727
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/admin-data/batch`, { operations });
|
|
1728
|
+
}
|
|
1729
|
+
async deleteDatabaseRecord(appId, databaseId, modelName, id) {
|
|
1730
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/admin-data/delete`, { modelName, id });
|
|
1731
|
+
}
|
|
1732
|
+
async batchDeleteDatabaseRecords(appId, databaseId, operations) {
|
|
1733
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/admin-data/batch`, { operations });
|
|
1734
|
+
}
|
|
1735
|
+
async listDatabaseUniqueConstraints(appId, databaseId) {
|
|
1736
|
+
const result = await this.get(`/app/${appId}/api/databases/${databaseId}/records/unique-constraints`);
|
|
1737
|
+
return result?.constraints || result || [];
|
|
1738
|
+
}
|
|
1739
|
+
async registerDatabaseUniqueConstraint(appId, databaseId, constraint) {
|
|
1740
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/records/unique-constraint/register`, constraint);
|
|
1741
|
+
}
|
|
1742
|
+
// ============================================
|
|
1743
|
+
// BLOB BUCKETS
|
|
1744
|
+
// ============================================
|
|
1745
|
+
async listBlobBuckets(appId) {
|
|
1746
|
+
return this.get(`/app/${appId}/api/blob-buckets`);
|
|
1747
|
+
}
|
|
1748
|
+
async createBlobBucket(appId, payload) {
|
|
1749
|
+
return this.post(`/app/${appId}/api/blob-buckets`, payload);
|
|
1750
|
+
}
|
|
1751
|
+
async getBlobBucket(appId, bucketIdOrKey) {
|
|
1752
|
+
return this.get(`/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}`);
|
|
1753
|
+
}
|
|
1754
|
+
async updateBlobBucket(appId, bucketIdOrKey, payload) {
|
|
1755
|
+
return this.patch(`/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}`, payload);
|
|
1756
|
+
}
|
|
1757
|
+
async deleteBlobBucket(appId, bucketIdOrKey) {
|
|
1758
|
+
return this.delete(`/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}`);
|
|
1759
|
+
}
|
|
1760
|
+
async listBucketBlobs(appId, bucketIdOrKey, params) {
|
|
1761
|
+
return this.get(`/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}/blobs`, params);
|
|
1762
|
+
}
|
|
1763
|
+
async uploadBucketBlob(appId, bucketIdOrKey, data, meta) {
|
|
1764
|
+
const credentials = await this.ensureAuthenticated();
|
|
1765
|
+
const url = `${credentials.serverUrl}/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}/blobs`;
|
|
1766
|
+
const headers = {
|
|
1767
|
+
Authorization: `Bearer ${credentials.accessToken}`,
|
|
1768
|
+
"Content-Type": meta.contentType,
|
|
1769
|
+
"X-Blob-Filename": encodeURIComponent(meta.filename),
|
|
1770
|
+
};
|
|
1771
|
+
if (meta.tags && meta.tags.length > 0) {
|
|
1772
|
+
headers["X-Blob-Tags"] = JSON.stringify(meta.tags);
|
|
1773
|
+
}
|
|
1774
|
+
if (credentials.globalAdminAppId) {
|
|
1775
|
+
headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
|
|
1776
|
+
}
|
|
1777
|
+
const response = await fetchWithTLS(url, {
|
|
1778
|
+
method: "POST",
|
|
1779
|
+
headers,
|
|
1780
|
+
body: data,
|
|
1781
|
+
});
|
|
1782
|
+
if (!response.ok) {
|
|
1783
|
+
const text = await response.text();
|
|
1784
|
+
throw new ApiError(`Failed to upload blob: ${text}`, response.status);
|
|
1785
|
+
}
|
|
1786
|
+
return response.json();
|
|
1787
|
+
}
|
|
1788
|
+
async downloadBucketBlob(appId, bucketIdOrKey, blobId) {
|
|
1789
|
+
const credentials = await this.ensureAuthenticated();
|
|
1790
|
+
const url = `${credentials.serverUrl}/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}/blobs/${blobId}`;
|
|
1791
|
+
const headers = {
|
|
1792
|
+
Authorization: `Bearer ${credentials.accessToken}`,
|
|
1793
|
+
};
|
|
1794
|
+
if (credentials.globalAdminAppId) {
|
|
1795
|
+
headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
|
|
1796
|
+
}
|
|
1797
|
+
const response = await fetchWithTLS(url, { headers });
|
|
1798
|
+
if (!response.ok) {
|
|
1799
|
+
throw new ApiError(`Failed to download blob: ${response.statusText}`, response.status);
|
|
1800
|
+
}
|
|
1801
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
1802
|
+
return Buffer.from(arrayBuffer);
|
|
1803
|
+
}
|
|
1804
|
+
async deleteBucketBlob(appId, bucketIdOrKey, blobId) {
|
|
1805
|
+
return this.delete(`/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}/blobs/${blobId}`);
|
|
1806
|
+
}
|
|
1807
|
+
async getBucketBlobSignedUrl(appId, bucketIdOrKey, blobId, expiresInSeconds) {
|
|
1808
|
+
return this.post(`/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}/blobs/${blobId}/signed-url`, {
|
|
1809
|
+
expiresInSeconds: expiresInSeconds || 300,
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
766
1812
|
}
|
|
767
1813
|
// Export a singleton instance
|
|
768
1814
|
export const apiClient = new ApiClient();
|