primitive-admin 1.1.0-alpha.73 → 1.1.0-alpha.74
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 +18 -25
- package/assets/skill/skills/primitive-platform/SKILL.md +30 -14
- package/dist/bin/primitive.js +14 -11
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/analytics.js +117 -2
- package/dist/src/commands/analytics.js.map +1 -1
- package/dist/src/commands/auth.js +13 -107
- package/dist/src/commands/auth.js.map +1 -1
- package/dist/src/commands/collection-type-configs.js +1 -1
- package/dist/src/commands/collection-type-configs.js.map +1 -1
- package/dist/src/commands/database-type-configs.js +1 -1
- package/dist/src/commands/database-type-configs.js.map +1 -1
- package/dist/src/commands/databases.js +1 -1
- package/dist/src/commands/databases.js.map +1 -1
- package/dist/src/commands/documents.js +749 -29
- package/dist/src/commands/documents.js.map +1 -1
- package/dist/src/commands/env.js +44 -6
- package/dist/src/commands/env.js.map +1 -1
- package/dist/src/commands/functions.d.ts +20 -0
- package/dist/src/commands/functions.js +329 -0
- package/dist/src/commands/functions.js.map +1 -0
- package/dist/src/commands/groups.js +1 -1
- package/dist/src/commands/groups.js.map +1 -1
- package/dist/src/commands/init.js +2 -15
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/integrations.js +5 -3
- package/dist/src/commands/integrations.js.map +1 -1
- package/dist/src/commands/prompts.js +5 -7
- package/dist/src/commands/prompts.js.map +1 -1
- package/dist/src/commands/rule-sets.js +1 -1
- package/dist/src/commands/rule-sets.js.map +1 -1
- package/dist/src/commands/scripts.js +6 -7
- package/dist/src/commands/scripts.js.map +1 -1
- package/dist/src/commands/sync.d.ts +63 -0
- package/dist/src/commands/sync.js +650 -2
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/workflows.js +5 -7
- package/dist/src/commands/workflows.js.map +1 -1
- package/dist/src/lib/api-client.d.ts +156 -1
- package/dist/src/lib/api-client.js +219 -3
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/app-settings-descriptor.js +4 -0
- package/dist/src/lib/app-settings-descriptor.js.map +1 -1
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +5 -7
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js.map +1 -1
- package/dist/src/lib/config-object-descriptor.js +39 -0
- package/dist/src/lib/config-object-descriptor.js.map +1 -1
- package/dist/src/lib/config-surface.d.ts +10 -0
- package/dist/src/lib/config-surface.js +17 -0
- package/dist/src/lib/config-surface.js.map +1 -1
- package/dist/src/lib/config.d.ts +13 -2
- package/dist/src/lib/config.js +20 -37
- package/dist/src/lib/config.js.map +1 -1
- package/dist/src/lib/credentials-store.d.ts +4 -12
- package/dist/src/lib/credentials-store.js +49 -82
- package/dist/src/lib/credentials-store.js.map +1 -1
- package/dist/src/lib/env-resolver-core.d.ts +23 -7
- package/dist/src/lib/env-resolver-core.js +44 -4
- package/dist/src/lib/env-resolver-core.js.map +1 -1
- package/dist/src/lib/function-bundle.d.ts +131 -0
- package/dist/src/lib/function-bundle.js +321 -0
- package/dist/src/lib/function-bundle.js.map +1 -0
- package/dist/src/lib/function-sync.d.ts +157 -0
- package/dist/src/lib/function-sync.js +371 -0
- package/dist/src/lib/function-sync.js.map +1 -0
- package/dist/src/lib/function-triggers.d.ts +54 -0
- package/dist/src/lib/function-triggers.js +223 -0
- package/dist/src/lib/function-triggers.js.map +1 -0
- package/dist/src/lib/generated-config-surfaces.d.ts +1 -0
- package/dist/src/lib/generated-config-surfaces.js +279 -1
- package/dist/src/lib/generated-config-surfaces.js.map +1 -1
- package/dist/src/lib/init-ios-links.d.ts +6 -5
- package/dist/src/lib/init-ios-links.js +8 -7
- package/dist/src/lib/init-ios-links.js.map +1 -1
- package/dist/src/lib/init-xcode.d.ts +4 -3
- package/dist/src/lib/init-xcode.js +28 -5
- package/dist/src/lib/init-xcode.js.map +1 -1
- package/dist/src/lib/ios-app-id.d.ts +34 -0
- package/dist/src/lib/ios-app-id.js +69 -0
- package/dist/src/lib/ios-app-id.js.map +1 -0
- package/dist/src/lib/project-config.d.ts +15 -1
- package/dist/src/lib/project-config.js +31 -3
- package/dist/src/lib/project-config.js.map +1 -1
- package/dist/src/lib/sync-resource-types.d.ts +21 -2
- package/dist/src/lib/sync-resource-types.js +104 -6
- package/dist/src/lib/sync-resource-types.js.map +1 -1
- package/dist/src/lib/test-case-variables.d.ts +14 -0
- package/dist/src/lib/test-case-variables.js +42 -0
- package/dist/src/lib/test-case-variables.js.map +1 -1
- package/dist/src/lib/workflow-usage.d.ts +198 -0
- package/dist/src/lib/workflow-usage.js +312 -0
- package/dist/src/lib/workflow-usage.js.map +1 -0
- package/dist/src/types/index.d.ts +12 -2
- package/package.json +2 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ListEnvelope } from "./paginate.js";
|
|
2
|
+
import { type GlobalAppRef, type WorkflowUsageReport } from "./workflow-usage.js";
|
|
2
3
|
/**
|
|
3
4
|
* Metadata for a single bucket blob, as returned by the metadata endpoint
|
|
4
5
|
* `GET blob-buckets/:bucketId/blobs/:blobId/metadata`. Mirrors the published
|
|
@@ -212,10 +213,19 @@ interface AnalyticsUserSummary {
|
|
|
212
213
|
lastSeen: string;
|
|
213
214
|
eventCount: number;
|
|
214
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* A `users.top` row. `firstSeen` is the user's first event ever, invariant
|
|
218
|
+
* under `windowDays`; `firstSeenInWindow` is the window-relative one (#3126).
|
|
219
|
+
* `users.search` rows carry no `firstSeenInWindow`: it queries the full
|
|
220
|
+
* retained range, so its `firstSeen` is already the all-time value.
|
|
221
|
+
*/
|
|
222
|
+
interface AnalyticsTopUserRow extends AnalyticsUserSummary {
|
|
223
|
+
firstSeenInWindow: string;
|
|
224
|
+
}
|
|
215
225
|
interface AnalyticsTopUsersResponse {
|
|
216
226
|
windowDays: number;
|
|
217
227
|
limit: number;
|
|
218
|
-
results:
|
|
228
|
+
results: AnalyticsTopUserRow[];
|
|
219
229
|
_timing: AnalyticsTiming;
|
|
220
230
|
}
|
|
221
231
|
interface AnalyticsUserSearchResponse {
|
|
@@ -257,6 +267,9 @@ interface AnalyticsUserSnapshotResponse {
|
|
|
257
267
|
interface AnalyticsEventRow {
|
|
258
268
|
timestamp: string;
|
|
259
269
|
user_ulid: string;
|
|
270
|
+
/** Display name for `user_ulid` — the system principal's `System`, else
|
|
271
|
+
* null. The id itself is unchanged (#3168). */
|
|
272
|
+
user_name: string | null;
|
|
260
273
|
action: string;
|
|
261
274
|
feature: string;
|
|
262
275
|
route: string;
|
|
@@ -468,6 +481,22 @@ export declare class ApiClient {
|
|
|
468
481
|
role?: string;
|
|
469
482
|
}>;
|
|
470
483
|
}>;
|
|
484
|
+
/**
|
|
485
|
+
* Every app on the server, not just the caller's own.
|
|
486
|
+
*
|
|
487
|
+
* Deliberately separate from `listApps()`, whose contract is "apps assigned
|
|
488
|
+
* to me" (`GET /admin/api/admins/me/apps`, backed by `admin.queryApps()`) —
|
|
489
|
+
* the contract every other command depends on. `analytics workflow-usage
|
|
490
|
+
* --all-apps` has to reach apps nobody is assigned to, which is the global
|
|
491
|
+
* route `GET /admin/api/apps` (`App.iterateAll()`), and that route
|
|
492
|
+
* identifies an app as `appId` rather than `id` (D3130-001).
|
|
493
|
+
*
|
|
494
|
+
* One drain of that route is lossy — its cursor re-scans `App.iterateAll()`
|
|
495
|
+
* from the start of each page, so a list that changes under it both repeats
|
|
496
|
+
* and drops rows — so the walk is the union of repeated drains
|
|
497
|
+
* (`drainAppsUntilConverged`, which carries the measurement).
|
|
498
|
+
*/
|
|
499
|
+
listAllAppsGlobal(): Promise<GlobalAppRef[]>;
|
|
471
500
|
createApp(data: {
|
|
472
501
|
name: string;
|
|
473
502
|
initialAdminEmail?: string;
|
|
@@ -663,6 +692,48 @@ export declare class ApiClient {
|
|
|
663
692
|
* and refused on every configuration write path.
|
|
664
693
|
*/
|
|
665
694
|
setWebhookAvailability(appId: string, webhookId: string, action: "enable" | "disable"): Promise<any>;
|
|
695
|
+
listFunctions(appId: string): Promise<{
|
|
696
|
+
items: any[];
|
|
697
|
+
}>;
|
|
698
|
+
getFunction(appId: string, functionId: string): Promise<any>;
|
|
699
|
+
createFunction(appId: string, payload: any): Promise<any>;
|
|
700
|
+
updateFunction(appId: string, functionId: string, payload: any): Promise<any>;
|
|
701
|
+
/**
|
|
702
|
+
* Archive a function, or destroy it with `hard`. Archiving KEEPS the key
|
|
703
|
+
* reserved in the app's single key namespace; the hard delete is what
|
|
704
|
+
* releases it, destroys every config version and removes the R2 objects.
|
|
705
|
+
*/
|
|
706
|
+
deleteFunction(appId: string, functionId: string, options?: {
|
|
707
|
+
hard?: boolean;
|
|
708
|
+
}): Promise<ArchiveEnvelope>;
|
|
709
|
+
/** #2803 — the operational verb pair for a server function. */
|
|
710
|
+
setFunctionAvailability(appId: string, functionId: string, action: "enable" | "disable"): Promise<any>;
|
|
711
|
+
/** Every config version of a function, oldest first. */
|
|
712
|
+
/**
|
|
713
|
+
* A function's runs, newest first (#3181).
|
|
714
|
+
*
|
|
715
|
+
* A function run has no `workflowId`, so no workflow runs listing can see
|
|
716
|
+
* it — this is the only surface that lists one. Cursor-paginated like the
|
|
717
|
+
* other admin listings.
|
|
718
|
+
*/
|
|
719
|
+
listFunctionRuns(appId: string, functionId: string, params?: {
|
|
720
|
+
limit?: number;
|
|
721
|
+
cursor?: string;
|
|
722
|
+
}): Promise<{
|
|
723
|
+
items: any[];
|
|
724
|
+
nextCursor?: string | null;
|
|
725
|
+
}>;
|
|
726
|
+
listFunctionConfigs(appId: string, functionId: string): Promise<{
|
|
727
|
+
items: any[];
|
|
728
|
+
}>;
|
|
729
|
+
getFunctionConfig(appId: string, functionId: string, configId: string): Promise<any>;
|
|
730
|
+
/**
|
|
731
|
+
* The stored envelope: the authored TOML bytes, every source file and the
|
|
732
|
+
* built bundle, all base64. This is what `config pull` writes back verbatim.
|
|
733
|
+
*/
|
|
734
|
+
getFunctionConfigEnvelope(appId: string, functionId: string, configId: string): Promise<any>;
|
|
735
|
+
/** Push a config version. The server verifies `contentHash` itself. */
|
|
736
|
+
pushFunctionConfig(appId: string, functionId: string, payload: any): Promise<any>;
|
|
666
737
|
/** #2803 — the operational verb pair for a prompt (admin API). */
|
|
667
738
|
setPromptAvailability(appId: string, promptId: string, action: "enable" | "disable"): Promise<any>;
|
|
668
739
|
/** #2803 — the operational verb pair for an integration (admin API). */
|
|
@@ -978,6 +1049,16 @@ export declare class ApiClient {
|
|
|
978
1049
|
nextCursor?: string | null;
|
|
979
1050
|
}>;
|
|
980
1051
|
getWorkflow(appId: string, workflowId: string): Promise<any>;
|
|
1052
|
+
/**
|
|
1053
|
+
* One app's workflow step usage report (#3132).
|
|
1054
|
+
*
|
|
1055
|
+
* `windowDays` is omitted from the query when the caller did not ask for a
|
|
1056
|
+
* window, so the server applies its own default rather than the CLI
|
|
1057
|
+
* restating it on the wire.
|
|
1058
|
+
*/
|
|
1059
|
+
getWorkflowUsage(appId: string, params?: {
|
|
1060
|
+
windowDays?: number;
|
|
1061
|
+
}): Promise<WorkflowUsageReport>;
|
|
981
1062
|
createWorkflow(appId: string, payload: any): Promise<any>;
|
|
982
1063
|
updateWorkflow(appId: string, workflowId: string, payload: any, expectedModifiedAt?: string): Promise<any>;
|
|
983
1064
|
/**
|
|
@@ -1136,6 +1217,20 @@ export declare class ApiClient {
|
|
|
1136
1217
|
windowDays?: number;
|
|
1137
1218
|
limit?: number;
|
|
1138
1219
|
}): Promise<TopPromptsResponse>;
|
|
1220
|
+
/**
|
|
1221
|
+
* The signed-in admin's own profile, `role` included.
|
|
1222
|
+
*
|
|
1223
|
+
* The role is read from the server rather than from the cached credentials
|
|
1224
|
+
* file: a role changed after login would otherwise let a stale local record
|
|
1225
|
+
* decide an authorization question.
|
|
1226
|
+
*/
|
|
1227
|
+
getMe(): Promise<{
|
|
1228
|
+
adminId: string;
|
|
1229
|
+
email: string;
|
|
1230
|
+
name: string;
|
|
1231
|
+
role: string;
|
|
1232
|
+
createdAt?: string;
|
|
1233
|
+
}>;
|
|
1139
1234
|
listAllAdmins(params?: {
|
|
1140
1235
|
limit?: number;
|
|
1141
1236
|
cursor?: string;
|
|
@@ -1710,7 +1805,45 @@ export declare class ApiClient {
|
|
|
1710
1805
|
byteLength: number;
|
|
1711
1806
|
documentId: string;
|
|
1712
1807
|
}>;
|
|
1808
|
+
/**
|
|
1809
|
+
* The export chain of a large document (#2816, behavior 20): the latest
|
|
1810
|
+
* completed snapshot, the sealed overlays after its epoch, and the open
|
|
1811
|
+
* epoch's overlay inline. Every other artifact arrives as an expiring
|
|
1812
|
+
* signature, read through {@link downloadDocumentArtifact}.
|
|
1813
|
+
*/
|
|
1814
|
+
exportDocumentChain(appId: string, documentId: string): Promise<any>;
|
|
1815
|
+
/**
|
|
1816
|
+
* Read one granted artifact of a large document.
|
|
1817
|
+
*
|
|
1818
|
+
* The path comes from the chain and is origin-relative — the room is a
|
|
1819
|
+
* Durable Object and has no reliable idea which host the caller reached it
|
|
1820
|
+
* through — and the signature in it is the whole authorization, so no key is
|
|
1821
|
+
* ever composed here.
|
|
1822
|
+
*/
|
|
1823
|
+
downloadDocumentArtifact(_appId: string, grantedPath: string): Promise<Buffer>;
|
|
1713
1824
|
importDocumentState(appId: string, documentId: string, stateBase64: string): Promise<any>;
|
|
1825
|
+
/**
|
|
1826
|
+
* Upload one artifact of a large document's import chain (#2816,
|
|
1827
|
+
* behavior 20).
|
|
1828
|
+
*
|
|
1829
|
+
* The bytes travel raw and unmodified: the manifest's digest is over the
|
|
1830
|
+
* STORED bytes, so re-encoding a chunk here would fail its own verification
|
|
1831
|
+
* on install. The query says which artifact it is — the server derives the
|
|
1832
|
+
* key from the document it belongs to, so nothing here composes one.
|
|
1833
|
+
*/
|
|
1834
|
+
uploadDocumentChainArtifact(appId: string, documentId: string, query: Record<string, string | number>, body: Buffer): Promise<any>;
|
|
1835
|
+
/** Start installing an uploaded chain (#2816, behavior 20). */
|
|
1836
|
+
installDocumentChain(appId: string, documentId: string, plan: {
|
|
1837
|
+
installId?: string;
|
|
1838
|
+
base: {
|
|
1839
|
+
epoch: number;
|
|
1840
|
+
buildId: string;
|
|
1841
|
+
} | null;
|
|
1842
|
+
overlays: number[];
|
|
1843
|
+
currentEpoch: number;
|
|
1844
|
+
}): Promise<any>;
|
|
1845
|
+
/** How far a document's chain install has got (#2816, behavior 20). */
|
|
1846
|
+
getDocumentChainInstall(appId: string, documentId: string): Promise<any>;
|
|
1714
1847
|
getDocument(appId: string, documentId: string): Promise<any>;
|
|
1715
1848
|
/**
|
|
1716
1849
|
* Delete a document and everything hanging off it (#2756). The server route
|
|
@@ -1830,6 +1963,7 @@ export declare class ApiClient {
|
|
|
1830
1963
|
documentId?: string;
|
|
1831
1964
|
tags?: string[];
|
|
1832
1965
|
createdBy?: string;
|
|
1966
|
+
documentFormat?: 1 | 2;
|
|
1833
1967
|
}): Promise<any>;
|
|
1834
1968
|
listDocumentPermissions(appId: string, documentId: string): Promise<any[]>;
|
|
1835
1969
|
grantDocumentPermission(appId: string, documentId: string, permissions: Array<{
|
|
@@ -1879,6 +2013,27 @@ export declare class ApiClient {
|
|
|
1879
2013
|
};
|
|
1880
2014
|
}>;
|
|
1881
2015
|
listAdminDocuments(appId: string, userId: string): Promise<any[]>;
|
|
2016
|
+
/**
|
|
2017
|
+
* The document `AppUser.rootDocId` points at, or null when the user has none
|
|
2018
|
+
* yet (#3135). A user who is not a member of the app is a 404, which
|
|
2019
|
+
* `documents import` reads as "this export has no target here" — distinct
|
|
2020
|
+
* from "this user has no root document yet".
|
|
2021
|
+
*/
|
|
2022
|
+
getUserRootDocument(appId: string, userId: string): Promise<{
|
|
2023
|
+
rootDocId: string | null;
|
|
2024
|
+
}>;
|
|
2025
|
+
/**
|
|
2026
|
+
* Get or create the user's root document (#3135).
|
|
2027
|
+
*
|
|
2028
|
+
* The server runs the same idempotent get-or-create sign-in uses, so this
|
|
2029
|
+
* never mints a second root. `created` is true only when this call's claim
|
|
2030
|
+
* won — an import must not report "created", or merge without `--overwrite`,
|
|
2031
|
+
* for a root a concurrent sign-in minted first.
|
|
2032
|
+
*/
|
|
2033
|
+
ensureUserRootDocument(appId: string, userId: string): Promise<{
|
|
2034
|
+
rootDocId: string;
|
|
2035
|
+
created: boolean;
|
|
2036
|
+
}>;
|
|
1882
2037
|
/**
|
|
1883
2038
|
* Apply an ordered batch of record operations via `records/batch`.
|
|
1884
2039
|
*
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { loadCredentials, saveCredentials, isTokenExpiringSoon, } from "./config.js";
|
|
2
|
+
import { getCurrentEnvNameSafe } from "./env-resolver.js";
|
|
2
3
|
import { fetchWithTLS } from "./fetch.js";
|
|
3
4
|
import { paginateAll, normalizeCliListEnvelope } from "./paginate.js";
|
|
4
5
|
import { RefreshError, refreshAdminCredentials, } from "./refresh-admin-credentials.js";
|
|
6
|
+
import { drainAppsUntilConverged, } from "./workflow-usage.js";
|
|
5
7
|
export class ApiError extends Error {
|
|
6
8
|
statusCode;
|
|
7
9
|
code;
|
|
@@ -211,7 +213,15 @@ export class ApiClient {
|
|
|
211
213
|
this.credentials = loadCredentials();
|
|
212
214
|
}
|
|
213
215
|
if (!this.credentials) {
|
|
214
|
-
|
|
216
|
+
// Name the environment when there is one (#1293). Before #3152 this case
|
|
217
|
+
// surfaced from `resolveAppId`, because an unauthenticated environment
|
|
218
|
+
// resolved no app at all; now the environment always names its app, so
|
|
219
|
+
// "not logged in" is only ever an auth failure, and it is reported here,
|
|
220
|
+
// once, for every command.
|
|
221
|
+
const envName = getCurrentEnvNameSafe();
|
|
222
|
+
throw new ApiError(envName
|
|
223
|
+
? `Not logged in to environment "${envName}". Run 'primitive -e ${envName} login'.`
|
|
224
|
+
: "Not logged in. Run 'primitive login' first.", 401);
|
|
215
225
|
}
|
|
216
226
|
// Check if token needs refresh
|
|
217
227
|
if (isTokenExpiringSoon(this.credentials)) {
|
|
@@ -345,8 +355,8 @@ export class ApiClient {
|
|
|
345
355
|
async listApps() {
|
|
346
356
|
// The server paginates cursor-style (default pageSize=25, cap=100). Historically
|
|
347
357
|
// this method issued a single un-paginated GET and silently truncated to the
|
|
348
|
-
// first page, which broke `primitive
|
|
349
|
-
//
|
|
358
|
+
// first page, which broke `primitive apps list` for admins with more than
|
|
359
|
+
// 25 apps (see #436). Iterate every page.
|
|
350
360
|
const apps = await paginateAll(async (cursor) => {
|
|
351
361
|
const qs = new URLSearchParams({ limit: "100" });
|
|
352
362
|
if (cursor)
|
|
@@ -363,6 +373,40 @@ export class ApiClient {
|
|
|
363
373
|
});
|
|
364
374
|
return { apps };
|
|
365
375
|
}
|
|
376
|
+
/**
|
|
377
|
+
* Every app on the server, not just the caller's own.
|
|
378
|
+
*
|
|
379
|
+
* Deliberately separate from `listApps()`, whose contract is "apps assigned
|
|
380
|
+
* to me" (`GET /admin/api/admins/me/apps`, backed by `admin.queryApps()`) —
|
|
381
|
+
* the contract every other command depends on. `analytics workflow-usage
|
|
382
|
+
* --all-apps` has to reach apps nobody is assigned to, which is the global
|
|
383
|
+
* route `GET /admin/api/apps` (`App.iterateAll()`), and that route
|
|
384
|
+
* identifies an app as `appId` rather than `id` (D3130-001).
|
|
385
|
+
*
|
|
386
|
+
* One drain of that route is lossy — its cursor re-scans `App.iterateAll()`
|
|
387
|
+
* from the start of each page, so a list that changes under it both repeats
|
|
388
|
+
* and drops rows — so the walk is the union of repeated drains
|
|
389
|
+
* (`drainAppsUntilConverged`, which carries the measurement).
|
|
390
|
+
*/
|
|
391
|
+
async listAllAppsGlobal() {
|
|
392
|
+
const drainOnce = () => paginateAll(async (cursor) => {
|
|
393
|
+
const qs = new URLSearchParams({ limit: "100" });
|
|
394
|
+
if (cursor)
|
|
395
|
+
qs.set("cursor", cursor);
|
|
396
|
+
const resp = await this.get(`/admin/api/apps?${qs.toString()}`);
|
|
397
|
+
// `items` first, with the `apps` alias (#1316) as the fallback,
|
|
398
|
+
// exactly as `listApps()` reads its own envelope.
|
|
399
|
+
const rows = resp.items ?? (Array.isArray(resp.apps) ? resp.apps : []);
|
|
400
|
+
return {
|
|
401
|
+
items: rows.map((row) => ({
|
|
402
|
+
appId: String(row.appId ?? row.id ?? ""),
|
|
403
|
+
name: String(row.name ?? ""),
|
|
404
|
+
})),
|
|
405
|
+
nextCursor: resp.nextCursor ?? resp.cursor,
|
|
406
|
+
};
|
|
407
|
+
});
|
|
408
|
+
return drainAppsUntilConverged(drainOnce);
|
|
409
|
+
}
|
|
366
410
|
async createApp(data) {
|
|
367
411
|
return this.post("/admin/api/apps", data);
|
|
368
412
|
}
|
|
@@ -678,6 +722,68 @@ export class ApiClient {
|
|
|
678
722
|
async setWebhookAvailability(appId, webhookId, action) {
|
|
679
723
|
return this.post(`/admin/api/apps/${appId}/webhooks/${webhookId}/${action}`, {});
|
|
680
724
|
}
|
|
725
|
+
// ── Server functions (#3179) ─────────────────────────────────────────
|
|
726
|
+
//
|
|
727
|
+
// The `functions` family mirrors workflows and scripts: a header with the
|
|
728
|
+
// gate and the entry, and append-only immutable config versions carrying the
|
|
729
|
+
// authored TOML bytes, the sources and the built bundle. The listing drains
|
|
730
|
+
// server-side, so it has no cursor.
|
|
731
|
+
async listFunctions(appId) {
|
|
732
|
+
const result = await this.get(`/admin/api/apps/${appId}/functions`);
|
|
733
|
+
return { items: result?.items ?? [] };
|
|
734
|
+
}
|
|
735
|
+
async getFunction(appId, functionId) {
|
|
736
|
+
return this.get(`/admin/api/apps/${appId}/functions/${functionId}`);
|
|
737
|
+
}
|
|
738
|
+
async createFunction(appId, payload) {
|
|
739
|
+
return this.post(`/admin/api/apps/${appId}/functions`, payload);
|
|
740
|
+
}
|
|
741
|
+
async updateFunction(appId, functionId, payload) {
|
|
742
|
+
return this.patch(`/admin/api/apps/${appId}/functions/${functionId}`, payload);
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Archive a function, or destroy it with `hard`. Archiving KEEPS the key
|
|
746
|
+
* reserved in the app's single key namespace; the hard delete is what
|
|
747
|
+
* releases it, destroys every config version and removes the R2 objects.
|
|
748
|
+
*/
|
|
749
|
+
async deleteFunction(appId, functionId, options) {
|
|
750
|
+
const query = options?.hard ? "?hard=true" : "";
|
|
751
|
+
return this.delete(`/admin/api/apps/${appId}/functions/${functionId}${query}`);
|
|
752
|
+
}
|
|
753
|
+
/** #2803 — the operational verb pair for a server function. */
|
|
754
|
+
async setFunctionAvailability(appId, functionId, action) {
|
|
755
|
+
return this.post(`/admin/api/apps/${appId}/functions/${functionId}/${action}`, {});
|
|
756
|
+
}
|
|
757
|
+
/** Every config version of a function, oldest first. */
|
|
758
|
+
/**
|
|
759
|
+
* A function's runs, newest first (#3181).
|
|
760
|
+
*
|
|
761
|
+
* A function run has no `workflowId`, so no workflow runs listing can see
|
|
762
|
+
* it — this is the only surface that lists one. Cursor-paginated like the
|
|
763
|
+
* other admin listings.
|
|
764
|
+
*/
|
|
765
|
+
async listFunctionRuns(appId, functionId, params) {
|
|
766
|
+
const result = await this.get(`/admin/api/apps/${appId}/functions/${functionId}/runs`, params);
|
|
767
|
+
return { items: result?.items ?? [], nextCursor: result?.nextCursor ?? null };
|
|
768
|
+
}
|
|
769
|
+
async listFunctionConfigs(appId, functionId) {
|
|
770
|
+
const result = await this.get(`/admin/api/apps/${appId}/functions/${functionId}/configs`);
|
|
771
|
+
return { items: result?.items ?? [] };
|
|
772
|
+
}
|
|
773
|
+
async getFunctionConfig(appId, functionId, configId) {
|
|
774
|
+
return this.get(`/admin/api/apps/${appId}/functions/${functionId}/configs/${configId}`);
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* The stored envelope: the authored TOML bytes, every source file and the
|
|
778
|
+
* built bundle, all base64. This is what `config pull` writes back verbatim.
|
|
779
|
+
*/
|
|
780
|
+
async getFunctionConfigEnvelope(appId, functionId, configId) {
|
|
781
|
+
return this.get(`/admin/api/apps/${appId}/functions/${functionId}/configs/${configId}/envelope`);
|
|
782
|
+
}
|
|
783
|
+
/** Push a config version. The server verifies `contentHash` itself. */
|
|
784
|
+
async pushFunctionConfig(appId, functionId, payload) {
|
|
785
|
+
return this.post(`/admin/api/apps/${appId}/functions/${functionId}/configs`, payload);
|
|
786
|
+
}
|
|
681
787
|
/** #2803 — the operational verb pair for a prompt (admin API). */
|
|
682
788
|
async setPromptAvailability(appId, promptId, action) {
|
|
683
789
|
return this.post(`/admin/api/apps/${appId}/prompts/${promptId}/${action}`, {});
|
|
@@ -1166,6 +1272,16 @@ export class ApiClient {
|
|
|
1166
1272
|
async getWorkflow(appId, workflowId) {
|
|
1167
1273
|
return this.get(`/admin/api/apps/${appId}/workflows/${workflowId}`);
|
|
1168
1274
|
}
|
|
1275
|
+
/**
|
|
1276
|
+
* One app's workflow step usage report (#3132).
|
|
1277
|
+
*
|
|
1278
|
+
* `windowDays` is omitted from the query when the caller did not ask for a
|
|
1279
|
+
* window, so the server applies its own default rather than the CLI
|
|
1280
|
+
* restating it on the wire.
|
|
1281
|
+
*/
|
|
1282
|
+
async getWorkflowUsage(appId, params) {
|
|
1283
|
+
return this.get(`/admin/api/apps/${appId}/workflows/usage`, params);
|
|
1284
|
+
}
|
|
1169
1285
|
async createWorkflow(appId, payload) {
|
|
1170
1286
|
return this.post(`/admin/api/apps/${appId}/workflows`, payload);
|
|
1171
1287
|
}
|
|
@@ -1327,6 +1443,16 @@ export class ApiClient {
|
|
|
1327
1443
|
// ============================================
|
|
1328
1444
|
// SUPER ADMIN: ADMINS
|
|
1329
1445
|
// ============================================
|
|
1446
|
+
/**
|
|
1447
|
+
* The signed-in admin's own profile, `role` included.
|
|
1448
|
+
*
|
|
1449
|
+
* The role is read from the server rather than from the cached credentials
|
|
1450
|
+
* file: a role changed after login would otherwise let a stale local record
|
|
1451
|
+
* decide an authorization question.
|
|
1452
|
+
*/
|
|
1453
|
+
async getMe() {
|
|
1454
|
+
return this.get("/admin/api/me");
|
|
1455
|
+
}
|
|
1330
1456
|
async listAllAdmins(params) {
|
|
1331
1457
|
const result = await this.get("/admin/api/admins", params);
|
|
1332
1458
|
// #1316: prefer `nextCursor`; fall back to the deprecated `cursor` alias.
|
|
@@ -2133,9 +2259,79 @@ export class ApiClient {
|
|
|
2133
2259
|
async exportDocumentState(appId, documentId) {
|
|
2134
2260
|
return this.get(`/app/${appId}/api/documents/${documentId}/export/state`);
|
|
2135
2261
|
}
|
|
2262
|
+
/**
|
|
2263
|
+
* The export chain of a large document (#2816, behavior 20): the latest
|
|
2264
|
+
* completed snapshot, the sealed overlays after its epoch, and the open
|
|
2265
|
+
* epoch's overlay inline. Every other artifact arrives as an expiring
|
|
2266
|
+
* signature, read through {@link downloadDocumentArtifact}.
|
|
2267
|
+
*/
|
|
2268
|
+
async exportDocumentChain(appId, documentId) {
|
|
2269
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/export/chain`);
|
|
2270
|
+
}
|
|
2271
|
+
/**
|
|
2272
|
+
* Read one granted artifact of a large document.
|
|
2273
|
+
*
|
|
2274
|
+
* The path comes from the chain and is origin-relative — the room is a
|
|
2275
|
+
* Durable Object and has no reliable idea which host the caller reached it
|
|
2276
|
+
* through — and the signature in it is the whole authorization, so no key is
|
|
2277
|
+
* ever composed here.
|
|
2278
|
+
*/
|
|
2279
|
+
async downloadDocumentArtifact(_appId, grantedPath) {
|
|
2280
|
+
const credentials = await this.ensureAuthenticated();
|
|
2281
|
+
const url = `${credentials.serverUrl}${grantedPath}`;
|
|
2282
|
+
const response = await fetchWithTLS(url);
|
|
2283
|
+
if (!response.ok) {
|
|
2284
|
+
throw new ApiError(`Failed to download document artifact: ${response.statusText}`, response.status);
|
|
2285
|
+
}
|
|
2286
|
+
return Buffer.from(await response.arrayBuffer());
|
|
2287
|
+
}
|
|
2136
2288
|
async importDocumentState(appId, documentId, stateBase64) {
|
|
2137
2289
|
return this.post(`/app/${appId}/api/documents/${documentId}/import/state`, { state: stateBase64 });
|
|
2138
2290
|
}
|
|
2291
|
+
/**
|
|
2292
|
+
* Upload one artifact of a large document's import chain (#2816,
|
|
2293
|
+
* behavior 20).
|
|
2294
|
+
*
|
|
2295
|
+
* The bytes travel raw and unmodified: the manifest's digest is over the
|
|
2296
|
+
* STORED bytes, so re-encoding a chunk here would fail its own verification
|
|
2297
|
+
* on install. The query says which artifact it is — the server derives the
|
|
2298
|
+
* key from the document it belongs to, so nothing here composes one.
|
|
2299
|
+
*/
|
|
2300
|
+
async uploadDocumentChainArtifact(appId, documentId, query, body) {
|
|
2301
|
+
const credentials = await this.ensureAuthenticated();
|
|
2302
|
+
const search = new URLSearchParams();
|
|
2303
|
+
for (const [key, value] of Object.entries(query)) {
|
|
2304
|
+
search.set(key, String(value));
|
|
2305
|
+
}
|
|
2306
|
+
const url = `${credentials.serverUrl}/app/${appId}/api/documents/${documentId}` +
|
|
2307
|
+
`/import/chain/artifact?${search.toString()}`;
|
|
2308
|
+
const headers = {
|
|
2309
|
+
Authorization: `Bearer ${credentials.accessToken}`,
|
|
2310
|
+
"Content-Type": "application/octet-stream",
|
|
2311
|
+
};
|
|
2312
|
+
if (credentials.globalAdminAppId) {
|
|
2313
|
+
headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
|
|
2314
|
+
}
|
|
2315
|
+
const response = await fetchWithTLS(url, {
|
|
2316
|
+
method: "POST",
|
|
2317
|
+
headers,
|
|
2318
|
+
body: body,
|
|
2319
|
+
});
|
|
2320
|
+
const result = await response.json().catch(() => ({}));
|
|
2321
|
+
if (!response.ok) {
|
|
2322
|
+
throw new ApiError(result?.error ||
|
|
2323
|
+
`Failed to upload import artifact: ${response.statusText}`, response.status);
|
|
2324
|
+
}
|
|
2325
|
+
return result;
|
|
2326
|
+
}
|
|
2327
|
+
/** Start installing an uploaded chain (#2816, behavior 20). */
|
|
2328
|
+
async installDocumentChain(appId, documentId, plan) {
|
|
2329
|
+
return this.post(`/app/${appId}/api/documents/${documentId}/import/chain`, plan);
|
|
2330
|
+
}
|
|
2331
|
+
/** How far a document's chain install has got (#2816, behavior 20). */
|
|
2332
|
+
async getDocumentChainInstall(appId, documentId) {
|
|
2333
|
+
return this.get(`/app/${appId}/api/documents/${documentId}/import/chain`);
|
|
2334
|
+
}
|
|
2139
2335
|
async getDocument(appId, documentId) {
|
|
2140
2336
|
return this.get(`/app/${appId}/api/documents/${documentId}`);
|
|
2141
2337
|
}
|
|
@@ -2352,6 +2548,26 @@ export class ApiClient {
|
|
|
2352
2548
|
const result = await this.get(`/admin/api/apps/${appId}/documents`, { userId });
|
|
2353
2549
|
return result?.documents || result || [];
|
|
2354
2550
|
}
|
|
2551
|
+
/**
|
|
2552
|
+
* The document `AppUser.rootDocId` points at, or null when the user has none
|
|
2553
|
+
* yet (#3135). A user who is not a member of the app is a 404, which
|
|
2554
|
+
* `documents import` reads as "this export has no target here" — distinct
|
|
2555
|
+
* from "this user has no root document yet".
|
|
2556
|
+
*/
|
|
2557
|
+
async getUserRootDocument(appId, userId) {
|
|
2558
|
+
return this.get(`/admin/api/apps/${appId}/users/${userId}/root-document`);
|
|
2559
|
+
}
|
|
2560
|
+
/**
|
|
2561
|
+
* Get or create the user's root document (#3135).
|
|
2562
|
+
*
|
|
2563
|
+
* The server runs the same idempotent get-or-create sign-in uses, so this
|
|
2564
|
+
* never mints a second root. `created` is true only when this call's claim
|
|
2565
|
+
* won — an import must not report "created", or merge without `--overwrite`,
|
|
2566
|
+
* for a root a concurrent sign-in minted first.
|
|
2567
|
+
*/
|
|
2568
|
+
async ensureUserRootDocument(appId, userId) {
|
|
2569
|
+
return this.post(`/admin/api/apps/${appId}/users/${userId}/root-document`, {});
|
|
2570
|
+
}
|
|
2355
2571
|
// ============================================
|
|
2356
2572
|
// DATABASE EXPORT / IMPORT
|
|
2357
2573
|
// ============================================
|