primitive-admin 1.1.0-alpha.81 → 1.1.0-alpha.83

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/assets/skill/skills/primitive-platform/SKILL.md +12 -0
  2. package/dist/src/commands/documents.js +455 -22
  3. package/dist/src/commands/documents.js.map +1 -1
  4. package/dist/src/commands/functions.js +889 -76
  5. package/dist/src/commands/functions.js.map +1 -1
  6. package/dist/src/commands/locks.js +15 -1
  7. package/dist/src/commands/locks.js.map +1 -1
  8. package/dist/src/commands/prompts.js +19 -2
  9. package/dist/src/commands/prompts.js.map +1 -1
  10. package/dist/src/commands/sync.d.ts +50 -0
  11. package/dist/src/commands/sync.js +587 -316
  12. package/dist/src/commands/sync.js.map +1 -1
  13. package/dist/src/commands/workflows.d.ts +1 -29
  14. package/dist/src/commands/workflows.js +8 -109
  15. package/dist/src/commands/workflows.js.map +1 -1
  16. package/dist/src/lib/api-client.d.ts +191 -2
  17. package/dist/src/lib/api-client.js +283 -7
  18. package/dist/src/lib/api-client.js.map +1 -1
  19. package/dist/src/lib/config-object-descriptor.js +12 -4
  20. package/dist/src/lib/config-object-descriptor.js.map +1 -1
  21. package/dist/src/lib/document-ingest-artifact.d.ts +120 -0
  22. package/dist/src/lib/document-ingest-artifact.js +505 -0
  23. package/dist/src/lib/document-ingest-artifact.js.map +1 -0
  24. package/dist/src/lib/document-ingest-input.d.ts +51 -0
  25. package/dist/src/lib/document-ingest-input.js +132 -0
  26. package/dist/src/lib/document-ingest-input.js.map +1 -0
  27. package/dist/src/lib/document-ingest.d.ts +78 -0
  28. package/dist/src/lib/document-ingest.js +231 -0
  29. package/dist/src/lib/document-ingest.js.map +1 -0
  30. package/dist/src/lib/env-resolver-core.d.ts +2 -2
  31. package/dist/src/lib/env-resolver-core.js +2 -2
  32. package/dist/src/lib/function-bundle.d.ts +6 -0
  33. package/dist/src/lib/function-bundle.js +8 -0
  34. package/dist/src/lib/function-bundle.js.map +1 -1
  35. package/dist/src/lib/function-collect.d.ts +1 -1
  36. package/dist/src/lib/function-collect.js +16 -0
  37. package/dist/src/lib/function-collect.js.map +1 -1
  38. package/dist/src/lib/function-db-types.d.ts +4 -0
  39. package/dist/src/lib/function-db-types.js +46 -2
  40. package/dist/src/lib/function-db-types.js.map +1 -1
  41. package/dist/src/lib/function-document-types.d.ts +83 -0
  42. package/dist/src/lib/function-document-types.js +137 -0
  43. package/dist/src/lib/function-document-types.js.map +1 -1
  44. package/dist/src/lib/function-grants-preflight.js +5 -0
  45. package/dist/src/lib/function-grants-preflight.js.map +1 -1
  46. package/dist/src/lib/function-log-lines.d.ts +76 -0
  47. package/dist/src/lib/function-log-lines.js +160 -0
  48. package/dist/src/lib/function-log-lines.js.map +1 -0
  49. package/dist/src/lib/function-log-row.d.ts +29 -0
  50. package/dist/src/lib/function-log-row.js +73 -0
  51. package/dist/src/lib/function-log-row.js.map +1 -0
  52. package/dist/src/lib/function-log-tail.d.ts +51 -2
  53. package/dist/src/lib/function-log-tail.js +160 -13
  54. package/dist/src/lib/function-log-tail.js.map +1 -1
  55. package/dist/src/lib/function-run.d.ts +289 -0
  56. package/dist/src/lib/function-run.js +389 -0
  57. package/dist/src/lib/function-run.js.map +1 -0
  58. package/dist/src/lib/function-schema-codegen.d.ts +17 -9
  59. package/dist/src/lib/function-schema-codegen.js +30 -29
  60. package/dist/src/lib/function-schema-codegen.js.map +1 -1
  61. package/dist/src/lib/function-sync.d.ts +226 -30
  62. package/dist/src/lib/function-sync.js +731 -77
  63. package/dist/src/lib/function-sync.js.map +1 -1
  64. package/dist/src/lib/function-versions.d.ts +122 -0
  65. package/dist/src/lib/function-versions.js +182 -0
  66. package/dist/src/lib/function-versions.js.map +1 -0
  67. package/dist/src/lib/generated-config-surfaces.d.ts +561 -91
  68. package/dist/src/lib/generated-config-surfaces.js +1858 -287
  69. package/dist/src/lib/generated-config-surfaces.js.map +1 -1
  70. package/dist/src/lib/generated-sdk-types.d.ts +1 -1
  71. package/dist/src/lib/generated-sdk-types.js +1 -1
  72. package/dist/src/lib/generated-sdk-types.js.map +1 -1
  73. package/dist/src/lib/log-inspection.d.ts +2 -0
  74. package/dist/src/lib/log-inspection.js +4 -0
  75. package/dist/src/lib/log-inspection.js.map +1 -1
  76. package/dist/src/lib/output.d.ts +15 -0
  77. package/dist/src/lib/output.js +28 -0
  78. package/dist/src/lib/output.js.map +1 -1
  79. package/dist/src/lib/prompt-schema-codegen.d.ts +94 -0
  80. package/dist/src/lib/prompt-schema-codegen.js +212 -0
  81. package/dist/src/lib/prompt-schema-codegen.js.map +1 -0
  82. package/dist/src/lib/snapshot-audit-source.d.ts +45 -0
  83. package/dist/src/lib/snapshot-audit-source.js +58 -0
  84. package/dist/src/lib/snapshot-audit-source.js.map +1 -0
  85. package/dist/src/lib/snapshot-audit-store.d.ts +52 -0
  86. package/dist/src/lib/snapshot-audit-store.js +196 -0
  87. package/dist/src/lib/snapshot-audit-store.js.map +1 -0
  88. package/dist/src/lib/snapshot-audit.d.ts +207 -0
  89. package/dist/src/lib/snapshot-audit.js +431 -0
  90. package/dist/src/lib/snapshot-audit.js.map +1 -0
  91. package/dist/src/lib/snapshot-build-rows.d.ts +60 -0
  92. package/dist/src/lib/snapshot-build-rows.js +87 -0
  93. package/dist/src/lib/snapshot-build-rows.js.map +1 -0
  94. package/dist/src/lib/snapshot-manifest-layout.d.ts +61 -0
  95. package/dist/src/lib/snapshot-manifest-layout.js +70 -0
  96. package/dist/src/lib/snapshot-manifest-layout.js.map +1 -0
  97. package/dist/src/lib/step-run-table.d.ts +43 -0
  98. package/dist/src/lib/step-run-table.js +129 -0
  99. package/dist/src/lib/step-run-table.js.map +1 -0
  100. package/dist/src/lib/swift-codegen/functionGenerator.d.ts +26 -13
  101. package/dist/src/lib/swift-codegen/functionGenerator.js +38 -27
  102. package/dist/src/lib/swift-codegen/functionGenerator.js.map +1 -1
  103. package/dist/src/lib/sync-selectors.d.ts +45 -2
  104. package/dist/src/lib/sync-selectors.js +66 -5
  105. package/dist/src/lib/sync-selectors.js.map +1 -1
  106. package/dist/src/lib/workflow-toml-validator.d.ts +9 -5
  107. package/dist/src/lib/workflow-toml-validator.js +18 -10
  108. package/dist/src/lib/workflow-toml-validator.js.map +1 -1
  109. package/package.json +2 -2
@@ -753,6 +753,31 @@ export declare class ApiClient {
753
753
  items: any[];
754
754
  nextCursor?: string | null;
755
755
  }>;
756
+ /**
757
+ * A function run's step trace — #3348.
758
+ *
759
+ * The function-scoped twin of `getWorkflowStepRuns`, and it answers with the
760
+ * same rows in the same shape: `workflows/:idOrKey/runs/...` resolves its
761
+ * first argument to a workflow DEFINITION, which a function id and a function
762
+ * key alike miss, so a durable run's steps needed a route that addresses the
763
+ * run by the function that owns it.
764
+ */
765
+ getFunctionStepRuns(appId: string, functionId: string, runId: string): Promise<{
766
+ items: any[];
767
+ }>;
768
+ /**
769
+ * End a function run that will not settle — #3348.
770
+ *
771
+ * `terminated: false` means the run had already settled and the route
772
+ * reported its outcome instead of overwriting it.
773
+ */
774
+ terminateFunctionRun(appId: string, functionId: string, runId: string): Promise<{
775
+ runId: string;
776
+ status: string;
777
+ terminated: boolean;
778
+ endedAt?: string | null;
779
+ instanceTerminated?: boolean;
780
+ }>;
756
781
  /**
757
782
  * A function's invocation records, newest first — #3287.
758
783
  *
@@ -763,13 +788,102 @@ export declare class ApiClient {
763
788
  listFunctionLogs(appId: string, functionId: string, params?: {
764
789
  limit?: number;
765
790
  cursor?: string;
791
+ runId?: string;
766
792
  }): Promise<{
767
793
  items: any[];
768
794
  nextCursor?: string | null;
769
795
  }>;
770
- listFunctionConfigs(appId: string, functionId: string): Promise<{
796
+ /**
797
+ * One invocation record by its id — #3448, behind `functions logs
798
+ * --invocation <id>`.
799
+ *
800
+ * The id the invoke envelope answered with. A record that never existed,
801
+ * one of ANOTHER function, and one the seven-day TTL has expired all answer
802
+ * the same 404, which is what the listing's own filter promises.
803
+ */
804
+ getFunctionLog(appId: string, functionId: string, invocationId: string): Promise<any>;
805
+ /**
806
+ * One function run, RECONCILED against the engine — #3448, what
807
+ * `functions runs wait` polls.
808
+ *
809
+ * NOT the runs listing: that reports the STORED status, and a durable run's
810
+ * row stays `running` until something asks the engine, so a wait built on it
811
+ * would never settle.
812
+ */
813
+ getFunctionRun(appId: string, functionId: string, runId: string): Promise<{
814
+ status: any;
815
+ run: any;
816
+ slice?: any;
817
+ }>;
818
+ /**
819
+ * The operator's own APP user — #3448 D3448-009.
820
+ *
821
+ * `getMe()` reads the ADMIN profile (`/admin/api/me`); this is the
822
+ * app-scoped route, which answers the app user `requireAppPermission`
823
+ * provisions for an admin and the `appRole` it gave them. That is the
824
+ * identity a `functions invoke` runs under by default, and printing it is
825
+ * how an operator knows their call bypassed the function's `access` gate.
826
+ */
827
+ getAppProfile(appId: string): Promise<{
828
+ userId: string;
829
+ email: string | null;
830
+ name?: string | null;
831
+ appRole: string;
832
+ appId: string;
833
+ }>;
834
+ /**
835
+ * Invoke a function and hand back the ANSWER, whatever its HTTP status —
836
+ * #3448 D3448-010.
837
+ *
838
+ * `request` throws an `ApiError` on any non-2xx, which is right for every
839
+ * other verb and wrong for this one: a 429 carries a `Retry-After` an
840
+ * operator needs to see, and a 409 mode mismatch carries the version's real
841
+ * mode. Rendering those from an exception means re-deriving what the server
842
+ * already said. So this returns the envelope with its status and its retry
843
+ * hint, and throws only when the request did not complete at all.
844
+ *
845
+ * `bearer` is the `--user` path's minted token; absent, the CLI's own
846
+ * credentials. `mode` is ALWAYS sent (D3448-012): it is what makes the
847
+ * server, not the CLI, the authority on a wrong verb.
848
+ */
849
+ invokeFunction(appId: string, functionKey: string, body: Record<string, unknown>, options?: {
850
+ bearer?: string;
851
+ asSystem?: boolean;
852
+ /**
853
+ * Which runtime this call means — #3482. The ROUTE is the selector:
854
+ * `POST functions/{key}` runs the function inside the request and
855
+ * `POST functions/{key}/start` runs it as a task. The admin-only system
856
+ * door is ONE route for both, so `--as system` keeps naming its runtime
857
+ * in the body (F-3482-007); phase 7's rename may fold it.
858
+ */
859
+ runtime?: "request" | "task";
860
+ }): Promise<{
861
+ httpStatus: number;
862
+ retryAfterSeconds: number | null;
863
+ body: any;
864
+ }>;
865
+ /**
866
+ * A function's versions, newest first (#3462).
867
+ *
868
+ * Paging is opt-in on the server: with no `limit` and no `cursor` every
869
+ * version comes back, which is what this method did before paging existed and
870
+ * what an already-installed CLI still asks for. A caller that passes either
871
+ * gets the shared `{ items, hasMore, nextCursor }` page.
872
+ */
873
+ listFunctionConfigs(appId: string, functionId: string, options?: {
874
+ limit?: number;
875
+ cursor?: string;
876
+ }): Promise<{
771
877
  items: any[];
878
+ hasMore: boolean;
879
+ nextCursor?: string | null;
772
880
  }>;
881
+ /**
882
+ * Point the function at one of its OWN existing versions (#3462). Creates no
883
+ * version; the activated version's capabilities, limits and triggers apply
884
+ * from the next call.
885
+ */
886
+ activateFunctionConfig(appId: string, functionId: string, configId: string): Promise<any>;
773
887
  getFunctionConfig(appId: string, functionId: string, configId: string): Promise<any>;
774
888
  /**
775
889
  * The stored envelope: the authored TOML bytes, every source file and the
@@ -838,7 +952,11 @@ export declare class ApiClient {
838
952
  locks: any[];
839
953
  }>;
840
954
  getLockStatus(appId: string, key: string): Promise<any>;
841
- acquireLock(appId: string, key: string, ttlMs: number): Promise<any>;
955
+ /**
956
+ * #3562 — `owner` is sent only when one was given, so a call that names none
957
+ * composes byte for byte the body it always did.
958
+ */
959
+ acquireLock(appId: string, key: string, ttlMs: number, owner?: string): Promise<any>;
842
960
  releaseLock(appId: string, key: string, handleId: string): Promise<any>;
843
961
  listCronTriggers(appId: string): Promise<{
844
962
  items: any[];
@@ -1958,6 +2076,62 @@ export declare class ApiClient {
1958
2076
  * record/model/blob counts, approximate size, and last-modified timestamp.
1959
2077
  */
1960
2078
  getDocumentStats(appId: string, documentId: string): Promise<any>;
2079
+ /**
2080
+ * A large document's snapshot builds, newest first, one page (#3432).
2081
+ * `GET documents/:id/snapshots` → `{ items, hasMore, nextCursor? }`.
2082
+ */
2083
+ listDocumentSnapshots(appId: string, documentId: string, options?: {
2084
+ limit?: number;
2085
+ cursor?: string;
2086
+ }): Promise<{
2087
+ items: any[];
2088
+ hasMore: boolean;
2089
+ nextCursor?: string;
2090
+ }>;
2091
+ /**
2092
+ * A large document's bulk-load sessions, newest first (#3434, criterion 9).
2093
+ *
2094
+ * Read-only, through the document read helper's app-admin arm — the same
2095
+ * door the snapshot readers use. The action verb `documents ingest` is
2096
+ * #3435's; these two are the operator's window onto what a session did.
2097
+ */
2098
+ listDocumentIngests(appId: string, documentId: string, options?: {
2099
+ limit?: number;
2100
+ cursor?: string;
2101
+ }): Promise<{
2102
+ items: any[];
2103
+ hasMore: boolean;
2104
+ nextCursor?: string;
2105
+ }>;
2106
+ /** One bulk-load session's status (#3434). */
2107
+ getDocumentIngest(appId: string, documentId: string, sessionId: string): Promise<any>;
2108
+ /** Open a bulk-load session on a large document (#3435, criterion 9). */
2109
+ createDocumentIngest(appId: string, documentId: string): Promise<any>;
2110
+ /**
2111
+ * Upload one chunk of a bulk-load session's artifact (#3435).
2112
+ *
2113
+ * `multipart/form-data`: the eight descriptor fields as text parts and the
2114
+ * gzipped bytes as one file part named `chunk`. No `Content-Type` is set on
2115
+ * the request — fetch supplies the boundary, and a header written by hand
2116
+ * would name one the body does not use.
2117
+ */
2118
+ uploadDocumentIngestChunk(appId: string, documentId: string, sessionId: string, chunk: {
2119
+ model: string;
2120
+ index: number;
2121
+ rows: number;
2122
+ bytes: number;
2123
+ rawBytes: number;
2124
+ sha256: string;
2125
+ firstId: string;
2126
+ lastId: string;
2127
+ body: Buffer;
2128
+ }): Promise<any>;
2129
+ /** Freeze a session's manifest and hand it to the pipeline (#3435). */
2130
+ commitDocumentIngest(appId: string, documentId: string, sessionId: string): Promise<any>;
2131
+ /** Give a session up; allowed until the swap (#3435). */
2132
+ abortDocumentIngest(appId: string, documentId: string, sessionId: string): Promise<any>;
2133
+ /** One snapshot build with its verification result (#3432). */
2134
+ getDocumentSnapshot(appId: string, documentId: string, buildId: string): Promise<any>;
1961
2135
  /**
1962
2136
  * Create or replace a record in a document model (issue #1964 Phase 4).
1963
2137
  * `POST documents/:id/records/:model` with `{ id, data, options? }` —
@@ -2069,6 +2243,21 @@ export declare class ApiClient {
2069
2243
  email?: string;
2070
2244
  };
2071
2245
  }>;
2246
+ /**
2247
+ * The admin inventory of one user's documents, followed to the end (#3399).
2248
+ *
2249
+ * The endpoint speaks the standard list contract (`{ items, hasMore,
2250
+ * nextCursor }`) and caps a page, so one request reads one page: reading it
2251
+ * would truncate a heavy user's inventory at the page size, which is the
2252
+ * bug this issue is about with a smaller number on it. `documents list
2253
+ * --user-id` and `documents export-all` both read this, and both mean "all
2254
+ * of them". `paginateAll` carries the repeat-cursor and max-page guards, so
2255
+ * a server handing out a non-advancing cursor fails loudly instead of
2256
+ * looping forever.
2257
+ *
2258
+ * Tolerant of a pre-contract server, which answers with a bare
2259
+ * `{ documents }` array and no cursor: that reads as a single page.
2260
+ */
2072
2261
  listAdminDocuments(appId: string, userId: string): Promise<any[]>;
2073
2262
  /**
2074
2263
  * The document `AppUser.rootDocId` points at, or null when the user has none
@@ -4,6 +4,13 @@ import { fetchWithTLS } from "./fetch.js";
4
4
  import { paginateAll, normalizeCliListEnvelope } from "./paginate.js";
5
5
  import { RefreshError, refreshAdminCredentials, } from "./refresh-admin-credentials.js";
6
6
  import { drainAppsUntilConverged, } from "./workflow-usage.js";
7
+ /**
8
+ * Page size the admin per-user inventories are asked for (#3399).
9
+ *
10
+ * `MAX_LIST_LIMIT` on the server, so a heavy user's inventory is walked in the
11
+ * fewest round trips the contract allows; a larger value is clamped there.
12
+ */
13
+ const ADMIN_INVENTORY_PAGE_SIZE = 100;
7
14
  export class ApiError extends Error {
8
15
  statusCode;
9
16
  code;
@@ -780,6 +787,28 @@ export class ApiClient {
780
787
  const result = await this.get(`/admin/api/apps/${appId}/functions/${functionId}/runs`, params);
781
788
  return { items: result?.items ?? [], nextCursor: result?.nextCursor ?? null };
782
789
  }
790
+ /**
791
+ * A function run's step trace — #3348.
792
+ *
793
+ * The function-scoped twin of `getWorkflowStepRuns`, and it answers with the
794
+ * same rows in the same shape: `workflows/:idOrKey/runs/...` resolves its
795
+ * first argument to a workflow DEFINITION, which a function id and a function
796
+ * key alike miss, so a durable run's steps needed a route that addresses the
797
+ * run by the function that owns it.
798
+ */
799
+ async getFunctionStepRuns(appId, functionId, runId) {
800
+ const result = await this.get(`/admin/api/apps/${appId}/functions/${functionId}/runs/${runId}/steps`);
801
+ return { items: result?.items ?? [] };
802
+ }
803
+ /**
804
+ * End a function run that will not settle — #3348.
805
+ *
806
+ * `terminated: false` means the run had already settled and the route
807
+ * reported its outcome instead of overwriting it.
808
+ */
809
+ async terminateFunctionRun(appId, functionId, runId) {
810
+ return this.post(`/admin/api/apps/${appId}/functions/${functionId}/runs/${runId}/terminate`, {});
811
+ }
783
812
  /**
784
813
  * A function's invocation records, newest first — #3287.
785
814
  *
@@ -791,9 +820,125 @@ export class ApiClient {
791
820
  const result = await this.get(`/admin/api/apps/${appId}/functions/${functionId}/logs`, params);
792
821
  return { items: result?.items ?? [], nextCursor: result?.nextCursor ?? null };
793
822
  }
794
- async listFunctionConfigs(appId, functionId) {
795
- const result = await this.get(`/admin/api/apps/${appId}/functions/${functionId}/configs`);
796
- return { items: result?.items ?? [] };
823
+ /**
824
+ * One invocation record by its id — #3448, behind `functions logs
825
+ * --invocation <id>`.
826
+ *
827
+ * The id the invoke envelope answered with. A record that never existed,
828
+ * one of ANOTHER function, and one the seven-day TTL has expired all answer
829
+ * the same 404, which is what the listing's own filter promises.
830
+ */
831
+ async getFunctionLog(appId, functionId, invocationId) {
832
+ return this.get(`/admin/api/apps/${appId}/functions/${functionId}/logs/${invocationId}`);
833
+ }
834
+ /**
835
+ * One function run, RECONCILED against the engine — #3448, what
836
+ * `functions runs wait` polls.
837
+ *
838
+ * NOT the runs listing: that reports the STORED status, and a durable run's
839
+ * row stays `running` until something asks the engine, so a wait built on it
840
+ * would never settle.
841
+ */
842
+ async getFunctionRun(appId, functionId, runId) {
843
+ return this.get(`/admin/api/apps/${appId}/functions/${functionId}/runs/${runId}`);
844
+ }
845
+ /**
846
+ * The operator's own APP user — #3448 D3448-009.
847
+ *
848
+ * `getMe()` reads the ADMIN profile (`/admin/api/me`); this is the
849
+ * app-scoped route, which answers the app user `requireAppPermission`
850
+ * provisions for an admin and the `appRole` it gave them. That is the
851
+ * identity a `functions invoke` runs under by default, and printing it is
852
+ * how an operator knows their call bypassed the function's `access` gate.
853
+ */
854
+ async getAppProfile(appId) {
855
+ return this.get(`/app/${appId}/api/me`);
856
+ }
857
+ /**
858
+ * Invoke a function and hand back the ANSWER, whatever its HTTP status —
859
+ * #3448 D3448-010.
860
+ *
861
+ * `request` throws an `ApiError` on any non-2xx, which is right for every
862
+ * other verb and wrong for this one: a 429 carries a `Retry-After` an
863
+ * operator needs to see, and a 409 mode mismatch carries the version's real
864
+ * mode. Rendering those from an exception means re-deriving what the server
865
+ * already said. So this returns the envelope with its status and its retry
866
+ * hint, and throws only when the request did not complete at all.
867
+ *
868
+ * `bearer` is the `--user` path's minted token; absent, the CLI's own
869
+ * credentials. `mode` is ALWAYS sent (D3448-012): it is what makes the
870
+ * server, not the CLI, the authority on a wrong verb.
871
+ */
872
+ async invokeFunction(appId, functionKey, body, options) {
873
+ const credentials = await this.ensureAuthenticated();
874
+ const base = `/app/${appId}/api/functions/${encodeURIComponent(functionKey)}`;
875
+ const path = options?.asSystem
876
+ ? `${base}/invoke-as-system`
877
+ : options?.runtime === "task"
878
+ ? `${base}/start`
879
+ : base;
880
+ const headers = {
881
+ Authorization: `Bearer ${options?.bearer ?? credentials.accessToken}`,
882
+ "Content-Type": "application/json",
883
+ };
884
+ // The CLI's own credentials reach an app-scoped route through the
885
+ // global-admin header; a minted user token does not need it and must not
886
+ // carry it.
887
+ if (!options?.bearer && credentials.globalAdminAppId) {
888
+ headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
889
+ }
890
+ const response = await fetchWithTLS(`${credentials.serverUrl}${path}`, {
891
+ method: "POST",
892
+ headers,
893
+ body: JSON.stringify(body),
894
+ });
895
+ const text = await response.text();
896
+ let parsed = null;
897
+ try {
898
+ parsed = text ? JSON.parse(text) : null;
899
+ }
900
+ catch {
901
+ // A body that is not JSON is still an answer; keep it readable.
902
+ parsed = { error: text };
903
+ }
904
+ const retryAfter = response.headers.get("retry-after");
905
+ const retryAfterSeconds = retryAfter && Number.isFinite(Number(retryAfter)) ? Number(retryAfter) : null;
906
+ return {
907
+ httpStatus: response.status,
908
+ retryAfterSeconds: retryAfterSeconds ??
909
+ (typeof parsed?.retryAfter === "number" ? parsed.retryAfter : null),
910
+ body: parsed,
911
+ };
912
+ }
913
+ /**
914
+ * A function's versions, newest first (#3462).
915
+ *
916
+ * Paging is opt-in on the server: with no `limit` and no `cursor` every
917
+ * version comes back, which is what this method did before paging existed and
918
+ * what an already-installed CLI still asks for. A caller that passes either
919
+ * gets the shared `{ items, hasMore, nextCursor }` page.
920
+ */
921
+ async listFunctionConfigs(appId, functionId, options = {}) {
922
+ const query = new URLSearchParams();
923
+ if (options.limit !== undefined)
924
+ query.set("limit", String(options.limit));
925
+ if (options.cursor)
926
+ query.set("cursor", options.cursor);
927
+ const suffix = query.toString() ? `?${query.toString()}` : "";
928
+ const result = await this.get(`/admin/api/apps/${appId}/functions/${functionId}/configs${suffix}`);
929
+ return {
930
+ items: result?.items ?? [],
931
+ hasMore: result?.hasMore === true,
932
+ nextCursor: result?.nextCursor ?? null,
933
+ };
934
+ }
935
+ /**
936
+ * Point the function at one of its OWN existing versions (#3462). Creates no
937
+ * version; the activated version's capabilities, limits and triggers apply
938
+ * from the next call.
939
+ */
940
+ async activateFunctionConfig(appId, functionId, configId) {
941
+ return this.post(`/admin/api/apps/${appId}/functions/${functionId}/configs/${configId}/activate`, {});
797
942
  }
798
943
  async getFunctionConfig(appId, functionId, configId) {
799
944
  return this.get(`/admin/api/apps/${appId}/functions/${functionId}/configs/${configId}`);
@@ -885,8 +1030,16 @@ export class ApiClient {
885
1030
  async getLockStatus(appId, key) {
886
1031
  return this.post(`/app/${appId}/api/locks/status`, { key });
887
1032
  }
888
- async acquireLock(appId, key, ttlMs) {
889
- return this.post(`/app/${appId}/api/locks/acquire`, { key, ttlMs });
1033
+ /**
1034
+ * #3562 — `owner` is sent only when one was given, so a call that names none
1035
+ * composes byte for byte the body it always did.
1036
+ */
1037
+ async acquireLock(appId, key, ttlMs, owner) {
1038
+ return this.post(`/app/${appId}/api/locks/acquire`, {
1039
+ key,
1040
+ ttlMs,
1041
+ ...(owner !== undefined ? { owner } : {}),
1042
+ });
890
1043
  }
891
1044
  async releaseLock(appId, key, handleId) {
892
1045
  return this.post(`/app/${appId}/api/locks/release`, {
@@ -2431,6 +2584,101 @@ export class ApiClient {
2431
2584
  async getDocumentStats(appId, documentId) {
2432
2585
  return this.get(`/app/${appId}/api/documents/${documentId}/stats`);
2433
2586
  }
2587
+ /**
2588
+ * A large document's snapshot builds, newest first, one page (#3432).
2589
+ * `GET documents/:id/snapshots` → `{ items, hasMore, nextCursor? }`.
2590
+ */
2591
+ async listDocumentSnapshots(appId, documentId, options = {}) {
2592
+ const params = new URLSearchParams();
2593
+ if (options.limit !== undefined)
2594
+ params.set("limit", String(options.limit));
2595
+ if (options.cursor)
2596
+ params.set("cursor", options.cursor);
2597
+ const query = params.toString();
2598
+ return this.get(`/app/${appId}/api/documents/${documentId}/snapshots${query ? `?${query}` : ""}`);
2599
+ }
2600
+ /**
2601
+ * A large document's bulk-load sessions, newest first (#3434, criterion 9).
2602
+ *
2603
+ * Read-only, through the document read helper's app-admin arm — the same
2604
+ * door the snapshot readers use. The action verb `documents ingest` is
2605
+ * #3435's; these two are the operator's window onto what a session did.
2606
+ */
2607
+ async listDocumentIngests(appId, documentId, options = {}) {
2608
+ const params = new URLSearchParams();
2609
+ if (options.limit !== undefined)
2610
+ params.set("limit", String(options.limit));
2611
+ if (options.cursor)
2612
+ params.set("cursor", options.cursor);
2613
+ const query = params.toString();
2614
+ return this.get(`/app/${appId}/api/documents/${documentId}/ingest${query ? `?${query}` : ""}`);
2615
+ }
2616
+ /** One bulk-load session's status (#3434). */
2617
+ async getDocumentIngest(appId, documentId, sessionId) {
2618
+ return this.get(`/app/${appId}/api/documents/${documentId}/ingest/${encodeURIComponent(sessionId)}`);
2619
+ }
2620
+ /** Open a bulk-load session on a large document (#3435, criterion 9). */
2621
+ async createDocumentIngest(appId, documentId) {
2622
+ return this.post(`/app/${appId}/api/documents/${documentId}/ingest`, {});
2623
+ }
2624
+ /**
2625
+ * Upload one chunk of a bulk-load session's artifact (#3435).
2626
+ *
2627
+ * `multipart/form-data`: the eight descriptor fields as text parts and the
2628
+ * gzipped bytes as one file part named `chunk`. No `Content-Type` is set on
2629
+ * the request — fetch supplies the boundary, and a header written by hand
2630
+ * would name one the body does not use.
2631
+ */
2632
+ async uploadDocumentIngestChunk(appId, documentId, sessionId, chunk) {
2633
+ const credentials = await this.ensureAuthenticated();
2634
+ const form = new FormData();
2635
+ form.set("model", chunk.model);
2636
+ form.set("index", String(chunk.index));
2637
+ form.set("rows", String(chunk.rows));
2638
+ form.set("bytes", String(chunk.bytes));
2639
+ form.set("rawBytes", String(chunk.rawBytes));
2640
+ form.set("sha256", chunk.sha256);
2641
+ form.set("firstId", chunk.firstId);
2642
+ form.set("lastId", chunk.lastId);
2643
+ form.set("chunk", new Blob([chunk.body]), `${chunk.index}.ndjson.gz`);
2644
+ const headers = {
2645
+ Authorization: `Bearer ${credentials.accessToken}`,
2646
+ };
2647
+ if (credentials.globalAdminAppId) {
2648
+ headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
2649
+ }
2650
+ const url = `${credentials.serverUrl}/app/${appId}/api/documents/${documentId}` +
2651
+ `/ingest/${encodeURIComponent(sessionId)}/chunks`;
2652
+ const response = await fetchWithTLS(url, {
2653
+ method: "POST",
2654
+ headers,
2655
+ body: form,
2656
+ });
2657
+ const text = await response.text();
2658
+ let result = {};
2659
+ try {
2660
+ result = text ? JSON.parse(text) : {};
2661
+ }
2662
+ catch {
2663
+ result = { error: text };
2664
+ }
2665
+ if (!response.ok) {
2666
+ throw new ApiError(result?.error || `Failed to upload chunk: ${response.statusText}`, response.status, result?.code, result?.details);
2667
+ }
2668
+ return result;
2669
+ }
2670
+ /** Freeze a session's manifest and hand it to the pipeline (#3435). */
2671
+ async commitDocumentIngest(appId, documentId, sessionId) {
2672
+ return this.post(`/app/${appId}/api/documents/${documentId}/ingest/${encodeURIComponent(sessionId)}/commit`, {});
2673
+ }
2674
+ /** Give a session up; allowed until the swap (#3435). */
2675
+ async abortDocumentIngest(appId, documentId, sessionId) {
2676
+ return this.post(`/app/${appId}/api/documents/${documentId}/ingest/${encodeURIComponent(sessionId)}/abort`, {});
2677
+ }
2678
+ /** One snapshot build with its verification result (#3432). */
2679
+ async getDocumentSnapshot(appId, documentId, buildId) {
2680
+ return this.get(`/app/${appId}/api/documents/${documentId}/snapshots/${encodeURIComponent(buildId)}`);
2681
+ }
2434
2682
  /**
2435
2683
  * Create or replace a record in a document model (issue #1964 Phase 4).
2436
2684
  * `POST documents/:id/records/:model` with `{ id, data, options? }` —
@@ -2575,9 +2823,37 @@ export class ApiClient {
2575
2823
  async lookupUserByEmail(appId, email) {
2576
2824
  return this.get(`/app/${appId}/api/users/lookup`, { email });
2577
2825
  }
2826
+ /**
2827
+ * The admin inventory of one user's documents, followed to the end (#3399).
2828
+ *
2829
+ * The endpoint speaks the standard list contract (`{ items, hasMore,
2830
+ * nextCursor }`) and caps a page, so one request reads one page: reading it
2831
+ * would truncate a heavy user's inventory at the page size, which is the
2832
+ * bug this issue is about with a smaller number on it. `documents list
2833
+ * --user-id` and `documents export-all` both read this, and both mean "all
2834
+ * of them". `paginateAll` carries the repeat-cursor and max-page guards, so
2835
+ * a server handing out a non-advancing cursor fails loudly instead of
2836
+ * looping forever.
2837
+ *
2838
+ * Tolerant of a pre-contract server, which answers with a bare
2839
+ * `{ documents }` array and no cursor: that reads as a single page.
2840
+ */
2578
2841
  async listAdminDocuments(appId, userId) {
2579
- const result = await this.get(`/admin/api/apps/${appId}/documents`, { userId });
2580
- return result?.documents || result || [];
2842
+ return paginateAll(async (cursor) => {
2843
+ const params = new URLSearchParams({
2844
+ userId,
2845
+ limit: String(ADMIN_INVENTORY_PAGE_SIZE),
2846
+ });
2847
+ if (cursor)
2848
+ params.set("cursor", cursor);
2849
+ const result = await this.get(`/admin/api/apps/${appId}/documents?${params.toString()}`);
2850
+ return {
2851
+ items: result?.items ?? result?.documents ?? [],
2852
+ // #1316: prefer `nextCursor`; the deprecated `cursor` alias keeps a
2853
+ // shipped CLI paginating against a server that still dual-emits.
2854
+ nextCursor: result?.nextCursor ?? result?.cursor,
2855
+ };
2856
+ });
2581
2857
  }
2582
2858
  /**
2583
2859
  * The document `AppUser.rootDocId` points at, or null when the user has none