@foir/sdk 0.13.0 → 0.15.0

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/CHANGELOG.md CHANGED
@@ -108,6 +108,51 @@ nobody asking for `@foir/sdk` gets it early. The push that carries the commit to
108
108
 
109
109
  Changes on `dev` that have not been published to npm.
110
110
 
111
+ ## [0.15.0] — 2026-10-02
112
+
113
+ Published under npm's `next` tag from `dev`, because it calls parts of the
114
+ platform that production serves from the next promotion; `latest` moves to it
115
+ then.
116
+
117
+ ### Added
118
+
119
+ - **`notifications.setConsent` answers `confirmationPending`**, and takes
120
+ `givenElsewhere` and `locale` (`@foir/sdk/app`). A project can ask that an
121
+ opt-in an app collects in a category is confirmed by email before it counts.
122
+ In such a category an opt-in writes nothing: the platform sends the customer
123
+ one confirmation email, in the page's `locale` when they have stated no
124
+ language of their own, and the answer says `confirmationPending`. The
125
+ preference turns on when they follow the link. Everywhere else an opt-in is
126
+ applied at once, as before.
127
+ - A consent the app carries over from the system it was given in says
128
+ `givenElsewhere: true`, and is applied as it stands.
129
+
130
+ ### Changed
131
+
132
+ - `notifications.setConsent` asks the platform for `confirmationPending`, a
133
+ field a platform older than this release does not have.
134
+
135
+ ## [0.14.0] — 2026-10-01
136
+
137
+ Published under npm's `next` tag from `dev`, because it calls parts of the
138
+ platform that production serves from the next promotion; `latest` moves to it
139
+ then.
140
+
141
+ ### Added
142
+
143
+ - **`customers.get`, `customers.byEmail` and `customers.list`** on the app
144
+ client (`@foir/sdk/app`): an operation reads the customers its work is about.
145
+ `get(id)` and `byEmail(email)` answer one customer or null, and need
146
+ `customers:read:named`: the customer the operation names, by id or by their
147
+ exact email address at the install's level, and nothing wider. `list` pages
148
+ through the customers at the install's level and needs `customers:read`,
149
+ which covers the two lookups as well. An operation declaring only the named
150
+ read is refused the list. `AppCustomer` and `AppCustomerList` are their types.
151
+ - **`customers.ensure` answers `verified`**: whether the customer it found or
152
+ made has proved the address is theirs. It finds the customer holding the
153
+ email either way, so one shopper asking twice is one customer; a caller with
154
+ something that should wait for proof asks the flag first.
155
+
111
156
  ## [0.13.0] — 2026-10-01
112
157
 
113
158
  Published under npm's `next` tag from `dev`, because it calls parts of the
@@ -183,6 +183,22 @@ export interface AppConsentChange {
183
183
  * answering `stale`.
184
184
  */
185
185
  sourceModifiedAt?: string;
186
+ /**
187
+ * True when the customer gave this consent in another system and the app is
188
+ * carrying it over (a mirror). It is applied as it stands.
189
+ *
190
+ * Left out, the app collected the consent itself (a sign-up form), and an
191
+ * opt-in is applied the way its category says: at once, or, in a category
192
+ * that confirms by email, only when the customer follows the link the
193
+ * platform sends them. Until then nothing is written and the answer says
194
+ * `confirmationPending`.
195
+ */
196
+ givenElsewhere?: boolean;
197
+ /**
198
+ * The language of the page the consent was collected on. The confirmation
199
+ * email is written in it when the customer has stated none of their own.
200
+ */
201
+ locale?: string;
186
202
  }
187
203
  /** What the calling token is, and what it is served. Read from the platform, never off the token. */
188
204
  export interface AppWhoami {
@@ -367,10 +383,40 @@ export interface AppClient {
367
383
  * The customer who has this email at the install's level, made when there
368
384
  * is none. The same customer every time for one email. Needs
369
385
  * `customers:write`.
386
+ *
387
+ * `verified` says whether that address is one the customer has proved is
388
+ * theirs. A customer this call makes has not, and neither has one an
389
+ * earlier call made: anybody can type an address. So what is written for
390
+ * an unverified customer is written for whoever holds the address, and
391
+ * anything that should wait for proof asks `verified` first.
370
392
  */
371
393
  ensure(email: string): Promise<{
372
394
  id: string;
373
395
  email: string;
396
+ verified: boolean;
397
+ }>;
398
+ /**
399
+ * One customer by id, or null when there is none at the install's level.
400
+ * Needs `customers:read:named`, or `customers:read`.
401
+ */
402
+ get(id: string): Promise<AppCustomer | null>;
403
+ /**
404
+ * The customer who has this email at the install's level, or null. The
405
+ * exact address, as `ensure` matches it, and nothing wider: it finds one
406
+ * customer and never searches. Needs `customers:read:named`, or
407
+ * `customers:read`.
408
+ */
409
+ byEmail(email: string): Promise<AppCustomer | null>;
410
+ /**
411
+ * The customers at the install's level, a page at a time, newest first.
412
+ * Needs `customers:read`: an operation declaring only
413
+ * `customers:read:named` reads the customers it names and is refused the
414
+ * list.
415
+ */
416
+ list(query?: AppCustomerList): Promise<{
417
+ customers: AppCustomer[];
418
+ hasMore: boolean;
419
+ endCursor: string | null;
374
420
  }>;
375
421
  };
376
422
  notifications: {
@@ -379,9 +425,16 @@ export interface AppClient {
379
425
  /**
380
426
  * Set a named customer's consent in an opt-in category. Needs
381
427
  * `notifications:consent`, which only an operation's token carries.
428
+ *
429
+ * `confirmationPending` is true when an opt-in the app collected was not
430
+ * applied because its category confirms by email first: the customer has
431
+ * been sent a link, or was sent one recently, and the preference turns on
432
+ * when they follow it. The answer is the same whether or not the address
433
+ * already had a customer.
382
434
  */
383
435
  setConsent(change: AppConsentChange): Promise<{
384
436
  stale: boolean;
437
+ confirmationPending: boolean;
385
438
  }>;
386
439
  };
387
440
  operations: {
@@ -402,6 +455,23 @@ export interface AppClient {
402
455
  start(operationKey: string, options?: AppOperationStart): Promise<AppStartedOperation>;
403
456
  };
404
457
  }
458
+ /** A customer, as an operation that declared a customer read sees them. */
459
+ export interface AppCustomer {
460
+ id: string;
461
+ email: string;
462
+ /** Whether the customer has proved the address is theirs. */
463
+ verified: boolean;
464
+ /** `ACTIVE`, `SUSPENDED` or `PENDING`, as the platform holds it. */
465
+ status: string;
466
+ }
467
+ export interface AppCustomerList {
468
+ /** Part of an email address to look for. Absent lists everyone. */
469
+ search?: string;
470
+ /** How many to answer, at most. The platform's own page size when absent. */
471
+ first?: number;
472
+ /** The `endCursor` of the page before, to read the next one. */
473
+ after?: string;
474
+ }
405
475
  export interface AppRecordList {
406
476
  /** The model's typed filter, by model field key. Absent matches every record. */
407
477
  where?: JsonObject;
@@ -1414,6 +1414,21 @@ export enum ManifestCapability {
1414
1414
  */
1415
1415
  NOTIFICATIONS_CONSENT = 12,
1416
1416
 
1417
+ /**
1418
+ * Reading customers at the install's level. customers:read:named is one
1419
+ * customer the operation names, by id or by their exact email address, and
1420
+ * never the list. customers:read is that and the list. An operation that
1421
+ * declares the whole read holds the named one too.
1422
+ *
1423
+ * @generated from enum value: MANIFEST_CAPABILITY_CUSTOMERS_READ = 14;
1424
+ */
1425
+ CUSTOMERS_READ = 14,
1426
+
1427
+ /**
1428
+ * @generated from enum value: MANIFEST_CAPABILITY_CUSTOMERS_READ_NAMED = 15;
1429
+ */
1430
+ CUSTOMERS_READ_NAMED = 15,
1431
+
1417
1432
  /**
1418
1433
  * Reporting an ASYNC run's outcome.
1419
1434
  *
@@ -21,7 +21,7 @@
21
21
  * The manifest's own messages are on `@foir/sdk/app/contract`.
22
22
  */
23
23
  export { createAppClient } from './client';
24
- export type { AppBinding, AppClient, AppClientConfig, AppConsentChange, AppCustomerEraseResult, AppCustomerSaveResult, AppFieldMapping, AppInstallConfig, AppNotification, AppOperationStart, AppRecord, AppRecordList, AppStartedOperation, AppStatusReport, AppSyncDirection, AppUpsertResult, AppWhoami, } from './client';
24
+ export type { AppBinding, AppClient, AppClientConfig, AppConsentChange, AppCustomer, AppCustomerEraseResult, AppCustomerList, AppCustomerSaveResult, AppFieldMapping, AppInstallConfig, AppNotification, AppOperationStart, AppRecord, AppRecordList, AppStartedOperation, AppStatusReport, AppSyncDirection, AppUpsertResult, AppWhoami, } from './client';
25
25
  export { appCustomerUnlinkedOf, appLookupConflictOf, isAppFieldWithheld } from './errors';
26
26
  export type { AppLookupConflict } from './errors';
27
27
  export { appModelFieldName, appModelTypeName, appSelectorEnumValue } from './naming';
@@ -7,7 +7,7 @@ export declare const APP_HOOK_EVENTS: readonly ["RECORD_CREATED", "RECORD_UPDATE
7
7
  /** An event a hook can name. */
8
8
  export type AppHookEvent = (typeof APP_HOOK_EVENTS)[number];
9
9
  /** The capabilities an operation declares as written. */
10
- export declare const APP_CAPABILITIES: readonly ["config:read", "secrets:read.app", "secrets:put.app", "secrets:list.app", "secrets:delete.app", "status:write", "files:read", "files:list", "files:write", "customers:write", "notifications:write", "notifications:consent", "operations:complete"];
10
+ export declare const APP_CAPABILITIES: readonly ["config:read", "secrets:read.app", "secrets:put.app", "secrets:list.app", "secrets:delete.app", "status:write", "files:read", "files:list", "files:write", "customers:write", "notifications:write", "notifications:consent", "customers:read", "customers:read:named", "operations:complete"];
11
11
  /** A capability an operation declares as written. */
12
12
  export type AppCapability = (typeof APP_CAPABILITIES)[number];
13
13
  /**
@@ -8,7 +8,7 @@ var file_buf_validate_validate = /* @__PURE__ */ fileDesc("ChtidWYvdmFsaWRhdGUvd
8
8
 
9
9
  // ../proto-ts/src/apps/v1/apps_pb.js
10
10
  import { file_google_protobuf_descriptor as file_google_protobuf_descriptor2, file_google_protobuf_struct } from "@bufbuild/protobuf/wkt";
11
- var file_apps_v1_apps = /* @__PURE__ */ fileDesc2("ChJhcHBzL3YxL2FwcHMucHJvdG8SB2FwcHMudjEilgUKCUFwcENvbmZpZxI0CgRuYW1lGAEgASgJQia6SCNyIRhAMh1eW2Etel1bYS16MC05XSooLVthLXowLTldKykqJBIYCgd2ZXJzaW9uGAIgASgJQge6SARyAhABEiMKCG1ldGFkYXRhGAMgASgLMhEuYXBwcy52MS5NZXRhZGF0YRInCgptaWRkbGV3YXJlGAQgASgLMhMuYXBwcy52MS5NaWRkbGV3YXJlEikKC2NyZWRlbnRpYWxzGAUgASgLMhQuYXBwcy52MS5DcmVkZW50aWFscxIwCg9zZXR0aW5nc19zY2hlbWEYBiABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0EjAKB3NvdXJjZXMYByADKAsyHy5hcHBzLnYxLkFwcENvbmZpZy5Tb3VyY2VzRW50cnkSLAoFc2lua3MYCCADKAsyHS5hcHBzLnYxLkFwcENvbmZpZy5TaW5rc0VudHJ5EiYKCm9wZXJhdGlvbnMYCSADKAsyEi5hcHBzLnYxLk9wZXJhdGlvbhIcCgVob29rcxgKIAMoCzINLmFwcHMudjEuSG9vaxIXCgJ1aRgLIAEoCzILLmFwcHMudjEuVUkSIQoHaW5ib3VuZBgMIAEoCzIQLmFwcHMudjEuSW5ib3VuZBIiCghmZWF0dXJlcxgNIAMoCzIQLmFwcHMudjEuRmVhdHVyZRpDCgxTb3VyY2VzRW50cnkSCwoDa2V5GAEgASgJEiIKBXZhbHVlGAIgASgLMhMuYXBwcy52MS5Tb3VyY2VUeXBlOgI4ARpDCgpTaW5rc0VudHJ5EgsKA2tleRgBIAEoCRIkCgV2YWx1ZRgCIAEoCzIVLmFwcHMudjEuU2lua0NvbnRyYWN0OgI4ASKNAQoHRmVhdHVyZRIzCgNrZXkYASABKAlCJrpII3IhGEAyHV5bYS16XVthLXowLTldKigtW2EtejAtOV0rKSokEhYKBXRpdGxlGAIgASgJQge6SARyAhABEhMKC2Rlc2NyaXB0aW9uGAMgASgJEg8KB3NldHRpbmcYBCABKAkSDwoHc291cmNlcxgFIAMoCSJlCghNZXRhZGF0YRIUCgxkaXNwbGF5X25hbWUYASABKAkSEwoLZGVzY3JpcHRpb24YAiABKAkSDAoEaWNvbhgDIAEoCRIOCgZhdXRob3IYBCABKAkSEAoIaG9tZXBhZ2UYBSABKAkiLQoKTWlkZGxld2FyZRIfCgN1cmwYASABKAlCErpID3INMgteaHR0cHM6Ly8uKyLXAgoLQ3JlZGVudGlhbHMSLwoIc3RyYXRlZ3kYASABKA4yHS5hcHBzLnYxLkNyZWRlbnRpYWxzLlN0cmF0ZWd5EiwKBnNjaGVtYRgCIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3RIAIgBARIoCgVvYXV0aBgDIAEoCzIULmFwcHMudjEuT0F1dGhDb25maWdIAYgBASKpAQoIU3RyYXRlZ3kSGAoUU1RSQVRFR1lfVU5TUEVDSUZJRUQQABISCg5TVFJBVEVHWV9PQVVUSBABEhQKEFNUUkFURUdZX0FQSV9LRVkQAhIaChZTVFJBVEVHWV9TSEFSRURfU0VDUkVUEAMSFAoQU1RSQVRFR1lfU1NIX0tFWRAEEhEKDVNUUkFURUdZX05PTkUQBRIUChBTVFJBVEVHWV9NQU5BR0VEEAZCCQoHX3NjaGVtYUIICgZfb2F1dGgiTAoLT0F1dGhDb25maWcSHAoUYXV0aG9yaXplX3N0YXJ0X3BhdGgYASABKAkSHwoXYXV0aG9yaXplX2NhbGxiYWNrX3BhdGgYAiABKAkiVgoHSW5ib3VuZBIhCgd3ZWJob29rGAEgASgLMhAuYXBwcy52MS5XZWJob29rEigKB2FjY291bnQYAiABKAsyFy5hcHBzLnYxLkFjY291bnRXZWJob29rIhcKB1dlYmhvb2sSDAoEcGF0aBgBIAEoCSI/Cg5BY2NvdW50V2ViaG9vaxIMCgRwYXRoGAEgASgJEg8KB3NldHRpbmcYAiABKAkSDgoGc291cmNlGAMgASgJIt0CCgpTb3VyY2VUeXBlEg0KBWxhYmVsGAEgASgJEi8KBmZpZWxkcxgCIAMoCzIfLmFwcHMudjEuU291cmNlVHlwZS5GaWVsZHNFbnRyeRITCgtuYXR1cmFsX2tleRgDIAEoCRIQCghvcHRpb25hbBgEIAEoCBIWCg5wcm92aXNpb25fb25seRgFIAEoCBIrCglyZWZlcmVuY2UYBiABKAsyGC5hcHBzLnYxLlNvdXJjZVJlZmVyZW5jZRIkCgVvd25lchgHIAEoDjIVLmFwcHMudjEuU3RvcmFnZU93bmVyEhMKC3JlY29tbWVuZGVkGAggASgIEg4KBmxvb2t1cBgJIAMoCRIQCghpZGVudGl0eRgKIAEoCRpGCgtGaWVsZHNFbnRyeRILCgNrZXkYASABKAkSJgoFdmFsdWUYAiABKAsyFy5hcHBzLnYxLlN1Z2dlc3RlZEZpZWxkOgI4ASKqAgoPU291cmNlUmVmZXJlbmNlEhEKCWtleV9maWVsZBgBIAEoCRI0CgZmaWVsZHMYAiADKAsyJC5hcHBzLnYxLlNvdXJjZVJlZmVyZW5jZS5GaWVsZHNFbnRyeRIQCghlbmRwb2ludBgDIAEoCRINCgVob3N0cxgEIAMoCRIqCgdyZXF1ZXN0GAUgASgLMhkuYXBwcy52MS5SZWZlcmVuY2VSZXF1ZXN0EhoKEnJlc3BvbnNlX3RyYW5zZm9ybRgGIAEoCRIdChVjYWNoZV9tYXhfYWdlX3NlY29uZHMYByABKAUaRgoLRmllbGRzRW50cnkSCwoDa2V5GAEgASgJEiYKBXZhbHVlGAIgASgLMhcuYXBwcy52MS5TdWdnZXN0ZWRGaWVsZDoCOAEiswIKEFJlZmVyZW5jZVJlcXVlc3QSDgoGbWV0aG9kGAEgASgJEhUKDXBhdGhfdGVtcGxhdGUYAiABKAkSMwoFcXVlcnkYAyADKAsyJC5hcHBzLnYxLlJlZmVyZW5jZVJlcXVlc3QuUXVlcnlFbnRyeRI3CgdoZWFkZXJzGAQgAygLMiYuYXBwcy52MS5SZWZlcmVuY2VSZXF1ZXN0LkhlYWRlcnNFbnRyeRIVCg1ib2R5X3RlbXBsYXRlGAUgASgJEhUKDWJhdGNoX2NhcGFibGUYBiABKAgaLAoKUXVlcnlFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBGi4KDEhlYWRlcnNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIroBCgxTaW5rQ29udHJhY3QSDQoFbGFiZWwYASABKAkSMQoGZmllbGRzGAIgAygLMiEuYXBwcy52MS5TaW5rQ29udHJhY3QuRmllbGRzRW50cnkSEwoLbmF0dXJhbF9rZXkYAyABKAkSEAoIb3B0aW9uYWwYBCABKAgaQQoLRmllbGRzRW50cnkSCwoDa2V5GAEgASgJEiEKBXZhbHVlGAIgASgLMhIuYXBwcy52MS5TaW5rRmllbGQ6AjgBIpYCCg5TdWdnZXN0ZWRGaWVsZBIMCgR0eXBlGAEgASgJEhIKBWxhYmVsGAIgASgJSACIAQESFQoIcmVxdWlyZWQYAyABKAhIAYgBARIsCgZjb25maWcYBCABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0SAKIAQESPQoTc3VwcG9ydGVkX2RpcmVjdGlvbhgFIAEoDjIbLmFwcHMudjEuRmllbGRTeW5jRGlyZWN0aW9uSAOIAQESFgoJcXVlcnlhYmxlGAYgASgISASIAQFCCAoGX2xhYmVsQgsKCV9yZXF1aXJlZEIJCgdfY29uZmlnQhYKFF9zdXBwb3J0ZWRfZGlyZWN0aW9uQgwKCl9xdWVyeWFibGUiWwoJU2lua0ZpZWxkEgwKBHR5cGUYASABKAkSEAoIc2VtYW50aWMYAiABKAkSEAoIcmVxdWlyZWQYAyABKAgSEgoFbGFiZWwYBCABKAlIAIgBAUIICgZfbGFiZWwimAQKCU9wZXJhdGlvbhI6CgNrZXkYASABKAlCLbpIKnIoGEAyJF4oX18pP1thLXpdW2EtejAtOV9dKigtW2EtejAtOV9dKykqJBIMCgRuYW1lGAIgASgJEhgKC2Rlc2NyaXB0aW9uGAMgASgJSACIAQESHQoIZW5kcG9pbnQYBCABKAlCC7pICHIGMgReLy4qEhQKDGNhcGFiaWxpdGllcxgFIAMoCRIXCgp0aW1lb3V0X21zGAYgASgFSAGIAQESMgoMcmV0cnlfcG9saWN5GAcgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdEgCiAEBEjIKDGlucHV0X3NjaGVtYRgIIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3RIA4gBARIzCg1vdXRwdXRfc2NoZW1hGAkgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdEgEiAEBEhsKDnN1cHBvcnRzX2FzeW5jGAogASgISAWIAQESGQoRcnVuX2FmdGVyX2luc3RhbGwYCyABKAgSHgoWcnVuc19vbl9tYXBwZWRfc291cmNlcxgMIAEoCEIOCgxfZGVzY3JpcHRpb25CDQoLX3RpbWVvdXRfbXNCDwoNX3JldHJ5X3BvbGljeUIPCg1faW5wdXRfc2NoZW1hQhAKDl9vdXRwdXRfc2NoZW1hQhEKD19zdXBwb3J0c19hc3luYyI9ChZNYW5pZmVzdENhcGFiaWxpdHlTcGVjEhAKCGRlY2xhcmVzGAEgASgJEhEKCXBlcl9tb2RlbBgCIAEoCCKmAQoESG9vaxIzCgNrZXkYASABKAlCJrpII3IhGEAyHV5bYS16XVthLXowLTldKigtW2EtejAtOV0rKSokEgwKBG5hbWUYAiABKAkSFgoFZXZlbnQYAyABKAlCB7pIBHICEAESJwoGZmlsdGVyGAQgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBIaCglvcGVyYXRpb24YBSABKAlCB7pIBHICEAEiNwoCVUkSMQoKcGxhY2VtZW50cxgBIAMoCzIdLmFwcHMudjEuUGxhY2VtZW50RGVjbGFyYXRpb24itgQKFFBsYWNlbWVudERlY2xhcmF0aW9uEjMKA2tleRgBIAEoCUImukgjciEYQDIdXlthLXpdW2EtejAtOV0qKC1bYS16MC05XSspKiQSNAoGdGFyZ2V0GAIgASgOMiQuYXBwcy52MS5QbGFjZW1lbnREZWNsYXJhdGlvbi5UYXJnZXQSEgoFbW9kZWwYAyABKAlIAIgBARISCgVmaWVsZBgEIAEoCUgBiAEBEiEKGWZpZWxkX3NlbGVjdGVkX2F0X2luc3RhbGwYBSABKAgSEAoDdGFiGAYgASgJSAKIAQESFgoFdGl0bGUYByABKAlCB7pIBHICEAESHwoDdXJsGAggASgJQhK6SA9yDTILXmh0dHBzOi8vLisSHQoQcmVwbGFjZXNfZGVmYXVsdBgJIAEoCEgDiAEBEisKBWhpbnRzGAogASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdEgEiAEBEg4KBnNjb3BlcxgLIAMoCRIQCghzZXR0aW5ncxgMIAMoCSJ0CgZUYXJnZXQSFgoSVEFSR0VUX1VOU1BFQ0lGSUVEEAASFAoQVEFSR0VUX0ZVTExfUEFHRRABEhYKElRBUkdFVF9NQUlOX0VESVRPUhACEhIKDlRBUkdFVF9TSURFQkFSEAMSEAoMVEFSR0VUX0ZJRUxEEARCCAoGX21vZGVsQggKBl9maWVsZEIGCgRfdGFiQhMKEV9yZXBsYWNlc19kZWZhdWx0QggKBl9oaW50cyqsAQoSRmllbGRTeW5jRGlyZWN0aW9uEiQKIEZJRUxEX1NZTkNfRElSRUNUSU9OX1VOU1BFQ0lGSUVEEAASJAogRklFTERfU1lOQ19ESVJFQ1RJT05fU09VUkNFX09OTFkQARIiCh5GSUVMRF9TWU5DX0RJUkVDVElPTl9TSU5LX09OTFkQAhImCiJGSUVMRF9TWU5DX0RJUkVDVElPTl9CSURJUkVDVElPTkFMEAMqhAEKDFN0b3JhZ2VPd25lchIdChlTVE9SQUdFX09XTkVSX1VOU1BFQ0lGSUVEEAASGQoVU1RPUkFHRV9PV05FUl9QUk9KRUNUEAESHgoaU1RPUkFHRV9PV05FUl9PUkdBTklaQVRJT04QAhIaChZTVE9SQUdFX09XTkVSX0NVU1RPTUVSEAMqvAkKEk1hbmlmZXN0Q2FwYWJpbGl0eRIjCh9NQU5JRkVTVF9DQVBBQklMSVRZX1VOU1BFQ0lGSUVEEAASNgofTUFOSUZFU1RfQ0FQQUJJTElUWV9DT05GSUdfUkVBRBABGhGaph0NCgtjb25maWc6cmVhZBJACiRNQU5JRkVTVF9DQVBBQklMSVRZX1NFQ1JFVFNfUkVBRF9BUFAQAhoWmqYdEgoQc2VjcmV0czpyZWFkLmFwcBI+CiNNQU5JRkVTVF9DQVBBQklMSVRZX1NFQ1JFVFNfUFVUX0FQUBADGhWaph0RCg9zZWNyZXRzOnB1dC5hcHASQAokTUFOSUZFU1RfQ0FQQUJJTElUWV9TRUNSRVRTX0xJU1RfQVBQEAQaFpqmHRIKEHNlY3JldHM6bGlzdC5hcHASRAomTUFOSUZFU1RfQ0FQQUJJTElUWV9TRUNSRVRTX0RFTEVURV9BUFAQBRoYmqYdFAoSc2VjcmV0czpkZWxldGUuYXBwEjgKIE1BTklGRVNUX0NBUEFCSUxJVFlfU1RBVFVTX1dSSVRFEAYaEpqmHQ4KDHN0YXR1czp3cml0ZRI0Ch5NQU5JRkVTVF9DQVBBQklMSVRZX0ZJTEVTX1JFQUQQBxoQmqYdDAoKZmlsZXM6cmVhZBI0Ch5NQU5JRkVTVF9DQVBBQklMSVRZX0ZJTEVTX0xJU1QQCBoQmqYdDAoKZmlsZXM6bGlzdBI2Ch9NQU5JRkVTVF9DQVBBQklMSVRZX0ZJTEVTX1dSSVRFEAkaEZqmHQ0KC2ZpbGVzOndyaXRlEj4KI01BTklGRVNUX0NBUEFCSUxJVFlfQ1VTVE9NRVJTX1dSSVRFEAoaFZqmHREKD2N1c3RvbWVyczp3cml0ZRJGCidNQU5JRkVTVF9DQVBBQklMSVRZX05PVElGSUNBVElPTlNfV1JJVEUQCxoZmqYdFQoTbm90aWZpY2F0aW9uczp3cml0ZRJKCilNQU5JRkVTVF9DQVBBQklMSVRZX05PVElGSUNBVElPTlNfQ09OU0VOVBAMGhuaph0XChVub3RpZmljYXRpb25zOmNvbnNlbnQSRgonTUFOSUZFU1RfQ0FQQUJJTElUWV9PUEVSQVRJT05TX0NPTVBMRVRFEA0aGZqmHRUKE29wZXJhdGlvbnM6Y29tcGxldGUSOwogTUFOSUZFU1RfQ0FQQUJJTElUWV9SRUNPUkRTX1JFQUQQFBoVmqYdEQoNcmVjb3JkczpyZWFkOhABEj0KIU1BTklGRVNUX0NBUEFCSUxJVFlfUkVDT1JEU19XUklURRAVGhaaph0SCg5yZWNvcmRzOndyaXRlOhABEj8KIk1BTklGRVNUX0NBUEFCSUxJVFlfUkVDT1JEU19ERUxFVEUQFhoXmqYdEwoPcmVjb3JkczpkZWxldGU6EAESQQojTUFOSUZFU1RfQ0FQQUJJTElUWV9SRUNPUkRTX1BVQkxJU0gQFxoYmqYdFAoQcmVjb3JkczpwdWJsaXNoOhABEkUKJU1BTklGRVNUX0NBUEFCSUxJVFlfUkVDT1JEU19BVFRSSUJVVEUQGBoamqYdFgoScmVjb3JkczphdHRyaWJ1dGU6EAE6dQoTbWFuaWZlc3RfY2FwYWJpbGl0eRIhLmdvb2dsZS5wcm90b2J1Zi5FbnVtVmFsdWVPcHRpb25zGOPUAyABKAsyHy5hcHBzLnYxLk1hbmlmZXN0Q2FwYWJpbGl0eVNwZWNSEm1hbmlmZXN0Q2FwYWJpbGl0eUI1WjNnaXRodWIuY29tL2VpZGVzdHVkaW8vZm9pci9nZW4vcHJvdG8vYXBwcy92MTthcHBzdjFiBnByb3RvMw", [file_buf_validate_validate, file_google_protobuf_descriptor2, file_google_protobuf_struct]);
11
+ var file_apps_v1_apps = /* @__PURE__ */ fileDesc2("ChJhcHBzL3YxL2FwcHMucHJvdG8SB2FwcHMudjEilgUKCUFwcENvbmZpZxI0CgRuYW1lGAEgASgJQia6SCNyIRhAMh1eW2Etel1bYS16MC05XSooLVthLXowLTldKykqJBIYCgd2ZXJzaW9uGAIgASgJQge6SARyAhABEiMKCG1ldGFkYXRhGAMgASgLMhEuYXBwcy52MS5NZXRhZGF0YRInCgptaWRkbGV3YXJlGAQgASgLMhMuYXBwcy52MS5NaWRkbGV3YXJlEikKC2NyZWRlbnRpYWxzGAUgASgLMhQuYXBwcy52MS5DcmVkZW50aWFscxIwCg9zZXR0aW5nc19zY2hlbWEYBiABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0EjAKB3NvdXJjZXMYByADKAsyHy5hcHBzLnYxLkFwcENvbmZpZy5Tb3VyY2VzRW50cnkSLAoFc2lua3MYCCADKAsyHS5hcHBzLnYxLkFwcENvbmZpZy5TaW5rc0VudHJ5EiYKCm9wZXJhdGlvbnMYCSADKAsyEi5hcHBzLnYxLk9wZXJhdGlvbhIcCgVob29rcxgKIAMoCzINLmFwcHMudjEuSG9vaxIXCgJ1aRgLIAEoCzILLmFwcHMudjEuVUkSIQoHaW5ib3VuZBgMIAEoCzIQLmFwcHMudjEuSW5ib3VuZBIiCghmZWF0dXJlcxgNIAMoCzIQLmFwcHMudjEuRmVhdHVyZRpDCgxTb3VyY2VzRW50cnkSCwoDa2V5GAEgASgJEiIKBXZhbHVlGAIgASgLMhMuYXBwcy52MS5Tb3VyY2VUeXBlOgI4ARpDCgpTaW5rc0VudHJ5EgsKA2tleRgBIAEoCRIkCgV2YWx1ZRgCIAEoCzIVLmFwcHMudjEuU2lua0NvbnRyYWN0OgI4ASKNAQoHRmVhdHVyZRIzCgNrZXkYASABKAlCJrpII3IhGEAyHV5bYS16XVthLXowLTldKigtW2EtejAtOV0rKSokEhYKBXRpdGxlGAIgASgJQge6SARyAhABEhMKC2Rlc2NyaXB0aW9uGAMgASgJEg8KB3NldHRpbmcYBCABKAkSDwoHc291cmNlcxgFIAMoCSJlCghNZXRhZGF0YRIUCgxkaXNwbGF5X25hbWUYASABKAkSEwoLZGVzY3JpcHRpb24YAiABKAkSDAoEaWNvbhgDIAEoCRIOCgZhdXRob3IYBCABKAkSEAoIaG9tZXBhZ2UYBSABKAkiLQoKTWlkZGxld2FyZRIfCgN1cmwYASABKAlCErpID3INMgteaHR0cHM6Ly8uKyLXAgoLQ3JlZGVudGlhbHMSLwoIc3RyYXRlZ3kYASABKA4yHS5hcHBzLnYxLkNyZWRlbnRpYWxzLlN0cmF0ZWd5EiwKBnNjaGVtYRgCIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3RIAIgBARIoCgVvYXV0aBgDIAEoCzIULmFwcHMudjEuT0F1dGhDb25maWdIAYgBASKpAQoIU3RyYXRlZ3kSGAoUU1RSQVRFR1lfVU5TUEVDSUZJRUQQABISCg5TVFJBVEVHWV9PQVVUSBABEhQKEFNUUkFURUdZX0FQSV9LRVkQAhIaChZTVFJBVEVHWV9TSEFSRURfU0VDUkVUEAMSFAoQU1RSQVRFR1lfU1NIX0tFWRAEEhEKDVNUUkFURUdZX05PTkUQBRIUChBTVFJBVEVHWV9NQU5BR0VEEAZCCQoHX3NjaGVtYUIICgZfb2F1dGgiTAoLT0F1dGhDb25maWcSHAoUYXV0aG9yaXplX3N0YXJ0X3BhdGgYASABKAkSHwoXYXV0aG9yaXplX2NhbGxiYWNrX3BhdGgYAiABKAkiVgoHSW5ib3VuZBIhCgd3ZWJob29rGAEgASgLMhAuYXBwcy52MS5XZWJob29rEigKB2FjY291bnQYAiABKAsyFy5hcHBzLnYxLkFjY291bnRXZWJob29rIhcKB1dlYmhvb2sSDAoEcGF0aBgBIAEoCSI/Cg5BY2NvdW50V2ViaG9vaxIMCgRwYXRoGAEgASgJEg8KB3NldHRpbmcYAiABKAkSDgoGc291cmNlGAMgASgJIt0CCgpTb3VyY2VUeXBlEg0KBWxhYmVsGAEgASgJEi8KBmZpZWxkcxgCIAMoCzIfLmFwcHMudjEuU291cmNlVHlwZS5GaWVsZHNFbnRyeRITCgtuYXR1cmFsX2tleRgDIAEoCRIQCghvcHRpb25hbBgEIAEoCBIWCg5wcm92aXNpb25fb25seRgFIAEoCBIrCglyZWZlcmVuY2UYBiABKAsyGC5hcHBzLnYxLlNvdXJjZVJlZmVyZW5jZRIkCgVvd25lchgHIAEoDjIVLmFwcHMudjEuU3RvcmFnZU93bmVyEhMKC3JlY29tbWVuZGVkGAggASgIEg4KBmxvb2t1cBgJIAMoCRIQCghpZGVudGl0eRgKIAEoCRpGCgtGaWVsZHNFbnRyeRILCgNrZXkYASABKAkSJgoFdmFsdWUYAiABKAsyFy5hcHBzLnYxLlN1Z2dlc3RlZEZpZWxkOgI4ASKqAgoPU291cmNlUmVmZXJlbmNlEhEKCWtleV9maWVsZBgBIAEoCRI0CgZmaWVsZHMYAiADKAsyJC5hcHBzLnYxLlNvdXJjZVJlZmVyZW5jZS5GaWVsZHNFbnRyeRIQCghlbmRwb2ludBgDIAEoCRINCgVob3N0cxgEIAMoCRIqCgdyZXF1ZXN0GAUgASgLMhkuYXBwcy52MS5SZWZlcmVuY2VSZXF1ZXN0EhoKEnJlc3BvbnNlX3RyYW5zZm9ybRgGIAEoCRIdChVjYWNoZV9tYXhfYWdlX3NlY29uZHMYByABKAUaRgoLRmllbGRzRW50cnkSCwoDa2V5GAEgASgJEiYKBXZhbHVlGAIgASgLMhcuYXBwcy52MS5TdWdnZXN0ZWRGaWVsZDoCOAEiswIKEFJlZmVyZW5jZVJlcXVlc3QSDgoGbWV0aG9kGAEgASgJEhUKDXBhdGhfdGVtcGxhdGUYAiABKAkSMwoFcXVlcnkYAyADKAsyJC5hcHBzLnYxLlJlZmVyZW5jZVJlcXVlc3QuUXVlcnlFbnRyeRI3CgdoZWFkZXJzGAQgAygLMiYuYXBwcy52MS5SZWZlcmVuY2VSZXF1ZXN0LkhlYWRlcnNFbnRyeRIVCg1ib2R5X3RlbXBsYXRlGAUgASgJEhUKDWJhdGNoX2NhcGFibGUYBiABKAgaLAoKUXVlcnlFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBGi4KDEhlYWRlcnNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIroBCgxTaW5rQ29udHJhY3QSDQoFbGFiZWwYASABKAkSMQoGZmllbGRzGAIgAygLMiEuYXBwcy52MS5TaW5rQ29udHJhY3QuRmllbGRzRW50cnkSEwoLbmF0dXJhbF9rZXkYAyABKAkSEAoIb3B0aW9uYWwYBCABKAgaQQoLRmllbGRzRW50cnkSCwoDa2V5GAEgASgJEiEKBXZhbHVlGAIgASgLMhIuYXBwcy52MS5TaW5rRmllbGQ6AjgBIpYCCg5TdWdnZXN0ZWRGaWVsZBIMCgR0eXBlGAEgASgJEhIKBWxhYmVsGAIgASgJSACIAQESFQoIcmVxdWlyZWQYAyABKAhIAYgBARIsCgZjb25maWcYBCABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0SAKIAQESPQoTc3VwcG9ydGVkX2RpcmVjdGlvbhgFIAEoDjIbLmFwcHMudjEuRmllbGRTeW5jRGlyZWN0aW9uSAOIAQESFgoJcXVlcnlhYmxlGAYgASgISASIAQFCCAoGX2xhYmVsQgsKCV9yZXF1aXJlZEIJCgdfY29uZmlnQhYKFF9zdXBwb3J0ZWRfZGlyZWN0aW9uQgwKCl9xdWVyeWFibGUiWwoJU2lua0ZpZWxkEgwKBHR5cGUYASABKAkSEAoIc2VtYW50aWMYAiABKAkSEAoIcmVxdWlyZWQYAyABKAgSEgoFbGFiZWwYBCABKAlIAIgBAUIICgZfbGFiZWwimAQKCU9wZXJhdGlvbhI6CgNrZXkYASABKAlCLbpIKnIoGEAyJF4oX18pP1thLXpdW2EtejAtOV9dKigtW2EtejAtOV9dKykqJBIMCgRuYW1lGAIgASgJEhgKC2Rlc2NyaXB0aW9uGAMgASgJSACIAQESHQoIZW5kcG9pbnQYBCABKAlCC7pICHIGMgReLy4qEhQKDGNhcGFiaWxpdGllcxgFIAMoCRIXCgp0aW1lb3V0X21zGAYgASgFSAGIAQESMgoMcmV0cnlfcG9saWN5GAcgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdEgCiAEBEjIKDGlucHV0X3NjaGVtYRgIIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3RIA4gBARIzCg1vdXRwdXRfc2NoZW1hGAkgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdEgEiAEBEhsKDnN1cHBvcnRzX2FzeW5jGAogASgISAWIAQESGQoRcnVuX2FmdGVyX2luc3RhbGwYCyABKAgSHgoWcnVuc19vbl9tYXBwZWRfc291cmNlcxgMIAEoCEIOCgxfZGVzY3JpcHRpb25CDQoLX3RpbWVvdXRfbXNCDwoNX3JldHJ5X3BvbGljeUIPCg1faW5wdXRfc2NoZW1hQhAKDl9vdXRwdXRfc2NoZW1hQhEKD19zdXBwb3J0c19hc3luYyI9ChZNYW5pZmVzdENhcGFiaWxpdHlTcGVjEhAKCGRlY2xhcmVzGAEgASgJEhEKCXBlcl9tb2RlbBgCIAEoCCKmAQoESG9vaxIzCgNrZXkYASABKAlCJrpII3IhGEAyHV5bYS16XVthLXowLTldKigtW2EtejAtOV0rKSokEgwKBG5hbWUYAiABKAkSFgoFZXZlbnQYAyABKAlCB7pIBHICEAESJwoGZmlsdGVyGAQgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBIaCglvcGVyYXRpb24YBSABKAlCB7pIBHICEAEiNwoCVUkSMQoKcGxhY2VtZW50cxgBIAMoCzIdLmFwcHMudjEuUGxhY2VtZW50RGVjbGFyYXRpb24itgQKFFBsYWNlbWVudERlY2xhcmF0aW9uEjMKA2tleRgBIAEoCUImukgjciEYQDIdXlthLXpdW2EtejAtOV0qKC1bYS16MC05XSspKiQSNAoGdGFyZ2V0GAIgASgOMiQuYXBwcy52MS5QbGFjZW1lbnREZWNsYXJhdGlvbi5UYXJnZXQSEgoFbW9kZWwYAyABKAlIAIgBARISCgVmaWVsZBgEIAEoCUgBiAEBEiEKGWZpZWxkX3NlbGVjdGVkX2F0X2luc3RhbGwYBSABKAgSEAoDdGFiGAYgASgJSAKIAQESFgoFdGl0bGUYByABKAlCB7pIBHICEAESHwoDdXJsGAggASgJQhK6SA9yDTILXmh0dHBzOi8vLisSHQoQcmVwbGFjZXNfZGVmYXVsdBgJIAEoCEgDiAEBEisKBWhpbnRzGAogASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdEgEiAEBEg4KBnNjb3BlcxgLIAMoCRIQCghzZXR0aW5ncxgMIAMoCSJ0CgZUYXJnZXQSFgoSVEFSR0VUX1VOU1BFQ0lGSUVEEAASFAoQVEFSR0VUX0ZVTExfUEFHRRABEhYKElRBUkdFVF9NQUlOX0VESVRPUhACEhIKDlRBUkdFVF9TSURFQkFSEAMSEAoMVEFSR0VUX0ZJRUxEEARCCAoGX21vZGVsQggKBl9maWVsZEIGCgRfdGFiQhMKEV9yZXBsYWNlc19kZWZhdWx0QggKBl9oaW50cyqsAQoSRmllbGRTeW5jRGlyZWN0aW9uEiQKIEZJRUxEX1NZTkNfRElSRUNUSU9OX1VOU1BFQ0lGSUVEEAASJAogRklFTERfU1lOQ19ESVJFQ1RJT05fU09VUkNFX09OTFkQARIiCh5GSUVMRF9TWU5DX0RJUkVDVElPTl9TSU5LX09OTFkQAhImCiJGSUVMRF9TWU5DX0RJUkVDVElPTl9CSURJUkVDVElPTkFMEAMqhAEKDFN0b3JhZ2VPd25lchIdChlTVE9SQUdFX09XTkVSX1VOU1BFQ0lGSUVEEAASGQoVU1RPUkFHRV9PV05FUl9QUk9KRUNUEAESHgoaU1RPUkFHRV9PV05FUl9PUkdBTklaQVRJT04QAhIaChZTVE9SQUdFX09XTkVSX0NVU1RPTUVSEAMqxAoKEk1hbmlmZXN0Q2FwYWJpbGl0eRIjCh9NQU5JRkVTVF9DQVBBQklMSVRZX1VOU1BFQ0lGSUVEEAASNgofTUFOSUZFU1RfQ0FQQUJJTElUWV9DT05GSUdfUkVBRBABGhGaph0NCgtjb25maWc6cmVhZBJACiRNQU5JRkVTVF9DQVBBQklMSVRZX1NFQ1JFVFNfUkVBRF9BUFAQAhoWmqYdEgoQc2VjcmV0czpyZWFkLmFwcBI+CiNNQU5JRkVTVF9DQVBBQklMSVRZX1NFQ1JFVFNfUFVUX0FQUBADGhWaph0RCg9zZWNyZXRzOnB1dC5hcHASQAokTUFOSUZFU1RfQ0FQQUJJTElUWV9TRUNSRVRTX0xJU1RfQVBQEAQaFpqmHRIKEHNlY3JldHM6bGlzdC5hcHASRAomTUFOSUZFU1RfQ0FQQUJJTElUWV9TRUNSRVRTX0RFTEVURV9BUFAQBRoYmqYdFAoSc2VjcmV0czpkZWxldGUuYXBwEjgKIE1BTklGRVNUX0NBUEFCSUxJVFlfU1RBVFVTX1dSSVRFEAYaEpqmHQ4KDHN0YXR1czp3cml0ZRI0Ch5NQU5JRkVTVF9DQVBBQklMSVRZX0ZJTEVTX1JFQUQQBxoQmqYdDAoKZmlsZXM6cmVhZBI0Ch5NQU5JRkVTVF9DQVBBQklMSVRZX0ZJTEVTX0xJU1QQCBoQmqYdDAoKZmlsZXM6bGlzdBI2Ch9NQU5JRkVTVF9DQVBBQklMSVRZX0ZJTEVTX1dSSVRFEAkaEZqmHQ0KC2ZpbGVzOndyaXRlEj4KI01BTklGRVNUX0NBUEFCSUxJVFlfQ1VTVE9NRVJTX1dSSVRFEAoaFZqmHREKD2N1c3RvbWVyczp3cml0ZRJGCidNQU5JRkVTVF9DQVBBQklMSVRZX05PVElGSUNBVElPTlNfV1JJVEUQCxoZmqYdFQoTbm90aWZpY2F0aW9uczp3cml0ZRJKCilNQU5JRkVTVF9DQVBBQklMSVRZX05PVElGSUNBVElPTlNfQ09OU0VOVBAMGhuaph0XChVub3RpZmljYXRpb25zOmNvbnNlbnQSPAoiTUFOSUZFU1RfQ0FQQUJJTElUWV9DVVNUT01FUlNfUkVBRBAOGhSaph0QCg5jdXN0b21lcnM6cmVhZBJICihNQU5JRkVTVF9DQVBBQklMSVRZX0NVU1RPTUVSU19SRUFEX05BTUVEEA8aGpqmHRYKFGN1c3RvbWVyczpyZWFkOm5hbWVkEkYKJ01BTklGRVNUX0NBUEFCSUxJVFlfT1BFUkFUSU9OU19DT01QTEVURRANGhmaph0VChNvcGVyYXRpb25zOmNvbXBsZXRlEjsKIE1BTklGRVNUX0NBUEFCSUxJVFlfUkVDT1JEU19SRUFEEBQaFZqmHREKDXJlY29yZHM6cmVhZDoQARI9CiFNQU5JRkVTVF9DQVBBQklMSVRZX1JFQ09SRFNfV1JJVEUQFRoWmqYdEgoOcmVjb3Jkczp3cml0ZToQARI/CiJNQU5JRkVTVF9DQVBBQklMSVRZX1JFQ09SRFNfREVMRVRFEBYaF5qmHRMKD3JlY29yZHM6ZGVsZXRlOhABEkEKI01BTklGRVNUX0NBUEFCSUxJVFlfUkVDT1JEU19QVUJMSVNIEBcaGJqmHRQKEHJlY29yZHM6cHVibGlzaDoQARJFCiVNQU5JRkVTVF9DQVBBQklMSVRZX1JFQ09SRFNfQVRUUklCVVRFEBgaGpqmHRYKEnJlY29yZHM6YXR0cmlidXRlOhABOnUKE21hbmlmZXN0X2NhcGFiaWxpdHkSIS5nb29nbGUucHJvdG9idWYuRW51bVZhbHVlT3B0aW9ucxjj1AMgASgLMh8uYXBwcy52MS5NYW5pZmVzdENhcGFiaWxpdHlTcGVjUhJtYW5pZmVzdENhcGFiaWxpdHlCNVozZ2l0aHViLmNvbS9laWRlc3R1ZGlvL2ZvaXIvZ2VuL3Byb3RvL2FwcHMvdjE7YXBwc3YxYgZwcm90bzM", [file_buf_validate_validate, file_google_protobuf_descriptor2, file_google_protobuf_struct]);
12
12
  var AppConfigSchema = /* @__PURE__ */ messageDesc2(file_apps_v1_apps, 0);
13
13
  var FeatureSchema = /* @__PURE__ */ messageDesc2(file_apps_v1_apps, 1);
14
14
  var MetadataSchema = /* @__PURE__ */ messageDesc2(file_apps_v1_apps, 2);
package/dist/app.js CHANGED
@@ -279,6 +279,14 @@ function isSelectableFieldKey(fieldKey) {
279
279
  }
280
280
 
281
281
  // src/app/client.ts
282
+ var CUSTOMER_FIELDS = "id email status emailVerifiedAt";
283
+ function isVerified(customer) {
284
+ return typeof customer.emailVerifiedAt === "string" && customer.emailVerifiedAt !== "";
285
+ }
286
+ function customerOf(customer) {
287
+ if (!customer?.id) return null;
288
+ return { id: customer.id, email: customer.email ?? "", verified: isVerified(customer), status: customer.status ?? "" };
289
+ }
282
290
  var RECORD_ID_FIELD = "_id";
283
291
  var RECORD_NATURAL_KEY_FIELD = "_naturalKey";
284
292
  var RECORD_OWNER_FIELD = "_ownerId";
@@ -735,11 +743,53 @@ function createAppClient(config) {
735
743
  if (address === "") refuse("a customer was asked for with an empty email");
736
744
  const customer = await field(
737
745
  "createCustomer",
738
- `mutation AppEnsureCustomer($input: CreateCustomerInput!) { createCustomer(input: $input) { id email } }`,
746
+ `mutation AppEnsureCustomer($input: CreateCustomerInput!) { createCustomer(input: $input) { id email emailVerifiedAt } }`,
739
747
  { input: { email: address, returnExisting: true } }
740
748
  );
741
749
  if (!customer?.id) return refuse("createCustomer reported no error and returned no customer");
742
- return { id: customer.id, email: customer.email ?? address };
750
+ return { id: customer.id, email: customer.email ?? address, verified: isVerified(customer) };
751
+ },
752
+ async get(id) {
753
+ if (id.trim() === "") refuse("a customer was asked for with an empty id");
754
+ const customer = await field(
755
+ "customer",
756
+ `query AppCustomer($id: String) { customer(id: $id) { ${CUSTOMER_FIELDS} } }`,
757
+ { id }
758
+ );
759
+ return customerOf(customer);
760
+ },
761
+ async byEmail(email) {
762
+ const address = email.trim();
763
+ if (address === "") refuse("a customer was asked for with an empty email");
764
+ const customer = await field(
765
+ "customer",
766
+ `query AppCustomerByEmail($email: String) { customer(email: $email) { ${CUSTOMER_FIELDS} } }`,
767
+ { email: address }
768
+ );
769
+ return customerOf(customer);
770
+ },
771
+ async list(query = {}) {
772
+ const search = query.search?.trim() ?? "";
773
+ const page = await field(
774
+ "customers",
775
+ `query AppCustomers($first: Int, $after: String, $filter: CustomersFilterInput) {
776
+ customers(first: $first, after: $after, filter: $filter) {
777
+ edges { node { ${CUSTOMER_FIELDS} } }
778
+ pageInfo { hasNextPage endCursor }
779
+ }
780
+ }`,
781
+ {
782
+ ...query.first !== void 0 ? { first: query.first } : {},
783
+ ...query.after !== void 0 ? { after: query.after } : {},
784
+ ...search !== "" ? { filter: { search } } : {}
785
+ }
786
+ );
787
+ const customers = [];
788
+ for (const edge of page?.edges ?? []) {
789
+ const customer = customerOf(edge?.node ?? null);
790
+ if (customer) customers.push(customer);
791
+ }
792
+ return { customers, hasMore: page?.pageInfo?.hasNextPage === true, endCursor: page?.pageInfo?.endCursor ?? null };
743
793
  }
744
794
  },
745
795
  notifications: {
@@ -775,13 +825,15 @@ function createAppClient(config) {
775
825
  enabled: change.enabled
776
826
  };
777
827
  if (change.sourceModifiedAt !== void 0) input.sourceModifiedAt = change.sourceModifiedAt;
828
+ if (change.givenElsewhere !== void 0) input.givenElsewhere = change.givenElsewhere;
829
+ if (change.locale !== void 0) input.locale = change.locale;
778
830
  const answer = await field(
779
831
  "setCustomerConsent",
780
- `mutation AppSetCustomerConsent($input: SetCustomerConsentInput!) { setCustomerConsent(input: $input) { stale } }`,
832
+ `mutation AppSetCustomerConsent($input: SetCustomerConsentInput!) { setCustomerConsent(input: $input) { stale confirmationPending } }`,
781
833
  { input }
782
834
  );
783
835
  if (!answer) return refuse("setCustomerConsent reported no error and answered nothing");
784
- return { stale: answer.stale === true };
836
+ return { stale: answer.stale === true, confirmationPending: answer.confirmationPending === true };
785
837
  }
786
838
  },
787
839
  operations: {
@@ -1075,6 +1127,8 @@ var APP_CAPABILITIES = [
1075
1127
  "customers:write",
1076
1128
  "notifications:write",
1077
1129
  "notifications:consent",
1130
+ "customers:read",
1131
+ "customers:read:named",
1078
1132
  "operations:complete"
1079
1133
  ];
1080
1134
  var APP_RECORD_CAPABILITY_PREFIXES = [
@@ -4,8 +4,8 @@
4
4
  * Used by config API backends (e.g., Hono servers) to query the platform
5
5
  * GraphQL API at runtime. Provisioning (models, operations, schedules) is
6
6
  * handled by `foir push`. Record writes from upstream sources go through
7
- * the scoped-token `upsertRecord` GraphQL mutation served by the
8
- * integrations gateway, not through this client.
7
+ * the scoped-token `upsertRecord` GraphQL mutation an app's token reaches
8
+ * (`@foir/sdk/app`), not through this client.
9
9
  *
10
10
  * Auth: All requests use an API key (x-api-key header).
11
11
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foir/sdk",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "description": "Official Foir SDK — editor iframe client + server-side platform helpers",
5
5
  "type": "module",
6
6
  "publishConfig": {