primitive-admin 1.1.0-alpha.4 → 1.1.0-alpha.40
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 +266 -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 +45 -43
- 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/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 +174 -0
- package/dist/src/commands/sync.js +4392 -235
- 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 +1272 -0
- package/dist/src/lib/api-client.js +1115 -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 +213 -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 +108 -0
- package/dist/src/lib/toml-metadata-config.js +371 -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);
|
|
104
250
|
}
|
|
105
|
-
if (response.status ===
|
|
106
|
-
throw new
|
|
251
|
+
if (response.status === 422 && parsed.code === "SCHEMA_BREAKS_OPERATIONS") {
|
|
252
|
+
throw new SchemaBreaksOpsError(parsed.message, detailsRecord);
|
|
107
253
|
}
|
|
108
|
-
|
|
254
|
+
if (response.status === 422 && parsed.code === "SCHEMA_HAS_UNCHECKABLE_OPS") {
|
|
255
|
+
throw new SchemaHasUncheckableOpsError(parsed.message, detailsRecord);
|
|
256
|
+
}
|
|
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,99 @@ 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
|
+
// ============================================
|
|
277
587
|
// PROMPTS
|
|
278
588
|
// ============================================
|
|
279
589
|
async listPrompts(appId, params) {
|
|
@@ -325,6 +635,55 @@ export class ApiClient {
|
|
|
325
635
|
async duplicatePromptConfig(appId, promptId, configId, payload) {
|
|
326
636
|
return this.post(`/admin/api/apps/${appId}/prompts/${promptId}/configs/${configId}/duplicate`, payload || {});
|
|
327
637
|
}
|
|
638
|
+
// ============================================
|
|
639
|
+
// SCRIPTS (Rhai transforms) — #1000 (prompt-model convergence)
|
|
640
|
+
// ============================================
|
|
641
|
+
//
|
|
642
|
+
// The `Script` model is the authoring surface for `script` workflow
|
|
643
|
+
// steps. A `Script` is a HEADER (name/description/activeConfigId); the
|
|
644
|
+
// Rhai body lives on versioned `ScriptConfig` rows resolved LIVE at run
|
|
645
|
+
// time. CLI `sync push` reads `transforms/*.rhai` files and reconciles
|
|
646
|
+
// them: a new file → create script (mints a default config); a changed
|
|
647
|
+
// file → create a new config + activate it (zero fan-out — referencing
|
|
648
|
+
// workflows pick up the new body on their next run).
|
|
649
|
+
async listScripts(appId) {
|
|
650
|
+
const result = await this.get(`/admin/api/apps/${appId}/scripts`);
|
|
651
|
+
return { items: result?.items ?? [] };
|
|
652
|
+
}
|
|
653
|
+
async getScript(appId, scriptId) {
|
|
654
|
+
return this.get(`/admin/api/apps/${appId}/scripts/${scriptId}`);
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* List a script's versioned configs (the `ScriptConfig` rows). Each config
|
|
658
|
+
* is a distinct block version: `configId` is the stable version selector,
|
|
659
|
+
* `contentHash` is its content identity, and `status` reports whether it is
|
|
660
|
+
* `active`/`draft`/`archived`. The script header's `activeConfigId` names
|
|
661
|
+
* the live version. Used by `scripts configs list` and by the script test
|
|
662
|
+
* commands to run against a specific pinned version.
|
|
663
|
+
*/
|
|
664
|
+
async listScriptConfigs(appId, scriptId) {
|
|
665
|
+
const result = await this.get(`/admin/api/apps/${appId}/scripts/${scriptId}/configs`);
|
|
666
|
+
return { items: result?.items ?? [] };
|
|
667
|
+
}
|
|
668
|
+
async createScript(appId, payload) {
|
|
669
|
+
// Mints the script header + a default `active` config carrying the body.
|
|
670
|
+
return this.post(`/admin/api/apps/${appId}/scripts`, payload);
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Push a new body for an existing script by creating a new `ScriptConfig`
|
|
674
|
+
* and activating it (the prompt-model "edit → activate" flow). Referencing
|
|
675
|
+
* workflows pick up the new body on their next run with no fan-out. The
|
|
676
|
+
* config name is unique per script, so we mint a timestamped name.
|
|
677
|
+
*/
|
|
678
|
+
async pushScriptBody(appId, scriptId, body) {
|
|
679
|
+
const configName = `sync-${Date.now()}`;
|
|
680
|
+
const config = await this.post(`/admin/api/apps/${appId}/scripts/${scriptId}/configs`, { configName, body });
|
|
681
|
+
await this.post(`/admin/api/apps/${appId}/scripts/${scriptId}/configs/${config.configId}/activate`, {});
|
|
682
|
+
return config;
|
|
683
|
+
}
|
|
684
|
+
async deleteScript(appId, scriptId) {
|
|
685
|
+
return this.delete(`/admin/api/apps/${appId}/scripts/${scriptId}`);
|
|
686
|
+
}
|
|
328
687
|
async getPromptSchema(appId, promptId) {
|
|
329
688
|
return this.get(`/admin/api/apps/${appId}/prompts/${promptId}/schema`);
|
|
330
689
|
}
|
|
@@ -415,14 +774,8 @@ export class ApiClient {
|
|
|
415
774
|
});
|
|
416
775
|
const text = await response.text();
|
|
417
776
|
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);
|
|
777
|
+
const parsed = parseErrorResponse(response, text, url);
|
|
778
|
+
throw new ApiError(parsed.message, response.status, parsed.code, parsed.details);
|
|
426
779
|
}
|
|
427
780
|
return text ? JSON.parse(text) : null;
|
|
428
781
|
}
|
|
@@ -442,14 +795,8 @@ export class ApiClient {
|
|
|
442
795
|
});
|
|
443
796
|
if (!response.ok) {
|
|
444
797
|
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);
|
|
798
|
+
const parsed = parseErrorResponse(response, text, url);
|
|
799
|
+
throw new ApiError(parsed.message, response.status, parsed.code, parsed.details);
|
|
453
800
|
}
|
|
454
801
|
const arrayBuffer = await response.arrayBuffer();
|
|
455
802
|
return {
|
|
@@ -519,6 +866,16 @@ export class ApiClient {
|
|
|
519
866
|
async getWorkflowRunStatus(appId, workflowId, runId) {
|
|
520
867
|
return this.get(`/admin/api/apps/${appId}/workflows/${workflowId}/runs/${runId}/status`);
|
|
521
868
|
}
|
|
869
|
+
async getWorkflowStepRuns(appId, workflowId, runId) {
|
|
870
|
+
const result = await this.get(`/admin/api/apps/${appId}/workflows/${workflowId}/runs/${runId}/steps`);
|
|
871
|
+
return { items: result?.items ?? [] };
|
|
872
|
+
}
|
|
873
|
+
async getWorkflowAnalytics(appId, params) {
|
|
874
|
+
return this.get(`/app/${appId}/api/analytics/workflows/overview`, params);
|
|
875
|
+
}
|
|
876
|
+
async getTopWorkflows(appId, params) {
|
|
877
|
+
return this.get(`/app/${appId}/api/analytics/workflows/top`, params);
|
|
878
|
+
}
|
|
522
879
|
// ============================================
|
|
523
880
|
// WORKFLOW CONFIGURATIONS
|
|
524
881
|
// ============================================
|
|
@@ -547,21 +904,54 @@ export class ApiClient {
|
|
|
547
904
|
// ============================================
|
|
548
905
|
// ANALYTICS
|
|
549
906
|
// ============================================
|
|
550
|
-
async getAnalyticsOverview(appId, params) {
|
|
551
|
-
return this.get(`/app/${appId}/api/analytics/overview`, params);
|
|
552
|
-
}
|
|
553
907
|
async getAnalyticsTopUsers(appId, params) {
|
|
554
908
|
return this.get(`/app/${appId}/api/analytics/users/top`, params);
|
|
555
909
|
}
|
|
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
910
|
async getAnalyticsIntegrationMetrics(appId, params) {
|
|
563
911
|
return this.get(`/app/${appId}/api/analytics/integrations`, params);
|
|
564
912
|
}
|
|
913
|
+
async getAnalyticsOverviewDau(appId) {
|
|
914
|
+
return this.get(`/app/${appId}/api/analytics/overview/dau`);
|
|
915
|
+
}
|
|
916
|
+
async getAnalyticsOverviewWau(appId) {
|
|
917
|
+
return this.get(`/app/${appId}/api/analytics/overview/wau`);
|
|
918
|
+
}
|
|
919
|
+
async getAnalyticsOverviewMau(appId) {
|
|
920
|
+
return this.get(`/app/${appId}/api/analytics/overview/mau`);
|
|
921
|
+
}
|
|
922
|
+
async getAnalyticsOverviewGrowth(appId, params) {
|
|
923
|
+
return this.get(`/app/${appId}/api/analytics/overview/growth`, params);
|
|
924
|
+
}
|
|
925
|
+
async getAnalyticsDailyActive(appId, params) {
|
|
926
|
+
return this.get(`/app/${appId}/api/analytics/daily-active`, params);
|
|
927
|
+
}
|
|
928
|
+
async getAnalyticsRollingActive(appId, params) {
|
|
929
|
+
return this.get(`/app/${appId}/api/analytics/rolling-active`, params);
|
|
930
|
+
}
|
|
931
|
+
async getAnalyticsCohortRetention(appId) {
|
|
932
|
+
return this.get(`/app/${appId}/api/analytics/cohort-retention`);
|
|
933
|
+
}
|
|
934
|
+
async getAnalyticsUserSearch(appId, params) {
|
|
935
|
+
return this.get(`/app/${appId}/api/analytics/users/search`, params);
|
|
936
|
+
}
|
|
937
|
+
async getAnalyticsUserDetail(appId, userUlid) {
|
|
938
|
+
return this.get(`/app/${appId}/api/analytics/users/${userUlid}/detail`);
|
|
939
|
+
}
|
|
940
|
+
async getAnalyticsUserSnapshot(appId, userUlid) {
|
|
941
|
+
return this.get(`/app/${appId}/api/analytics/users/${userUlid}/snapshot`);
|
|
942
|
+
}
|
|
943
|
+
async getAnalyticsEvents(appId, params) {
|
|
944
|
+
return this.get(`/app/${appId}/api/analytics/events`, params);
|
|
945
|
+
}
|
|
946
|
+
async getAnalyticsEventsGrouped(appId, params) {
|
|
947
|
+
return this.get(`/app/${appId}/api/analytics/events/grouped`, params);
|
|
948
|
+
}
|
|
949
|
+
async getAnalyticsTopWorkflows(appId, params) {
|
|
950
|
+
return this.get(`/app/${appId}/api/analytics/workflows/top`, params);
|
|
951
|
+
}
|
|
952
|
+
async getAnalyticsTopPrompts(appId, params) {
|
|
953
|
+
return this.get(`/app/${appId}/api/analytics/prompts/top`, params);
|
|
954
|
+
}
|
|
565
955
|
// ============================================
|
|
566
956
|
// SUPER ADMIN: ADMINS
|
|
567
957
|
// ============================================
|
|
@@ -670,6 +1060,24 @@ export class ApiClient {
|
|
|
670
1060
|
return this.get(`/admin/api/apps/${appId}/comparisons/${group}`);
|
|
671
1061
|
}
|
|
672
1062
|
// ============================================
|
|
1063
|
+
// EMAIL TEMPLATES
|
|
1064
|
+
// ============================================
|
|
1065
|
+
async listEmailTemplates(appId) {
|
|
1066
|
+
return this.get(`/admin/api/apps/${appId}/email-templates`);
|
|
1067
|
+
}
|
|
1068
|
+
async getEmailTemplate(appId, emailType) {
|
|
1069
|
+
return this.get(`/admin/api/apps/${appId}/email-templates/${emailType}`);
|
|
1070
|
+
}
|
|
1071
|
+
async upsertEmailTemplate(appId, emailType, payload) {
|
|
1072
|
+
return this.put(`/admin/api/apps/${appId}/email-templates/${emailType}`, payload);
|
|
1073
|
+
}
|
|
1074
|
+
async deleteEmailTemplate(appId, emailType) {
|
|
1075
|
+
return this.delete(`/admin/api/apps/${appId}/email-templates/${emailType}`);
|
|
1076
|
+
}
|
|
1077
|
+
async testEmailTemplate(appId, emailType, payload) {
|
|
1078
|
+
return this.post(`/admin/api/apps/${appId}/email-templates/${emailType}/test`, payload || {});
|
|
1079
|
+
}
|
|
1080
|
+
// ============================================
|
|
673
1081
|
// ACCESS TOKENS
|
|
674
1082
|
// ============================================
|
|
675
1083
|
async createToken(appId, data) {
|
|
@@ -697,32 +1105,268 @@ export class ApiClient {
|
|
|
697
1105
|
async getDatabase(appId, databaseId) {
|
|
698
1106
|
return this.get(`/app/${appId}/api/databases/${databaseId}`);
|
|
699
1107
|
}
|
|
1108
|
+
async updateDatabase(appId, databaseId, data) {
|
|
1109
|
+
return this.patch(`/app/${appId}/api/databases/${databaseId}`, data);
|
|
1110
|
+
}
|
|
700
1111
|
async deleteDatabase(appId, databaseId) {
|
|
701
1112
|
return this.delete(`/app/${appId}/api/databases/${databaseId}`);
|
|
702
1113
|
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Read a database's CEL context dict.
|
|
1116
|
+
*
|
|
1117
|
+
* The HTTP path stays `/metadata` because the wire field name is still
|
|
1118
|
+
* `metadata`; only the client/CLI-facing helper names were reframed.
|
|
1119
|
+
*/
|
|
1120
|
+
async getDatabaseCelContext(appId, databaseId) {
|
|
1121
|
+
return this.get(`/app/${appId}/api/databases/${databaseId}/metadata`);
|
|
1122
|
+
}
|
|
1123
|
+
/** Update a database's CEL context dict (merge with existing). */
|
|
1124
|
+
async updateDatabaseCelContext(appId, databaseId, celContext) {
|
|
1125
|
+
return this.patch(`/app/${appId}/api/databases/${databaseId}/metadata`, celContext);
|
|
1126
|
+
}
|
|
1127
|
+
/** @deprecated Use {@link getDatabaseCelContext} instead. */
|
|
1128
|
+
async getDatabaseMetadata(appId, databaseId) {
|
|
1129
|
+
return this.getDatabaseCelContext(appId, databaseId);
|
|
1130
|
+
}
|
|
1131
|
+
/** @deprecated Use {@link updateDatabaseCelContext} instead. */
|
|
1132
|
+
async updateDatabaseMetadata(appId, databaseId, metadata) {
|
|
1133
|
+
return this.updateDatabaseCelContext(appId, databaseId, metadata);
|
|
1134
|
+
}
|
|
703
1135
|
// ============================================
|
|
704
1136
|
// DATABASE PERMISSIONS
|
|
705
1137
|
// ============================================
|
|
706
1138
|
async listDatabasePermissions(appId, databaseId) {
|
|
707
1139
|
return this.get(`/app/${appId}/api/databases/${databaseId}/permissions`);
|
|
708
1140
|
}
|
|
1141
|
+
async addDatabaseManager(appId, databaseId, userId) {
|
|
1142
|
+
return this.put(`/app/${appId}/api/databases/${databaseId}/permissions`, {
|
|
1143
|
+
userId,
|
|
1144
|
+
permission: "manager",
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
async removeDatabaseManager(appId, databaseId, userId) {
|
|
1148
|
+
return this.delete(`/app/${appId}/api/databases/${databaseId}/permissions/${userId}`);
|
|
1149
|
+
}
|
|
1150
|
+
/** @deprecated Use {@link addDatabaseManager} instead. */
|
|
709
1151
|
async grantDatabasePermission(appId, databaseId, data) {
|
|
710
|
-
return this.
|
|
1152
|
+
return this.addDatabaseManager(appId, databaseId, data.userId);
|
|
711
1153
|
}
|
|
1154
|
+
/** @deprecated Use {@link removeDatabaseManager} instead. */
|
|
712
1155
|
async revokeDatabasePermission(appId, databaseId, userId) {
|
|
713
1156
|
return this.delete(`/app/${appId}/api/databases/${databaseId}/permissions/${userId}`);
|
|
714
1157
|
}
|
|
715
1158
|
// ============================================
|
|
716
|
-
// DATABASE
|
|
1159
|
+
// DATABASE RECORDS & SCHEMA
|
|
1160
|
+
// ============================================
|
|
1161
|
+
async listDatabaseModels(appId, databaseId) {
|
|
1162
|
+
return this.get(`/app/${appId}/api/databases/${databaseId}/records/models`);
|
|
1163
|
+
}
|
|
1164
|
+
async describeDatabaseModel(appId, databaseId, modelName) {
|
|
1165
|
+
return this.get(`/app/${appId}/api/databases/${databaseId}/records/describe`, { modelName });
|
|
1166
|
+
}
|
|
1167
|
+
async queryDatabaseRecords(appId, databaseId, modelName, queryOptions) {
|
|
1168
|
+
const body = { modelName };
|
|
1169
|
+
if (queryOptions?.filter)
|
|
1170
|
+
body.filter = queryOptions.filter;
|
|
1171
|
+
const options = {};
|
|
1172
|
+
if (queryOptions?.limit)
|
|
1173
|
+
options.limit = queryOptions.limit;
|
|
1174
|
+
if (queryOptions?.cursor)
|
|
1175
|
+
options.uniqueStartKey = queryOptions.cursor;
|
|
1176
|
+
if (Object.keys(options).length > 0)
|
|
1177
|
+
body.options = options;
|
|
1178
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/admin-data/query`, body);
|
|
1179
|
+
}
|
|
1180
|
+
// ============================================
|
|
1181
|
+
// DATABASE OPERATIONS (registered operations)
|
|
1182
|
+
// ============================================
|
|
1183
|
+
async listDatabaseOperations(appId, databaseId) {
|
|
1184
|
+
return this.get(`/app/${appId}/api/databases/${databaseId}/operations`);
|
|
1185
|
+
}
|
|
1186
|
+
async executeDatabaseOperation(appId, databaseId, operationName, data, token, options) {
|
|
1187
|
+
const path = `/app/${appId}/api/databases/${databaseId}/operations/${encodeURIComponent(operationName)}/execute`;
|
|
1188
|
+
const extraHeaders = {};
|
|
1189
|
+
if (options?.timing) {
|
|
1190
|
+
extraHeaders["X-Timing"] = "true";
|
|
1191
|
+
}
|
|
1192
|
+
if (token) {
|
|
1193
|
+
return this.requestWithToken(path, token, {
|
|
1194
|
+
method: "POST",
|
|
1195
|
+
body: JSON.stringify(data || {}),
|
|
1196
|
+
headers: extraHeaders,
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
return this.request(path, {
|
|
1200
|
+
method: "POST",
|
|
1201
|
+
body: JSON.stringify(data || {}),
|
|
1202
|
+
headers: extraHeaders,
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
/**
|
|
1206
|
+
* Execute a registered batch (bulk) database operation. Posts a chunk of
|
|
1207
|
+
* items to the canonical `operations/:name/batch` endpoint (the same one the
|
|
1208
|
+
* client library's `executeBatch` uses — NOT the deprecated `import-bulk`
|
|
1209
|
+
* alias). Used by `databases import-csv`.
|
|
1210
|
+
*
|
|
1211
|
+
* @returns `{ imported, failed }` — DO-level write outcome counts. Per-item
|
|
1212
|
+
* validation/access failures abort the whole chunk with a 4xx (thrown).
|
|
1213
|
+
*/
|
|
1214
|
+
async executeBatch(appId, databaseId, operationName, batch) {
|
|
1215
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/operations/${encodeURIComponent(operationName)}/batch`, { batch });
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Make a request using a specific JWT token instead of the CLI's credentials.
|
|
1219
|
+
* Used for executing operations as a different user.
|
|
1220
|
+
*/
|
|
1221
|
+
async requestWithToken(path, token, options = {}) {
|
|
1222
|
+
const credentials = await this.ensureAuthenticated();
|
|
1223
|
+
const url = `${credentials.serverUrl}${path}`;
|
|
1224
|
+
const headers = {
|
|
1225
|
+
Authorization: `Bearer ${token}`,
|
|
1226
|
+
"Content-Type": "application/json",
|
|
1227
|
+
...(options.headers || {}),
|
|
1228
|
+
};
|
|
1229
|
+
const response = await fetchWithTLS(url, { ...options, headers });
|
|
1230
|
+
const text = await response.text();
|
|
1231
|
+
if (!response.ok) {
|
|
1232
|
+
const parsed = parseErrorResponse(response, text, path);
|
|
1233
|
+
throw new ApiError(parsed.message, response.status, parsed.code, parsed.details);
|
|
1234
|
+
}
|
|
1235
|
+
return text ? JSON.parse(text) : null;
|
|
1236
|
+
}
|
|
1237
|
+
// ============================================
|
|
1238
|
+
// DATABASE INDEXES
|
|
1239
|
+
// ============================================
|
|
1240
|
+
async listDatabaseIndexes(appId, databaseId, modelName) {
|
|
1241
|
+
return this.get(`/app/${appId}/api/databases/${databaseId}/records/indexes`, modelName ? { modelName } : undefined);
|
|
1242
|
+
}
|
|
1243
|
+
async registerDatabaseIndex(appId, databaseId, data) {
|
|
1244
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/records/index/register`, data);
|
|
1245
|
+
}
|
|
1246
|
+
async dropDatabaseIndex(appId, databaseId, data) {
|
|
1247
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/records/index/drop`, data);
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Issue #974: back-provision an existing database instance's schema-declared
|
|
1251
|
+
* unique indexes by reading its type schema and registering any declared-but-
|
|
1252
|
+
* missing unique index. Idempotent.
|
|
1253
|
+
*/
|
|
1254
|
+
async reindexDatabaseFromSchema(appId, databaseId) {
|
|
1255
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/reindex`, {});
|
|
1256
|
+
}
|
|
1257
|
+
// ============================================
|
|
1258
|
+
// DATABASE TYPE CONFIGS
|
|
717
1259
|
// ============================================
|
|
718
|
-
async
|
|
719
|
-
return this.get(`/app/${appId}/api/databases
|
|
1260
|
+
async listDatabaseTypeConfigs(appId) {
|
|
1261
|
+
return this.get(`/app/${appId}/api/databases/types`);
|
|
1262
|
+
}
|
|
1263
|
+
async getDatabaseTypeConfig(appId, databaseType) {
|
|
1264
|
+
return this.get(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}`);
|
|
1265
|
+
}
|
|
1266
|
+
async createDatabaseTypeConfig(appId, data) {
|
|
1267
|
+
return this.post(`/app/${appId}/api/databases/types`, data);
|
|
1268
|
+
}
|
|
1269
|
+
async updateDatabaseTypeConfig(appId, databaseType, data, expectedModifiedAt, options) {
|
|
1270
|
+
const body = expectedModifiedAt ? { ...data, expectedModifiedAt } : data;
|
|
1271
|
+
const qs = new URLSearchParams();
|
|
1272
|
+
if (options?.dryRun)
|
|
1273
|
+
qs.set("dryRun", "true");
|
|
1274
|
+
if (options?.acceptWarnings)
|
|
1275
|
+
qs.set("acceptWarnings", "true");
|
|
1276
|
+
const query = qs.toString();
|
|
1277
|
+
const path = `/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}${query ? `?${query}` : ""}`;
|
|
1278
|
+
return this.patch(path, body);
|
|
1279
|
+
}
|
|
1280
|
+
async deleteDatabaseTypeConfig(appId, databaseType, options) {
|
|
1281
|
+
const query = options?.force ? "?force=true" : "";
|
|
1282
|
+
return this.delete(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}${query}`);
|
|
1283
|
+
}
|
|
1284
|
+
/**
|
|
1285
|
+
* Issue #666 Phase 3: ask the server to scaffold a TOML schema from
|
|
1286
|
+
* existing ops + DO field introspection. Read-only — does NOT persist.
|
|
1287
|
+
*/
|
|
1288
|
+
async scaffoldDatabaseTypeSchema(appId, databaseType) {
|
|
1289
|
+
return this.post(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/schema:scaffold`, {});
|
|
720
1290
|
}
|
|
721
|
-
|
|
722
|
-
|
|
1291
|
+
// ============================================
|
|
1292
|
+
// DATABASE TYPE OPERATIONS
|
|
1293
|
+
// ============================================
|
|
1294
|
+
async listDatabaseTypeOperations(appId, databaseType) {
|
|
1295
|
+
return this.get(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/operations`);
|
|
1296
|
+
}
|
|
1297
|
+
async getDatabaseTypeOperation(appId, databaseType, name) {
|
|
1298
|
+
return this.get(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/operations/${encodeURIComponent(name)}`);
|
|
1299
|
+
}
|
|
1300
|
+
async createDatabaseTypeOperation(appId, databaseType, data,
|
|
1301
|
+
// Issue #813 (1A): when `dryRun` is set the server runs the op-edit gate
|
|
1302
|
+
// without persisting — used by `sync push --dry-run` to surface gate
|
|
1303
|
+
// failures the real push would hit. `schemaOverride` lets the gate run
|
|
1304
|
+
// against the schema the SAME push is about to land (so an op that depends
|
|
1305
|
+
// on a new field isn't falsely rejected against the stale stored schema).
|
|
1306
|
+
//
|
|
1307
|
+
// Issue #915 (defect a, follow-up): for a FRESH db-type the parent config
|
|
1308
|
+
// does not exist on the server yet, so the server's "access required"
|
|
1309
|
+
// check has no `defaultAccess` to fall back on. Thread the TOML-declared
|
|
1310
|
+
// type-level `defaultAccess` so an op that omits `access` (intending to
|
|
1311
|
+
// inherit it after the type is created) isn't falsely rejected during the
|
|
1312
|
+
// dry-run. Server only trusts this on the fresh-type dry-run path.
|
|
1313
|
+
options) {
|
|
1314
|
+
const query = options?.dryRun ? "?dryRun=true" : "";
|
|
1315
|
+
let body = data;
|
|
1316
|
+
if (options?.dryRun && options.schemaOverride !== undefined) {
|
|
1317
|
+
body = { ...body, schemaOverride: options.schemaOverride };
|
|
1318
|
+
}
|
|
1319
|
+
if (options?.dryRun && options.defaultAccess !== undefined) {
|
|
1320
|
+
body = { ...body, defaultAccess: options.defaultAccess };
|
|
1321
|
+
}
|
|
1322
|
+
return this.post(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/operations${query}`, body);
|
|
1323
|
+
}
|
|
1324
|
+
async updateDatabaseTypeOperation(appId, databaseType, name, data, expectedModifiedAt,
|
|
1325
|
+
// Issue #813 (1A): dry-run runs the op-edit gate without persisting.
|
|
1326
|
+
// `schemaOverride` gates against the schema the same push is landing.
|
|
1327
|
+
options) {
|
|
1328
|
+
let body = expectedModifiedAt ? { ...data, expectedModifiedAt } : { ...data };
|
|
1329
|
+
if (options?.dryRun && options.schemaOverride !== undefined) {
|
|
1330
|
+
body = { ...body, schemaOverride: options.schemaOverride };
|
|
1331
|
+
}
|
|
1332
|
+
const query = options?.dryRun ? "?dryRun=true" : "";
|
|
1333
|
+
return this.patch(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/operations/${encodeURIComponent(name)}${query}`, body);
|
|
1334
|
+
}
|
|
1335
|
+
async deleteDatabaseTypeOperation(appId, databaseType, name) {
|
|
1336
|
+
return this.delete(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/operations/${encodeURIComponent(name)}`);
|
|
723
1337
|
}
|
|
724
|
-
|
|
725
|
-
|
|
1338
|
+
// ============================================
|
|
1339
|
+
// DATABASE TYPE SUBSCRIPTIONS (#740 / #803)
|
|
1340
|
+
// ============================================
|
|
1341
|
+
//
|
|
1342
|
+
// Transparent passthroughs against the server's type-scoped subscription
|
|
1343
|
+
// routes (GET/POST/PUT/DELETE `/databases/types/:type/subscriptions[/:key]`).
|
|
1344
|
+
// The wire format is authoritative in the controller
|
|
1345
|
+
// (`database-type-subscriptions-controller.ts`): create requires
|
|
1346
|
+
// `subscriptionKey`, `displayName`, `modelName`, `filter` (CEL), `access`
|
|
1347
|
+
// (CEL); optional `description`, `select` (string[]), `emit`
|
|
1348
|
+
// (enter/update/leave subset), `params` (object, ≤5 entries), `status`.
|
|
1349
|
+
//
|
|
1350
|
+
// The list endpoint wraps results in `{ items: [...] }` (unlike operations,
|
|
1351
|
+
// which returns a bare array). We unwrap here so callers get an array,
|
|
1352
|
+
// matching `listDatabaseTypeOperations`.
|
|
1353
|
+
async listDatabaseTypeSubscriptions(appId, databaseType) {
|
|
1354
|
+
const result = await this.get(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/subscriptions`);
|
|
1355
|
+
if (Array.isArray(result))
|
|
1356
|
+
return result;
|
|
1357
|
+
return Array.isArray(result?.items) ? result.items : [];
|
|
1358
|
+
}
|
|
1359
|
+
async getDatabaseTypeSubscription(appId, databaseType, subscriptionKey) {
|
|
1360
|
+
return this.get(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/subscriptions/${encodeURIComponent(subscriptionKey)}`);
|
|
1361
|
+
}
|
|
1362
|
+
async createDatabaseTypeSubscription(appId, databaseType, data) {
|
|
1363
|
+
return this.post(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/subscriptions`, data);
|
|
1364
|
+
}
|
|
1365
|
+
async updateDatabaseTypeSubscription(appId, databaseType, subscriptionKey, data) {
|
|
1366
|
+
return this.put(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/subscriptions/${encodeURIComponent(subscriptionKey)}`, data);
|
|
1367
|
+
}
|
|
1368
|
+
async deleteDatabaseTypeSubscription(appId, databaseType, subscriptionKey) {
|
|
1369
|
+
return this.delete(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/subscriptions/${encodeURIComponent(subscriptionKey)}`);
|
|
726
1370
|
}
|
|
727
1371
|
// ============================================
|
|
728
1372
|
// GROUPS
|
|
@@ -754,8 +1398,11 @@ export class ApiClient {
|
|
|
754
1398
|
async removeGroupMember(appId, groupType, groupId, userId) {
|
|
755
1399
|
return this.delete(`/app/${appId}/api/groups/${groupType}/${groupId}/members/${userId}`);
|
|
756
1400
|
}
|
|
757
|
-
|
|
758
|
-
|
|
1401
|
+
// ============================================
|
|
1402
|
+
// GROUP RESOURCE LISTINGS
|
|
1403
|
+
// ============================================
|
|
1404
|
+
async listGroupDocuments(appId, groupType, groupId) {
|
|
1405
|
+
return this.get(`/app/${appId}/api/groups/${groupType}/${groupId}/documents`);
|
|
759
1406
|
}
|
|
760
1407
|
// ============================================
|
|
761
1408
|
// USER MEMBERSHIPS
|
|
@@ -763,6 +1410,391 @@ export class ApiClient {
|
|
|
763
1410
|
async listUserMemberships(appId, userId) {
|
|
764
1411
|
return this.get(`/app/${appId}/api/users/${userId}/memberships`);
|
|
765
1412
|
}
|
|
1413
|
+
// ============================================
|
|
1414
|
+
// DOCUMENT GROUP PERMISSIONS
|
|
1415
|
+
// ============================================
|
|
1416
|
+
async listDocumentGroupPermissions(appId, documentId) {
|
|
1417
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/group-permissions`);
|
|
1418
|
+
}
|
|
1419
|
+
async grantDocumentGroupPermission(appId, documentId, data) {
|
|
1420
|
+
return this.post(`/app/${appId}/api/documents/${documentId}/group-permissions`, data);
|
|
1421
|
+
}
|
|
1422
|
+
async revokeDocumentGroupPermission(appId, documentId, groupType, groupId) {
|
|
1423
|
+
return this.delete(`/app/${appId}/api/documents/${documentId}/group-permissions/${groupType}/${groupId}`);
|
|
1424
|
+
}
|
|
1425
|
+
// ============================================
|
|
1426
|
+
// COLLECTIONS
|
|
1427
|
+
// ============================================
|
|
1428
|
+
async listCollections(appId, params) {
|
|
1429
|
+
const qs = new URLSearchParams();
|
|
1430
|
+
if (params?.limit)
|
|
1431
|
+
qs.set("limit", String(params.limit));
|
|
1432
|
+
if (params?.cursor)
|
|
1433
|
+
qs.set("cursor", params.cursor);
|
|
1434
|
+
const q = qs.toString();
|
|
1435
|
+
const result = await this.get(`/app/${appId}/api/collections${q ? `?${q}` : ""}`);
|
|
1436
|
+
return {
|
|
1437
|
+
items: result?.items ?? [],
|
|
1438
|
+
nextCursor: result?.cursor ?? null,
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
async listAllCollections(appId, params) {
|
|
1442
|
+
const qs = new URLSearchParams();
|
|
1443
|
+
if (params?.limit)
|
|
1444
|
+
qs.set("limit", String(params.limit));
|
|
1445
|
+
if (params?.cursor)
|
|
1446
|
+
qs.set("cursor", params.cursor);
|
|
1447
|
+
const q = qs.toString();
|
|
1448
|
+
const result = await this.get(`/app/${appId}/api/admin/collections${q ? `?${q}` : ""}`);
|
|
1449
|
+
return {
|
|
1450
|
+
items: result?.items ?? [],
|
|
1451
|
+
nextCursor: result?.cursor ?? null,
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
async createCollection(appId, data) {
|
|
1455
|
+
return this.post(`/app/${appId}/api/collections`, data);
|
|
1456
|
+
}
|
|
1457
|
+
async getCollection(appId, collectionId) {
|
|
1458
|
+
return this.get(`/app/${appId}/api/collections/${collectionId}`);
|
|
1459
|
+
}
|
|
1460
|
+
async updateCollection(appId, collectionId, data) {
|
|
1461
|
+
return this.patch(`/app/${appId}/api/collections/${collectionId}`, data);
|
|
1462
|
+
}
|
|
1463
|
+
async deleteCollection(appId, collectionId) {
|
|
1464
|
+
return this.delete(`/app/${appId}/api/collections/${collectionId}`);
|
|
1465
|
+
}
|
|
1466
|
+
// ============================================
|
|
1467
|
+
// COLLECTION DOCUMENTS
|
|
1468
|
+
// ============================================
|
|
1469
|
+
async listCollectionDocuments(appId, collectionId, params) {
|
|
1470
|
+
const qs = new URLSearchParams();
|
|
1471
|
+
if (params?.limit)
|
|
1472
|
+
qs.set("limit", String(params.limit));
|
|
1473
|
+
if (params?.cursor)
|
|
1474
|
+
qs.set("cursor", params.cursor);
|
|
1475
|
+
const q = qs.toString();
|
|
1476
|
+
const result = await this.get(`/app/${appId}/api/collections/${collectionId}/documents${q ? `?${q}` : ""}`);
|
|
1477
|
+
return {
|
|
1478
|
+
items: result?.items ?? [],
|
|
1479
|
+
nextCursor: result?.cursor ?? null,
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
async addCollectionDocument(appId, collectionId, data) {
|
|
1483
|
+
return this.post(`/app/${appId}/api/collections/${collectionId}/documents`, data);
|
|
1484
|
+
}
|
|
1485
|
+
async removeCollectionDocument(appId, collectionId, documentId) {
|
|
1486
|
+
return this.delete(`/app/${appId}/api/collections/${collectionId}/documents/${documentId}`);
|
|
1487
|
+
}
|
|
1488
|
+
async listCollectionsForDocument(appId, documentId, params) {
|
|
1489
|
+
const qs = new URLSearchParams();
|
|
1490
|
+
if (params?.limit)
|
|
1491
|
+
qs.set("limit", String(params.limit));
|
|
1492
|
+
if (params?.cursor)
|
|
1493
|
+
qs.set("cursor", params.cursor);
|
|
1494
|
+
const q = qs.toString();
|
|
1495
|
+
const result = await this.get(`/app/${appId}/api/documents/${documentId}/collections${q ? `?${q}` : ""}`);
|
|
1496
|
+
return {
|
|
1497
|
+
items: result?.items ?? [],
|
|
1498
|
+
nextCursor: result?.cursor ?? null,
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
// ============================================
|
|
1502
|
+
// COLLECTION ACCESS (GROUPS + MEMBERS)
|
|
1503
|
+
// ============================================
|
|
1504
|
+
async getCollectionAccess(appId, collectionId) {
|
|
1505
|
+
return this.get(`/app/${appId}/api/collections/${collectionId}/access`);
|
|
1506
|
+
}
|
|
1507
|
+
async grantCollectionGroupPermission(appId, collectionId, data) {
|
|
1508
|
+
return this.post(`/app/${appId}/api/collections/${collectionId}/group-permissions`, data);
|
|
1509
|
+
}
|
|
1510
|
+
async revokeCollectionGroupPermission(appId, collectionId, groupType, groupId) {
|
|
1511
|
+
return this.delete(`/app/${appId}/api/collections/${collectionId}/group-permissions/${groupType}/${groupId}`);
|
|
1512
|
+
}
|
|
1513
|
+
async addCollectionMember(appId, collectionId, data) {
|
|
1514
|
+
return this.post(`/app/${appId}/api/collections/${collectionId}/members`, data);
|
|
1515
|
+
}
|
|
1516
|
+
async removeCollectionMember(appId, collectionId, userId) {
|
|
1517
|
+
return this.delete(`/app/${appId}/api/collections/${collectionId}/members/${userId}`);
|
|
1518
|
+
}
|
|
1519
|
+
// ============================================
|
|
1520
|
+
// COLLECTION TYPE CONFIGS
|
|
1521
|
+
// ============================================
|
|
1522
|
+
async listCollectionTypeConfigs(appId) {
|
|
1523
|
+
return this.get(`/app/${appId}/api/collection-type-configs`);
|
|
1524
|
+
}
|
|
1525
|
+
async getCollectionTypeConfig(appId, collectionType) {
|
|
1526
|
+
return this.get(`/app/${appId}/api/collection-type-configs/${collectionType}`);
|
|
1527
|
+
}
|
|
1528
|
+
async createCollectionTypeConfig(appId, data) {
|
|
1529
|
+
return this.post(`/app/${appId}/api/collection-type-configs`, data);
|
|
1530
|
+
}
|
|
1531
|
+
async updateCollectionTypeConfig(appId, collectionType, data, expectedModifiedAt) {
|
|
1532
|
+
const body = expectedModifiedAt ? { ...data, expectedModifiedAt } : data;
|
|
1533
|
+
return this.patch(`/app/${appId}/api/collection-type-configs/${collectionType}`, body);
|
|
1534
|
+
}
|
|
1535
|
+
async deleteCollectionTypeConfig(appId, collectionType) {
|
|
1536
|
+
return this.delete(`/app/${appId}/api/collection-type-configs/${collectionType}`);
|
|
1537
|
+
}
|
|
1538
|
+
// ============================================
|
|
1539
|
+
// METADATA CATEGORY CONFIGS (issue #1304, P-B)
|
|
1540
|
+
// ============================================
|
|
1541
|
+
/**
|
|
1542
|
+
* List all metadata category configs for an app. The route returns
|
|
1543
|
+
* `{ configs: [...] }`; unwrap to a bare array to match the other
|
|
1544
|
+
* `list*Configs` helpers.
|
|
1545
|
+
*/
|
|
1546
|
+
async listMetadataCategoryConfigs(appId) {
|
|
1547
|
+
const res = await this.get(`/app/${appId}/api/metadata-categories`);
|
|
1548
|
+
return Array.isArray(res?.configs) ? res.configs : [];
|
|
1549
|
+
}
|
|
1550
|
+
async getMetadataCategoryConfig(appId, resourceType, category) {
|
|
1551
|
+
return this.get(`/app/${appId}/api/metadata-categories/${encodeURIComponent(resourceType)}/${encodeURIComponent(category)}`);
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1554
|
+
* Create or replace a metadata category config (idempotent upsert via the
|
|
1555
|
+
* path-addressed PUT route). `resourceType` / `category` identify the config;
|
|
1556
|
+
* the body carries `schema` / `readRule` / `writeRule` / `description`.
|
|
1557
|
+
*/
|
|
1558
|
+
async upsertMetadataCategoryConfig(appId, resourceType, category, data) {
|
|
1559
|
+
return this.put(`/app/${appId}/api/metadata-categories/${encodeURIComponent(resourceType)}/${encodeURIComponent(category)}`, data);
|
|
1560
|
+
}
|
|
1561
|
+
// ============================================
|
|
1562
|
+
// ACCESS RULE SETS
|
|
1563
|
+
// ============================================
|
|
1564
|
+
async listRuleSets(appId, params) {
|
|
1565
|
+
return this.get(`/app/${appId}/api/rule-sets`, params);
|
|
1566
|
+
}
|
|
1567
|
+
async createRuleSet(appId, data) {
|
|
1568
|
+
return this.post(`/app/${appId}/api/rule-sets`, data);
|
|
1569
|
+
}
|
|
1570
|
+
async getRuleSet(appId, ruleSetId) {
|
|
1571
|
+
return this.get(`/app/${appId}/api/rule-sets/${ruleSetId}`);
|
|
1572
|
+
}
|
|
1573
|
+
async updateRuleSet(appId, ruleSetId, data, expectedModifiedAt) {
|
|
1574
|
+
const body = expectedModifiedAt ? { ...data, expectedModifiedAt } : data;
|
|
1575
|
+
return this.patch(`/app/${appId}/api/rule-sets/${ruleSetId}`, body);
|
|
1576
|
+
}
|
|
1577
|
+
async deleteRuleSet(appId, ruleSetId) {
|
|
1578
|
+
return this.delete(`/app/${appId}/api/rule-sets/${ruleSetId}`);
|
|
1579
|
+
}
|
|
1580
|
+
async getRuleSetSchema(appId) {
|
|
1581
|
+
return this.get(`/app/${appId}/api/rule-sets/schema`);
|
|
1582
|
+
}
|
|
1583
|
+
async getRuleSetResourceTypes(appId) {
|
|
1584
|
+
return this.get(`/app/${appId}/api/rule-sets/resource-types`);
|
|
1585
|
+
}
|
|
1586
|
+
async testRuleSet(appId, ruleSetId, data) {
|
|
1587
|
+
return this.post(`/app/${appId}/api/rule-sets/${ruleSetId}/test`, data);
|
|
1588
|
+
}
|
|
1589
|
+
async debugRuleSet(appId, data) {
|
|
1590
|
+
return this.post(`/app/${appId}/api/rule-sets/debug`, data);
|
|
1591
|
+
}
|
|
1592
|
+
// ============================================
|
|
1593
|
+
// GROUP TYPE CONFIGS
|
|
1594
|
+
// ============================================
|
|
1595
|
+
async listGroupTypeConfigs(appId) {
|
|
1596
|
+
return this.get(`/app/${appId}/api/group-type-configs`);
|
|
1597
|
+
}
|
|
1598
|
+
async getGroupTypeConfig(appId, groupType) {
|
|
1599
|
+
return this.get(`/app/${appId}/api/group-type-configs/${groupType}`);
|
|
1600
|
+
}
|
|
1601
|
+
async createGroupTypeConfig(appId, data) {
|
|
1602
|
+
return this.post(`/app/${appId}/api/group-type-configs`, data);
|
|
1603
|
+
}
|
|
1604
|
+
async updateGroupTypeConfig(appId, groupType, data, expectedModifiedAt) {
|
|
1605
|
+
const body = expectedModifiedAt ? { ...data, expectedModifiedAt } : data;
|
|
1606
|
+
return this.patch(`/app/${appId}/api/group-type-configs/${groupType}`, body);
|
|
1607
|
+
}
|
|
1608
|
+
async deleteGroupTypeConfig(appId, groupType) {
|
|
1609
|
+
return this.delete(`/app/${appId}/api/group-type-configs/${groupType}`);
|
|
1610
|
+
}
|
|
1611
|
+
// ============================================
|
|
1612
|
+
// DOCUMENT EXPORT / IMPORT
|
|
1613
|
+
// ============================================
|
|
1614
|
+
async exportDocumentState(appId, documentId) {
|
|
1615
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/export/state`);
|
|
1616
|
+
}
|
|
1617
|
+
async importDocumentState(appId, documentId, stateBase64) {
|
|
1618
|
+
return this.post(`/app/${appId}/api/documents/${documentId}/import/state`, { state: stateBase64 });
|
|
1619
|
+
}
|
|
1620
|
+
async getDocument(appId, documentId) {
|
|
1621
|
+
return this.get(`/app/${appId}/api/documents/${documentId}`);
|
|
1622
|
+
}
|
|
1623
|
+
async createDocument(appId, data) {
|
|
1624
|
+
return this.post(`/app/${appId}/api/documents`, data);
|
|
1625
|
+
}
|
|
1626
|
+
async listDocumentPermissions(appId, documentId) {
|
|
1627
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/permissions`);
|
|
1628
|
+
}
|
|
1629
|
+
async grantDocumentPermission(appId, documentId, permissions) {
|
|
1630
|
+
return this.put(`/app/${appId}/api/documents/${documentId}/permissions`, { permissions });
|
|
1631
|
+
}
|
|
1632
|
+
async listDocumentInvitations(appId, documentId) {
|
|
1633
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/invitations`);
|
|
1634
|
+
}
|
|
1635
|
+
async listDocumentBlobs(appId, documentId) {
|
|
1636
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/blobs`);
|
|
1637
|
+
}
|
|
1638
|
+
async downloadBlob(appId, documentId, blobId) {
|
|
1639
|
+
const credentials = await this.ensureAuthenticated();
|
|
1640
|
+
const url = `${credentials.serverUrl}/app/${appId}/api/documents/${documentId}/blobs/${blobId}/download`;
|
|
1641
|
+
const headers = {
|
|
1642
|
+
Authorization: `Bearer ${credentials.accessToken}`,
|
|
1643
|
+
};
|
|
1644
|
+
if (credentials.globalAdminAppId) {
|
|
1645
|
+
headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
|
|
1646
|
+
}
|
|
1647
|
+
const response = await fetchWithTLS(url, { headers });
|
|
1648
|
+
if (!response.ok) {
|
|
1649
|
+
throw new ApiError(`Failed to download blob: ${response.statusText}`, response.status);
|
|
1650
|
+
}
|
|
1651
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
1652
|
+
return Buffer.from(arrayBuffer);
|
|
1653
|
+
}
|
|
1654
|
+
async uploadBlob(appId, documentId, blobId, data, meta) {
|
|
1655
|
+
const credentials = await this.ensureAuthenticated();
|
|
1656
|
+
const url = `${credentials.serverUrl}/app/${appId}/api/documents/${documentId}/blobs/${blobId}`;
|
|
1657
|
+
const headers = {
|
|
1658
|
+
Authorization: `Bearer ${credentials.accessToken}`,
|
|
1659
|
+
"Content-Type": meta.contentType,
|
|
1660
|
+
"X-Blob-Filename": encodeURIComponent(meta.filename),
|
|
1661
|
+
"X-Blob-Size": String(data.length),
|
|
1662
|
+
"X-Blob-Sha256": meta.sha256,
|
|
1663
|
+
};
|
|
1664
|
+
if (credentials.globalAdminAppId) {
|
|
1665
|
+
headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
|
|
1666
|
+
}
|
|
1667
|
+
const response = await fetchWithTLS(url, {
|
|
1668
|
+
method: "PUT",
|
|
1669
|
+
headers,
|
|
1670
|
+
body: data,
|
|
1671
|
+
});
|
|
1672
|
+
if (!response.ok) {
|
|
1673
|
+
const text = await response.text();
|
|
1674
|
+
throw new ApiError(`Failed to upload blob: ${text}`, response.status);
|
|
1675
|
+
}
|
|
1676
|
+
return response.json();
|
|
1677
|
+
}
|
|
1678
|
+
async listDocumentAliases(appId, documentId) {
|
|
1679
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/aliases`);
|
|
1680
|
+
}
|
|
1681
|
+
async setDocumentAlias(appId, aliasScope, aliasKey, documentId, ownerUserId, mustNotExist) {
|
|
1682
|
+
return this.put(`/app/${appId}/api/document-aliases/${aliasScope}/${encodeURIComponent(aliasKey)}`, {
|
|
1683
|
+
documentId,
|
|
1684
|
+
userId: ownerUserId,
|
|
1685
|
+
mustNotExist,
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
async findUserByEmail(appId, email) {
|
|
1689
|
+
try {
|
|
1690
|
+
const result = await this.get(`/app/${appId}/api/users`, { email });
|
|
1691
|
+
const items = result?.items || (Array.isArray(result) ? result : []);
|
|
1692
|
+
return items[0] || null;
|
|
1693
|
+
}
|
|
1694
|
+
catch {
|
|
1695
|
+
return null;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
async listAdminDocuments(appId, userId) {
|
|
1699
|
+
const result = await this.get(`/admin/api/apps/${appId}/documents`, { userId });
|
|
1700
|
+
return result?.documents || result || [];
|
|
1701
|
+
}
|
|
1702
|
+
// ============================================
|
|
1703
|
+
// DATABASE EXPORT / IMPORT
|
|
1704
|
+
// ============================================
|
|
1705
|
+
async saveDatabaseRecord(appId, databaseId, modelName, id, data) {
|
|
1706
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/admin-data/save`, {
|
|
1707
|
+
modelName,
|
|
1708
|
+
id,
|
|
1709
|
+
data,
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
async batchDatabaseRecords(appId, databaseId, operations) {
|
|
1713
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/admin-data/batch`, { operations });
|
|
1714
|
+
}
|
|
1715
|
+
async deleteDatabaseRecord(appId, databaseId, modelName, id) {
|
|
1716
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/admin-data/delete`, { modelName, id });
|
|
1717
|
+
}
|
|
1718
|
+
async batchDeleteDatabaseRecords(appId, databaseId, operations) {
|
|
1719
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/admin-data/batch`, { operations });
|
|
1720
|
+
}
|
|
1721
|
+
async listDatabaseUniqueConstraints(appId, databaseId) {
|
|
1722
|
+
const result = await this.get(`/app/${appId}/api/databases/${databaseId}/records/unique-constraints`);
|
|
1723
|
+
return result?.constraints || result || [];
|
|
1724
|
+
}
|
|
1725
|
+
async registerDatabaseUniqueConstraint(appId, databaseId, constraint) {
|
|
1726
|
+
return this.post(`/app/${appId}/api/databases/${databaseId}/records/unique-constraint/register`, constraint);
|
|
1727
|
+
}
|
|
1728
|
+
// ============================================
|
|
1729
|
+
// BLOB BUCKETS
|
|
1730
|
+
// ============================================
|
|
1731
|
+
async listBlobBuckets(appId) {
|
|
1732
|
+
return this.get(`/app/${appId}/api/blob-buckets`);
|
|
1733
|
+
}
|
|
1734
|
+
async createBlobBucket(appId, payload) {
|
|
1735
|
+
return this.post(`/app/${appId}/api/blob-buckets`, payload);
|
|
1736
|
+
}
|
|
1737
|
+
async getBlobBucket(appId, bucketIdOrKey) {
|
|
1738
|
+
return this.get(`/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}`);
|
|
1739
|
+
}
|
|
1740
|
+
async updateBlobBucket(appId, bucketIdOrKey, payload) {
|
|
1741
|
+
return this.patch(`/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}`, payload);
|
|
1742
|
+
}
|
|
1743
|
+
async deleteBlobBucket(appId, bucketIdOrKey) {
|
|
1744
|
+
return this.delete(`/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}`);
|
|
1745
|
+
}
|
|
1746
|
+
async listBucketBlobs(appId, bucketIdOrKey, params) {
|
|
1747
|
+
return this.get(`/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}/blobs`, params);
|
|
1748
|
+
}
|
|
1749
|
+
async uploadBucketBlob(appId, bucketIdOrKey, data, meta) {
|
|
1750
|
+
const credentials = await this.ensureAuthenticated();
|
|
1751
|
+
const url = `${credentials.serverUrl}/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}/blobs`;
|
|
1752
|
+
const headers = {
|
|
1753
|
+
Authorization: `Bearer ${credentials.accessToken}`,
|
|
1754
|
+
"Content-Type": meta.contentType,
|
|
1755
|
+
"X-Blob-Filename": encodeURIComponent(meta.filename),
|
|
1756
|
+
};
|
|
1757
|
+
if (meta.tags && meta.tags.length > 0) {
|
|
1758
|
+
headers["X-Blob-Tags"] = JSON.stringify(meta.tags);
|
|
1759
|
+
}
|
|
1760
|
+
if (credentials.globalAdminAppId) {
|
|
1761
|
+
headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
|
|
1762
|
+
}
|
|
1763
|
+
const response = await fetchWithTLS(url, {
|
|
1764
|
+
method: "POST",
|
|
1765
|
+
headers,
|
|
1766
|
+
body: data,
|
|
1767
|
+
});
|
|
1768
|
+
if (!response.ok) {
|
|
1769
|
+
const text = await response.text();
|
|
1770
|
+
throw new ApiError(`Failed to upload blob: ${text}`, response.status);
|
|
1771
|
+
}
|
|
1772
|
+
return response.json();
|
|
1773
|
+
}
|
|
1774
|
+
async downloadBucketBlob(appId, bucketIdOrKey, blobId) {
|
|
1775
|
+
const credentials = await this.ensureAuthenticated();
|
|
1776
|
+
const url = `${credentials.serverUrl}/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}/blobs/${blobId}`;
|
|
1777
|
+
const headers = {
|
|
1778
|
+
Authorization: `Bearer ${credentials.accessToken}`,
|
|
1779
|
+
};
|
|
1780
|
+
if (credentials.globalAdminAppId) {
|
|
1781
|
+
headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
|
|
1782
|
+
}
|
|
1783
|
+
const response = await fetchWithTLS(url, { headers });
|
|
1784
|
+
if (!response.ok) {
|
|
1785
|
+
throw new ApiError(`Failed to download blob: ${response.statusText}`, response.status);
|
|
1786
|
+
}
|
|
1787
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
1788
|
+
return Buffer.from(arrayBuffer);
|
|
1789
|
+
}
|
|
1790
|
+
async deleteBucketBlob(appId, bucketIdOrKey, blobId) {
|
|
1791
|
+
return this.delete(`/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}/blobs/${blobId}`);
|
|
1792
|
+
}
|
|
1793
|
+
async getBucketBlobSignedUrl(appId, bucketIdOrKey, blobId, expiresInSeconds) {
|
|
1794
|
+
return this.post(`/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}/blobs/${blobId}/signed-url`, {
|
|
1795
|
+
expiresInSeconds: expiresInSeconds || 300,
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
766
1798
|
}
|
|
767
1799
|
// Export a singleton instance
|
|
768
1800
|
export const apiClient = new ApiClient();
|