primitive-admin 1.2.0-alpha.7 → 1.2.0-alpha.8

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 (65) hide show
  1. package/README.md +8 -2
  2. package/assets/skill/skills/primitive-platform/SKILL.md +12 -6
  3. package/dist/src/commands/apps-children.js +2 -0
  4. package/dist/src/commands/apps-children.js.map +1 -1
  5. package/dist/src/commands/auth.js +107 -33
  6. package/dist/src/commands/auth.js.map +1 -1
  7. package/dist/src/commands/databases.js +13 -1
  8. package/dist/src/commands/databases.js.map +1 -1
  9. package/dist/src/commands/documents.js +70 -11
  10. package/dist/src/commands/documents.js.map +1 -1
  11. package/dist/src/commands/sync-app-settings.d.ts +12 -2
  12. package/dist/src/commands/sync-app-settings.js +35 -10
  13. package/dist/src/commands/sync-app-settings.js.map +1 -1
  14. package/dist/src/commands/sync.js +44 -10
  15. package/dist/src/commands/sync.js.map +1 -1
  16. package/dist/src/lib/api-client.d.ts +25 -6
  17. package/dist/src/lib/api-client.js +106 -72
  18. package/dist/src/lib/api-client.js.map +1 -1
  19. package/dist/src/lib/app-settings-descriptor.d.ts +38 -0
  20. package/dist/src/lib/app-settings-descriptor.js +96 -4
  21. package/dist/src/lib/app-settings-descriptor.js.map +1 -1
  22. package/dist/src/lib/credentials-home.d.ts +51 -0
  23. package/dist/src/lib/credentials-home.js +169 -0
  24. package/dist/src/lib/credentials-home.js.map +1 -0
  25. package/dist/src/lib/credentials-lock.d.ts +80 -0
  26. package/dist/src/lib/credentials-lock.js +222 -0
  27. package/dist/src/lib/credentials-lock.js.map +1 -0
  28. package/dist/src/lib/credentials-store.d.ts +56 -4
  29. package/dist/src/lib/credentials-store.js +236 -78
  30. package/dist/src/lib/credentials-store.js.map +1 -1
  31. package/dist/src/lib/env-resolver-core.d.ts +1 -1
  32. package/dist/src/lib/env-resolver-core.js.map +1 -1
  33. package/dist/src/lib/function-bundle.d.ts +38 -18
  34. package/dist/src/lib/function-bundle.js +60 -35
  35. package/dist/src/lib/function-bundle.js.map +1 -1
  36. package/dist/src/lib/function-document-types.d.ts +5 -3
  37. package/dist/src/lib/function-document-types.js +10 -14
  38. package/dist/src/lib/function-document-types.js.map +1 -1
  39. package/dist/src/lib/function-source-roots.d.ts +67 -0
  40. package/dist/src/lib/function-source-roots.js +148 -0
  41. package/dist/src/lib/function-source-roots.js.map +1 -0
  42. package/dist/src/lib/function-sync.d.ts +27 -0
  43. package/dist/src/lib/function-sync.js +16 -3
  44. package/dist/src/lib/function-sync.js.map +1 -1
  45. package/dist/src/lib/function-typecheck.d.ts +37 -2
  46. package/dist/src/lib/function-typecheck.js +210 -28
  47. package/dist/src/lib/function-typecheck.js.map +1 -1
  48. package/dist/src/lib/generated-config-surfaces.js +21 -10
  49. package/dist/src/lib/generated-config-surfaces.js.map +1 -1
  50. package/dist/src/lib/generated-sdk-types.d.ts +1 -1
  51. package/dist/src/lib/generated-sdk-types.js +1 -1
  52. package/dist/src/lib/generated-sdk-types.js.map +1 -1
  53. package/dist/src/lib/login-scope-lines.d.ts +1 -1
  54. package/dist/src/lib/login-scope-lines.js +1 -1
  55. package/dist/src/lib/login-scope-lines.js.map +1 -1
  56. package/dist/src/lib/project-config.d.ts +7 -0
  57. package/dist/src/lib/project-config.js +22 -1
  58. package/dist/src/lib/project-config.js.map +1 -1
  59. package/dist/src/lib/root-import-decision.d.ts +9 -4
  60. package/dist/src/lib/root-import-decision.js +24 -3
  61. package/dist/src/lib/root-import-decision.js.map +1 -1
  62. package/dist/src/lib/scope-request.d.ts +8 -5
  63. package/dist/src/lib/scope-request.js +17 -8
  64. package/dist/src/lib/scope-request.js.map +1 -1
  65. package/package.json +2 -2
@@ -51,6 +51,8 @@ export interface AppChildSummary {
51
51
  expiresAt: string | null;
52
52
  /** Exact origins the child's CORS and email-redirect checks accept beside its authored lists. */
53
53
  previewOrigins?: string[];
54
+ /** The child's protection level: always `internal` (#4564); absent from an older server. */
55
+ protection?: string;
54
56
  }
55
57
  /** A child's preview origins after a write to them. */
56
58
  export interface ChildPreviewOrigins {
@@ -499,9 +501,18 @@ export declare class ApiClient {
499
501
  request<T = any>(path: string, options?: RequestInit): Promise<T>;
500
502
  /**
501
503
  * Whether a 403 should be retried with the stored step-up: it names
502
- * `admin` among the missing scopes, and an unexpired step-up is stored.
504
+ * `admin` among the missing scopes, or it is a protected app's refusal of
505
+ * a token that is no step-up, and an unexpired step-up is stored.
503
506
  */
504
507
  private stepUpApplies;
508
+ /**
509
+ * #4564 — a request outside `request` (an upload, a function invocation)
510
+ * sent with the CLI's own credentials, retried once with the stored step-up
511
+ * exactly as `request` retries. `init` builds the request for a bearer. The
512
+ * answer is the retry's, unless the retry was refused 401: the step-up is
513
+ * dropped and the original refusal comes back, still readable.
514
+ */
515
+ private fetchWithStepUp;
505
516
  /** Forget a step-up the server no longer accepts. */
506
517
  private dropStepUp;
507
518
  /** The typed error a failed response throws. */
@@ -2104,10 +2115,14 @@ export declare class ApiClient {
2104
2115
  * through the caller's document permission (reader+) or the app-admin arm.
2105
2116
  * Returns the unified `{ items, hasMore, nextCursor? }` envelope. The cursor
2106
2117
  * is an opaque token — feed `nextCursor` back as `cursor` for the next page.
2118
+ * `limit` is 100 records when omitted and at most 10000; it is forwarded
2119
+ * unparsed (a string from the command line) so the server validates it.
2107
2120
  */
2108
2121
  queryDocumentRecords(appId: string, documentId: string, modelName: string, queryOptions?: {
2109
2122
  filter?: Record<string, any>;
2110
- limit?: number;
2123
+ /** `{ field: 1 | -1 }`, key order as precedence; the route validates it. */
2124
+ sort?: unknown;
2125
+ limit?: number | string;
2111
2126
  cursor?: string;
2112
2127
  /** #3764 — the format the caller believes this document has. */
2113
2128
  documentFormat?: 1 | 3;
@@ -2127,16 +2142,20 @@ export declare class ApiClient {
2127
2142
  * with `{ options: { operations, groupBy?, filter? } }`, answering `{ result }`
2128
2143
  * in the same shape as the database endpoint.
2129
2144
  *
2130
- * `groupBy` takes plain field names only: StringSet facet grouping and
2131
- * `{ field, contains }` membership grouping are database-only, which is why
2132
- * this signature is narrower than the databases one.
2145
+ * `groupBy` takes plain field names and period levels `{ field, period }`
2146
+ * (keyed `<field>_<period>` in a row's `group`): StringSet facet grouping
2147
+ * and `{ field, contains }` membership grouping are database-only, which is
2148
+ * why this signature is narrower than the databases one.
2133
2149
  */
2134
2150
  aggregateDocumentRecords(appId: string, documentId: string, modelName: string, options: {
2135
2151
  operations: {
2136
2152
  type: string;
2137
2153
  field?: string;
2138
2154
  }[];
2139
- groupBy?: string[];
2155
+ groupBy?: Array<string | {
2156
+ field: string;
2157
+ period: "month" | "quarter" | "year";
2158
+ }>;
2140
2159
  filter?: Record<string, any>;
2141
2160
  }, extra?: {
2142
2161
  documentFormat?: 1 | 3;
@@ -243,9 +243,10 @@ export class ApiClient {
243
243
  };
244
244
  let { response, text } = await send(credentials.accessToken);
245
245
  // #3889 — a 403 naming `admin` is retried once with a stored, unexpired
246
- // step-up. A retry refused 401 means the step-up is gone (revoked or
247
- // expired early): drop it and show the original refusal. The retry's own
248
- // refusal (say, an unpinned step-up on an app route) is shown as it is.
246
+ // step-up, and (#4564) so is a write a protected app refused for want of
247
+ // one. A retry refused 401 means the step-up is gone (revoked or expired
248
+ // early): drop it and show the original refusal. The retry's own refusal
249
+ // (say, a step-up without the scope this command needs) is shown as it is.
249
250
  if (response.status === 403 && this.stepUpApplies(response, text, path)) {
250
251
  const retried = await send(credentials.stepUp.accessToken);
251
252
  if (retried.response.ok) {
@@ -265,7 +266,8 @@ export class ApiClient {
265
266
  }
266
267
  /**
267
268
  * Whether a 403 should be retried with the stored step-up: it names
268
- * `admin` among the missing scopes, and an unexpired step-up is stored.
269
+ * `admin` among the missing scopes, or it is a protected app's refusal of
270
+ * a token that is no step-up, and an unexpired step-up is stored.
269
271
  */
270
272
  stepUpApplies(response, text, path) {
271
273
  const stepUp = this.credentials?.stepUp;
@@ -273,10 +275,41 @@ export class ApiClient {
273
275
  return false;
274
276
  if (new Date(stepUp.expiresAt).getTime() <= Date.now())
275
277
  return false;
276
- const details = parseErrorResponse(response, text, path).details;
278
+ const parsed = parseErrorResponse(response, text, path);
279
+ if (parsed.code === "PROTECTED_APP_STEP_UP_REQUIRED")
280
+ return true;
281
+ const details = parsed.details;
277
282
  const missing = details && !Array.isArray(details) ? details.missing : undefined;
278
283
  return Array.isArray(missing) && missing.includes("admin");
279
284
  }
285
+ /**
286
+ * #4564 — a request outside `request` (an upload, a function invocation)
287
+ * sent with the CLI's own credentials, retried once with the stored step-up
288
+ * exactly as `request` retries. `init` builds the request for a bearer. The
289
+ * answer is the retry's, unless the retry was refused 401: the step-up is
290
+ * dropped and the original refusal comes back, still readable.
291
+ */
292
+ async fetchWithStepUp(url, path, accessToken, init) {
293
+ const response = await fetchWithTLS(url, init(accessToken));
294
+ if (response.status !== 403)
295
+ return response;
296
+ // Read once to decide; hand the caller a response it can still read.
297
+ const text = await response.text();
298
+ const original = new Response(text, {
299
+ status: response.status,
300
+ statusText: response.statusText,
301
+ headers: response.headers,
302
+ });
303
+ if (!this.stepUpApplies(response, text, path))
304
+ return original;
305
+ const retried = await fetchWithTLS(url, init(this.credentials.stepUp.accessToken));
306
+ if (retried.status === 401) {
307
+ await retried.text().catch(() => "");
308
+ this.dropStepUp();
309
+ return original;
310
+ }
311
+ return retried;
312
+ }
280
313
  /** Forget a step-up the server no longer accepts. */
281
314
  dropStepUp() {
282
315
  if (!this.credentials?.stepUp)
@@ -944,21 +977,25 @@ export class ApiClient {
944
977
  : options?.runtime === "task"
945
978
  ? `${base}/start`
946
979
  : base;
947
- const headers = {
948
- Authorization: `Bearer ${options?.bearer ?? credentials.accessToken}`,
949
- "Content-Type": "application/json",
980
+ const init = (bearer) => {
981
+ const headers = {
982
+ Authorization: `Bearer ${bearer}`,
983
+ "Content-Type": "application/json",
984
+ };
985
+ // The CLI's own credentials reach an app-scoped route through the
986
+ // global-admin header; a minted user token does not need it and must
987
+ // not carry it.
988
+ if (!options?.bearer && credentials.globalAdminAppId) {
989
+ headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
990
+ }
991
+ return { method: "POST", headers, body: JSON.stringify(body) };
950
992
  };
951
- // The CLI's own credentials reach an app-scoped route through the
952
- // global-admin header; a minted user token does not need it and must not
953
- // carry it.
954
- if (!options?.bearer && credentials.globalAdminAppId) {
955
- headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
956
- }
957
- const response = await fetchWithTLS(`${credentials.serverUrl}${path}`, {
958
- method: "POST",
959
- headers,
960
- body: JSON.stringify(body),
961
- });
993
+ const url = `${credentials.serverUrl}${path}`;
994
+ // A minted user token is sent as it is; the CLI's own credentials get the
995
+ // step-up retry a protected app's refusal asks for (#4564).
996
+ const response = options?.bearer
997
+ ? await fetchWithTLS(url, init(options.bearer))
998
+ : await this.fetchWithStepUp(url, path, credentials.accessToken, init);
962
999
  const text = await response.text();
963
1000
  let parsed = null;
964
1001
  try {
@@ -1495,17 +1532,15 @@ export class ApiClient {
1495
1532
  const credentials = await this.ensureAuthenticated();
1496
1533
  const encodedFilename = encodeURIComponent(filename);
1497
1534
  const url = `${credentials.serverUrl}/admin/api/apps/${appId}/blocks/${blockType}/${blockId}/test-cases/${testCaseId}/attachments/${encodedFilename}`;
1498
- const headers = {
1499
- Authorization: `Bearer ${credentials.accessToken}`,
1500
- "Content-Type": contentType,
1501
- };
1502
- if (credentials.globalAdminAppId) {
1503
- headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
1504
- }
1505
- const response = await fetchWithTLS(url, {
1506
- method: "PUT",
1507
- headers,
1508
- body: data,
1535
+ const response = await this.fetchWithStepUp(url, url, credentials.accessToken, (bearer) => {
1536
+ const headers = {
1537
+ Authorization: `Bearer ${bearer}`,
1538
+ "Content-Type": contentType,
1539
+ };
1540
+ if (credentials.globalAdminAppId) {
1541
+ headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
1542
+ }
1543
+ return { method: "PUT", headers, body: data };
1509
1544
  });
1510
1545
  const text = await response.text();
1511
1546
  if (!response.ok) {
@@ -2460,11 +2495,15 @@ export class ApiClient {
2460
2495
  * through the caller's document permission (reader+) or the app-admin arm.
2461
2496
  * Returns the unified `{ items, hasMore, nextCursor? }` envelope. The cursor
2462
2497
  * is an opaque token — feed `nextCursor` back as `cursor` for the next page.
2498
+ * `limit` is 100 records when omitted and at most 10000; it is forwarded
2499
+ * unparsed (a string from the command line) so the server validates it.
2463
2500
  */
2464
2501
  async queryDocumentRecords(appId, documentId, modelName, queryOptions) {
2465
2502
  const query = {};
2466
2503
  if (queryOptions?.filter)
2467
2504
  query.filter = JSON.stringify(queryOptions.filter);
2505
+ if (queryOptions?.sort !== undefined)
2506
+ query.sort = JSON.stringify(queryOptions.sort);
2468
2507
  if (queryOptions?.documentFormat !== undefined) {
2469
2508
  query.documentFormat = queryOptions.documentFormat;
2470
2509
  }
@@ -2496,9 +2535,10 @@ export class ApiClient {
2496
2535
  * with `{ options: { operations, groupBy?, filter? } }`, answering `{ result }`
2497
2536
  * in the same shape as the database endpoint.
2498
2537
  *
2499
- * `groupBy` takes plain field names only: StringSet facet grouping and
2500
- * `{ field, contains }` membership grouping are database-only, which is why
2501
- * this signature is narrower than the databases one.
2538
+ * `groupBy` takes plain field names and period levels `{ field, period }`
2539
+ * (keyed `<field>_<period>` in a row's `group`): StringSet facet grouping
2540
+ * and `{ field, contains }` membership grouping are database-only, which is
2541
+ * why this signature is narrower than the databases one.
2502
2542
  */
2503
2543
  async aggregateDocumentRecords(appId, documentId, modelName, options, extra) {
2504
2544
  return this.post(`/app/${appId}/api/documents/${documentId}/records/${encodeURIComponent(modelName)}/aggregate` +
@@ -2589,18 +2629,16 @@ export class ApiClient {
2589
2629
  form.set("firstId", chunk.firstId);
2590
2630
  form.set("lastId", chunk.lastId);
2591
2631
  form.set("chunk", new Blob([chunk.body]), `${chunk.index}.ndjson.gz`);
2592
- const headers = {
2593
- Authorization: `Bearer ${credentials.accessToken}`,
2594
- };
2595
- if (credentials.globalAdminAppId) {
2596
- headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
2597
- }
2598
2632
  const url = `${credentials.serverUrl}/app/${appId}/api/documents/${documentId}` +
2599
2633
  `/ingest/${encodeURIComponent(sessionId)}/chunks`;
2600
- const response = await fetchWithTLS(url, {
2601
- method: "POST",
2602
- headers,
2603
- body: form,
2634
+ const response = await this.fetchWithStepUp(url, url, credentials.accessToken, (bearer) => {
2635
+ const headers = {
2636
+ Authorization: `Bearer ${bearer}`,
2637
+ };
2638
+ if (credentials.globalAdminAppId) {
2639
+ headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
2640
+ }
2641
+ return { method: "POST", headers, body: form };
2604
2642
  });
2605
2643
  const text = await response.text();
2606
2644
  let result = {};
@@ -2758,20 +2796,18 @@ export class ApiClient {
2758
2796
  async uploadBlob(appId, documentId, blobId, data, meta) {
2759
2797
  const credentials = await this.ensureAuthenticated();
2760
2798
  const url = `${credentials.serverUrl}/app/${appId}/api/documents/${documentId}/blobs/${blobId}`;
2761
- const headers = {
2762
- Authorization: `Bearer ${credentials.accessToken}`,
2763
- "Content-Type": meta.contentType,
2764
- "X-Blob-Filename": encodeURIComponent(meta.filename),
2765
- "X-Blob-Size": String(data.length),
2766
- "X-Blob-Sha256": meta.sha256,
2767
- };
2768
- if (credentials.globalAdminAppId) {
2769
- headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
2770
- }
2771
- const response = await fetchWithTLS(url, {
2772
- method: "PUT",
2773
- headers,
2774
- body: data,
2799
+ const response = await this.fetchWithStepUp(url, url, credentials.accessToken, (bearer) => {
2800
+ const headers = {
2801
+ Authorization: `Bearer ${bearer}`,
2802
+ "Content-Type": meta.contentType,
2803
+ "X-Blob-Filename": encodeURIComponent(meta.filename),
2804
+ "X-Blob-Size": String(data.length),
2805
+ "X-Blob-Sha256": meta.sha256,
2806
+ };
2807
+ if (credentials.globalAdminAppId) {
2808
+ headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
2809
+ }
2810
+ return { method: "PUT", headers, body: data };
2775
2811
  });
2776
2812
  if (!response.ok) {
2777
2813
  throw await blobFailure(response, "Failed to upload blob: ");
@@ -2939,21 +2975,19 @@ export class ApiClient {
2939
2975
  async uploadBucketBlob(appId, bucketIdOrKey, data, meta) {
2940
2976
  const credentials = await this.ensureAuthenticated();
2941
2977
  const url = `${credentials.serverUrl}/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}/blobs`;
2942
- const headers = {
2943
- Authorization: `Bearer ${credentials.accessToken}`,
2944
- "Content-Type": meta.contentType,
2945
- "X-Blob-Filename": encodeURIComponent(meta.filename),
2946
- };
2947
- if (meta.tags && meta.tags.length > 0) {
2948
- headers["X-Blob-Tags"] = JSON.stringify(meta.tags);
2949
- }
2950
- if (credentials.globalAdminAppId) {
2951
- headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
2952
- }
2953
- const response = await fetchWithTLS(url, {
2954
- method: "POST",
2955
- headers,
2956
- body: data,
2978
+ const response = await this.fetchWithStepUp(url, url, credentials.accessToken, (bearer) => {
2979
+ const headers = {
2980
+ Authorization: `Bearer ${bearer}`,
2981
+ "Content-Type": meta.contentType,
2982
+ "X-Blob-Filename": encodeURIComponent(meta.filename),
2983
+ };
2984
+ if (meta.tags && meta.tags.length > 0) {
2985
+ headers["X-Blob-Tags"] = JSON.stringify(meta.tags);
2986
+ }
2987
+ if (credentials.globalAdminAppId) {
2988
+ headers["X-Global-Admin-App-Id"] = credentials.globalAdminAppId;
2989
+ }
2990
+ return { method: "POST", headers, body: data };
2957
2991
  });
2958
2992
  if (!response.ok) {
2959
2993
  throw await blobFailure(response, "Failed to upload blob: ");