palbase 0.10.2 → 0.11.1

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 (56) hide show
  1. package/dist/ai/index.js +2 -2
  2. package/dist/backend/engine.js +2 -2
  3. package/dist/backend/env.js +1 -1
  4. package/dist/backend/index.js +11 -11
  5. package/dist/backend/stack.js +1 -1
  6. package/dist/backend/test.js +2 -2
  7. package/dist/chunks/26UTOC32.js +2 -0
  8. package/dist/chunks/{IW34A4TV.js → 3453XVQL.js} +2 -2
  9. package/dist/chunks/{7PBQ3LI2.js → 3VY3ZZCI.js} +1 -1
  10. package/dist/chunks/{MAPLN2FY.js → 43VM626B.js} +1 -1
  11. package/dist/chunks/{BMCC423W.js → 7WIMZTYB.js} +2 -2
  12. package/dist/chunks/{ZOAWYN6L.js → AFFE3Q5O.js} +2 -2
  13. package/dist/chunks/{RYWIWH4U.js → BUAGC2PT.js} +1 -1
  14. package/dist/chunks/{5TVS7F23.js → BUYFMLTF.js} +2 -2
  15. package/dist/chunks/{O5CJJFEU.js → EEWP43KI.js} +1 -1
  16. package/dist/chunks/EWBRYA7P.js +2 -0
  17. package/dist/chunks/{S4F256W2.js → FVLHT7UQ.js} +2 -2
  18. package/dist/chunks/{SCXHU5B7.js → KNPQNBC2.js} +2 -2
  19. package/dist/chunks/{D2UQQWJC.js → KW6ODEYS.js} +1 -1
  20. package/dist/chunks/{5NZGW67E.js → LYEEW4UG.js} +2 -2
  21. package/dist/chunks/{SBVBCENU.js → NGE3D2K4.js} +2 -2
  22. package/dist/chunks/Q3S76MYJ.js +2 -0
  23. package/dist/chunks/{QTAPRS4Q.js → R3EIGCWV.js} +2 -2
  24. package/dist/chunks/{XJEZB4MI.js → S2KRJF6J.js} +2 -2
  25. package/dist/chunks/{6JUQZ4D4.js → V2FEPVVD.js} +2 -2
  26. package/dist/chunks/WLAO2QSR.js +20 -0
  27. package/dist/chunks/{L4SCG6HT.js → ZZG7ICDG.js} +2 -2
  28. package/dist/cli.js +124 -122
  29. package/dist/refuse/ai.js +1 -1
  30. package/dist/refuse/backend.js +1 -1
  31. package/dist/refuse/server.js +1 -1
  32. package/dist/server/index.js +2 -2
  33. package/dist/server/testing.js +2 -2
  34. package/dist/web/index.js +2 -2
  35. package/dist/web/message.js +2 -2
  36. package/dist/web/palbe.js +2 -2
  37. package/dist/web/paywall.js +2 -2
  38. package/dist/web/push-sw.js +1 -1
  39. package/dist/web/ssr.js +2 -2
  40. package/dist/web/vite.js +2 -2
  41. package/package.json +2 -2
  42. package/types/backend/src/database.d.ts +8 -1
  43. package/types/backend/src/engine-db.d.ts +3 -1
  44. package/types/backend/src/engine.d.ts +2 -0
  45. package/types/backend/src/idempotency.d.ts +136 -0
  46. package/types/internal/src/modules/auth.d.ts +25 -8
  47. package/types/internal/src/modules/purchases.d.ts +39 -3
  48. package/types/internal/src/modules/secrets.d.ts +138 -3
  49. package/types/internal/src/modules/stores.d.ts +30 -1
  50. package/types/server/src/fields.d.ts +62 -0
  51. package/types/server/src/index.d.ts +1 -0
  52. package/types/server/src/server.d.ts +8 -0
  53. package/types/server/src/testing.d.ts +8 -0
  54. package/dist/chunks/I2TY2GLY.js +0 -2
  55. package/dist/chunks/PKBCMWTM.js +0 -2
  56. package/dist/chunks/QCKQXIQV.js +0 -20
@@ -39,6 +39,93 @@ export type ErrorResponse = {
39
39
  */
40
40
  status: number;
41
41
  };
42
+ /**
43
+ * A field key with the key.
44
+ */
45
+ export type FieldKey = {
46
+ /**
47
+ * When new values are sealed with it from.
48
+ */
49
+ activates_at: string;
50
+ id: string;
51
+ /**
52
+ * The 32-byte AES-256-GCM key, base64url without padding.
53
+ */
54
+ key: string;
55
+ };
56
+ /**
57
+ * One of the project's field keys, without the key.
58
+ */
59
+ export type FieldKeyInfo = {
60
+ /**
61
+ * When new values are sealed with it from.
62
+ */
63
+ activates_at: string;
64
+ created_at: string;
65
+ /**
66
+ * Who made it: a key's id, a developer's, or the first read's.
67
+ */
68
+ created_by: string;
69
+ /**
70
+ * The key's id (k1, k2, …): what a value it sealed names.
71
+ */
72
+ id: string;
73
+ };
74
+ /**
75
+ * The project's field keys, oldest first.
76
+ */
77
+ export type FieldKeyList = {
78
+ /**
79
+ * The key new values are sealed with now.
80
+ */
81
+ current: string;
82
+ keys: FieldKeyInfo[];
83
+ };
84
+ /**
85
+ * Every field key of the project, oldest first, and its lookup key.
86
+ */
87
+ export type FieldKeys = {
88
+ keys: FieldKey[];
89
+ /**
90
+ * The 32-byte HMAC-SHA256 key a field's lookup is made with, base64url without padding; no rotation changes it.
91
+ */
92
+ lookup: string;
93
+ };
94
+ export type FieldOpen = {
95
+ /**
96
+ * The column: table.column, or schema.table.column outside public.
97
+ */
98
+ address: string;
99
+ /**
100
+ * The column's encrypted values (pbe1.…).
101
+ */
102
+ values: string[];
103
+ };
104
+ export type FieldPlaceholder = {
105
+ /**
106
+ * The column: table.column, or schema.table.column outside public.
107
+ */
108
+ address: string;
109
+ };
110
+ export type FieldSealed = {
111
+ /**
112
+ * The placeholder sealed (pbe1.…) with the key in force.
113
+ */
114
+ value: string;
115
+ };
116
+ export type FieldsOpened = {
117
+ values: OpenedField[];
118
+ };
119
+ export type OpenedField = {
120
+ /**
121
+ * Why it did not open.
122
+ */
123
+ error?: "unopenable";
124
+ /**
125
+ * The field's value; null when it did not open.
126
+ */
127
+ value: string | null;
128
+ };
42
129
  /**
43
130
  * A secret, without its value.
44
131
  */
@@ -59,19 +146,22 @@ export type Secret = {
59
146
  * One read or write of the project's secrets.
60
147
  */
61
148
  export type SecretAuditEntry = {
62
- action: "list" | "read" | "read_all" | "write" | "rollback" | "delete" | "versions";
149
+ action: "list" | "read" | "read_all" | "write" | "rollback" | "delete" | "versions" | "field_keys_list" | "field_keys_read" | "field_keys_rotate" | "field_open" | "field_placeholder";
63
150
  /**
64
151
  * When it happened.
65
152
  */
66
153
  at: string;
67
- environment: "development" | "production";
154
+ /**
155
+ * The environment, for a secret's action; absent for the field keys'.
156
+ */
157
+ environment?: "development" | "production";
68
158
  id: string;
69
159
  /**
70
160
  * The id of the key that made the request.
71
161
  */
72
162
  key_id: string;
73
163
  /**
74
- * The secret, when the action names one.
164
+ * The secret, when the action names one; the field key made, for a rotation; the column, for a field opened or a placeholder sealed.
75
165
  */
76
166
  name?: string;
77
167
  request_id: string;
@@ -170,6 +260,51 @@ export declare function client(t: Transport): {
170
260
  }; /** Stops the call: the request in flight, a wait before a retry, a proof-of-work being solved. */
171
261
  signal?: AbortSignal;
172
262
  }): Promise<Reply<200, SecretAuditPage>>;
263
+ /**
264
+ * List the field keys
265
+ *
266
+ * The project's field keys without the keys: their ids, when each came (or comes) into force, who made it, and the one new values are sealed with now. The project's first key is made on first use. Recorded in the audit trail.
267
+ */
268
+ listFieldKeys(input?: {
269
+ signal?: AbortSignal;
270
+ }): Promise<Reply<200, FieldKeyList>>;
271
+ /**
272
+ * Read the field keys
273
+ *
274
+ * Every field key of the project with the key, and its lookup key: what its backend reads at startup and every minute after (palbase/server's `fields`). A developer reads them only as the project's owner — to open a dump of the database elsewhere. The read is recorded in the audit trail.
275
+ */
276
+ getFieldKeys(input?: {
277
+ signal?: AbortSignal;
278
+ }): Promise<Reply<200, FieldKeys>>;
279
+ /**
280
+ * Open encrypted fields
281
+ *
282
+ * Opens encrypted fields of one column for a developer looking at them in Studio, as the project's owner or an admin; the keys stay in the module. A value that does not open there — tampered, from another column, not encrypted — is answered `unopenable`. Every opening is recorded in the audit trail, with the column.
283
+ */
284
+ openFields(input: {
285
+ body: FieldOpen; /** Stops the call: the request in flight, a wait before a retry, a proof-of-work being solved. */
286
+ signal?: AbortSignal;
287
+ }): Promise<Reply<200, FieldsOpened>>;
288
+ /**
289
+ * Seal the placeholder
290
+ *
291
+ * Seals the placeholder (`•••`) at a column's address with the key in force: what a copy of another environment's data (`palbase db:copy`) fills its encrypted columns with — never that environment's values, whose keys are not this project's. The value is fixed: no caller has a value of its choice sealed here, which the project's backend would take as its own. Recorded in the audit trail, with the column.
292
+ */
293
+ sealPlaceholder(input: {
294
+ body: FieldPlaceholder; /** Stops the call: the request in flight, a wait before a retry, a proof-of-work being solved. */
295
+ signal?: AbortSignal;
296
+ }): Promise<Reply<200, FieldSealed>>;
297
+ /**
298
+ * Rotate the field keys
299
+ *
300
+ * Makes a new field key, in force five minutes from now: by then every running backend has fetched it, so none meets a value it cannot open. New values are sealed with it from then on; the older keys go on opening what they sealed until a deploy seals their fields again. A developer rotates as the project's owner or an admin. One rotation at a time: a second before the first is in force is refused (409). Recorded in the audit trail.
301
+ */
302
+ rotateFieldKeys(input?: {
303
+ headers?: {
304
+ "Idempotency-Key"?: string;
305
+ }; /** Stops the call: the request in flight, a wait before a retry, a proof-of-work being solved. */
306
+ signal?: AbortSignal;
307
+ }): Promise<Reply<200, FieldKeyList>>;
173
308
  /**
174
309
  * List an environment's secrets
175
310
  *
@@ -289,6 +289,10 @@ export type AppStoreVersion = {
289
289
  */
290
290
  missing: string[];
291
291
  watched?: WatchedVersion;
292
+ /**
293
+ * When a person said the app's privacy details were published in App Store Connect (app_privacy: published).
294
+ */
295
+ app_privacy_published_at?: string;
292
296
  };
293
297
  /**
294
298
  * The App Store version's own fields.
@@ -364,6 +368,14 @@ export type AppStoreVersionRequest = {
364
368
  */
365
369
  price?: "free" | null;
366
370
  review?: AppStoreReviewRequest;
371
+ /**
372
+ * all: an app sold in no country or region is sold in every one, and in those Apple opens later; one sold somewhere keeps its countries (they are set in App Store Connect).
373
+ */
374
+ territories?: "all";
375
+ /**
376
+ * App Privacy has no API: published says the app's privacy details were published in App Store Connect, and the missing list stops asking for them; unpublished takes it back. Apple refusing a submission for App Privacy takes it back too.
377
+ */
378
+ app_privacy?: "published" | "unpublished";
367
379
  };
368
380
  /**
369
381
  * The project's Apple account (App Store Connect API key) as the API shows it: never the key itself.
@@ -1197,6 +1209,10 @@ export type PackageBundle = {
1197
1209
  * Its path in the app (empty for the app itself).
1198
1210
  */
1199
1211
  path: string;
1212
+ /**
1213
+ * The app's UIDeviceFamily: 1 the iPhone, 2 the iPad (App Review then asks for iPad screenshots).
1214
+ */
1215
+ device_families?: number[];
1200
1216
  };
1201
1217
  /**
1202
1218
  * stores.package.changed: a package was made, or its build arrived.
@@ -1553,6 +1569,15 @@ export type Screenshot = {
1553
1569
  * The PNG or JPEG image, 10 MB at most.
1554
1570
  */
1555
1571
  export type ScreenshotFile = Blob;
1572
+ /**
1573
+ * What goes to App Review with the version: its in-app purchases, by product id — a product's first review goes with an app version, as Apple requires. Each is READY_TO_SUBMIT at Apple, or the plan is refused (in_app_purchase_not_ready).
1574
+ */
1575
+ export type SubmitRequest = {
1576
+ /**
1577
+ * The product ids of the subscriptions and in-app purchases sent with the version (50 at most).
1578
+ */
1579
+ in_app_purchases?: string[];
1580
+ };
1556
1581
  /**
1557
1582
  * A person to have in a group.
1558
1583
  */
@@ -2018,6 +2043,9 @@ export declare function client(t: Transport): {
2018
2043
  path: {
2019
2044
  app_id: string;
2020
2045
  };
2046
+ query?: {
2047
+ for?: "page" | "review";
2048
+ };
2021
2049
  headers?: {
2022
2050
  "Idempotency-Key"?: string;
2023
2051
  };
@@ -2131,7 +2159,8 @@ export declare function client(t: Transport): {
2131
2159
  };
2132
2160
  headers?: {
2133
2161
  "Idempotency-Key"?: string;
2134
- }; /** Stops the call: the request in flight, a wait before a retry, a proof-of-work being solved. */
2162
+ };
2163
+ body?: SubmitRequest; /** Stops the call: the request in flight, a wait before a retry, a proof-of-work being solved. */
2135
2164
  signal?: AbortSignal;
2136
2165
  }): Promise<Reply<201, Action>>;
2137
2166
  /**
@@ -0,0 +1,62 @@
1
+ /** What the secrets module answers GET /secrets/fields/keys with. */
2
+ export interface FieldKeySet {
3
+ keys: Array<{
4
+ id: string;
5
+ key: string;
6
+ activates_at: string;
7
+ }>;
8
+ lookup: string;
9
+ }
10
+ /** A field that is not an encrypted one, or does not open here. */
11
+ export declare class FieldError extends Error {
12
+ /** `not_loaded`, `not_encrypted` (a plain value where an encrypted one belongs), `unknown_key` or `unopenable` */
13
+ readonly code: 'not_loaded' | 'not_encrypted' | 'unknown_key' | 'unopenable';
14
+ constructor(code: FieldError['code'], message: string);
15
+ }
16
+ export declare const FIELD_PREFIX = "pbe1.";
17
+ export declare const LOOKUP_PREFIX = "pbl1.";
18
+ /** How often the keys are fetched again: well inside a rotation's five minutes. */
19
+ export declare const REFRESH_MS = 60000;
20
+ /** Whether address names a field: table.column, or schema.table.column. */
21
+ export declare function validAddress(address: string): boolean;
22
+ export interface FieldsOptions {
23
+ /** Reads the project's keys (the secrets module's GetFieldKeys). */
24
+ fetch: (signal?: AbortSignal) => Promise<FieldKeySet>;
25
+ /** The clock a key's coming into force is read by (tests). */
26
+ now?: () => number;
27
+ }
28
+ /**
29
+ * The project's encrypted fields. `await load()` once at startup; after it,
30
+ * encrypt, decrypt and lookup are synchronous — what a column transform runs
31
+ * inside a query.
32
+ */
33
+ export declare class Fields {
34
+ private crypto;
35
+ private keys;
36
+ private lookupKey;
37
+ private timer;
38
+ private readonly opts;
39
+ private readonly now;
40
+ constructor(opts: FieldsOptions);
41
+ /** Whether the keys are loaded. */
42
+ get loaded(): boolean;
43
+ /**
44
+ * Reads the keys, and keeps reading them every minute until close(). A
45
+ * failed refresh keeps the keys held: the next one tries again.
46
+ */
47
+ load(signal?: AbortSignal): Promise<void>;
48
+ /** Stops the refresh. */
49
+ close(): void;
50
+ private take;
51
+ private ready;
52
+ /** The id of the key new values are sealed with now: the newest in force. */
53
+ currentKey(): string;
54
+ /** Encrypts value as the field at address. */
55
+ encrypt(address: string, value: string): string;
56
+ /** Opens the field at address. A plain value, another column's, a tampered one or one sealed with a key not held is refused (FieldError). */
57
+ decrypt(address: string, value: string): string;
58
+ /** Whether value is an encrypted field (sealed with any key), without opening it. */
59
+ isEncrypted(value: string): boolean;
60
+ /** What the field at address holding value is found by. */
61
+ lookup(address: string, value: string): string;
62
+ }
@@ -1,4 +1,5 @@
1
1
  export { createServer, type PalbaseServer, type ServerOptions } from './server.js';
2
+ export { FieldError, Fields, type FieldKeySet } from './fields.js';
2
3
  export { EVENT_TYPES, signatureHeader, verifySignature, verifyWebhook, verifyWebhookRequest, WebhookSignatureError, type EventType, type PalbaseEvent, type PlatformEvent, type PlatformEvents, type SignatureOptions, type VerifyOptions, type WebhookEvent, type WebhookRefusal, } from './webhooks.js';
3
4
  export { AuthUnavailable, requestAddress, sessionStanding, TokenError, TokenVerifier, type ProofContext, type RequestLike, type SessionStanding, type TokenRefusal, type TokenUser, type VerifierOptions, } from './auth/jwt.js';
4
5
  export { isEntitled, ReservationSettled, withReservation, type CustomerInfo, type Reservation, type SpendReceipt } from './purchases.js';
@@ -1,6 +1,7 @@
1
1
  import { type Fetch, type Transport } from '../../internal/src/transport.js';
2
2
  import { type RetryPolicy } from '../../internal/src/retry.js';
3
3
  import * as modules from '../../internal/src/modules/index.js';
4
+ import { Fields } from './fields.js';
4
5
  export interface ServerOptions {
5
6
  /** The project's address: https://<ref>.<domain> */
6
7
  url: string;
@@ -24,6 +25,13 @@ export interface PalbaseServer {
24
25
  auth: ReturnType<typeof modules.auth.client>;
25
26
  /** The project's cache entries; palbase/backend's Cache is the way to use them. */
26
27
  cache: ReturnType<typeof modules.cache.client>;
28
+ /**
29
+ * The project's encrypted database fields (fields.ts): `await
30
+ * palbase.fields.load()` at startup, then encrypt, decrypt and lookup —
31
+ * what palgres's text().encrypted() runs on, and what a server with
32
+ * another way to its database calls around a column itself.
33
+ */
34
+ fields: Fields;
27
35
  flags: ReturnType<typeof modules.flags.client>;
28
36
  machines: ReturnType<typeof modules.machines.client>;
29
37
  notify: ReturnType<typeof modules.notify.client>;
@@ -1,4 +1,12 @@
1
1
  import type { Fetch } from '../../internal/src/transport.js';
2
+ import { Fields } from './fields.js';
3
+ /**
4
+ * Field keys of no project — random, held in this process, loaded — for a
5
+ * test that seals and opens .encrypted() columns against a database of its
6
+ * own (palbase/backend/engine's createRequestDatabase, palgres'
7
+ * createDatabase): no call to the project's secrets.
8
+ */
9
+ export declare function testFields(): Promise<Fields>;
2
10
  export interface TestApiConfig {
3
11
  /** The app's address: where its routes are. */
4
12
  baseUrl: string;
@@ -1,2 +0,0 @@
1
- /*! palbase 0.10.2 — Palbase SDK License (see LICENSE) */
2
- import{a as S,b as k}from"./RYWIWH4U.js";var T=class extends Error{status;error;data;body;constructor(t,a,l,d){let u=d!==null&&typeof d=="object"?d:{},i=u.error??String(l);super(`${t} ${a} \u2192 ${l} ${i}${u.error_description?`: ${u.error_description}`:""}`),this.name="TestApiError",this.status=l,this.error=i,this.data=u.data,this.body=u}};function I(n,t){if(!n)throw new Error(`${t} is not set: the test client has nowhere to send its requests \u2014 \`palbase test\` sets it, as the platform's test run does`);return n}function R(n){let t=n.split(".")[1];if(!t)return null;try{let a=JSON.parse(Buffer.from(t,"base64url").toString("utf8"));return typeof a.exp=="number"?a.exp-Math.floor(Date.now()/1e3):null}catch{return null}}function x(n){try{return JSON.parse(n)}catch{return n}}function L(n){let t=I(n.baseUrl,"PALBASE_TEST_BASE_URL").replace(/\/+$/,""),a=n.apiKey||void 0,l=(n.authUrl??t).replace(/\/+$/,""),d=n.fetch??fetch,u=[],i=null;async function c(r,e,s,o,f={},_=!0){let g={...f.headers},m=h=>Object.keys(g).some(O=>O.toLowerCase()===h);a!==void 0&&!m("apikey")&&(g.apikey=a),i&&_&&!m("authorization")&&(g.authorization=`Bearer ${i}`),o!==void 0&&!m("content-type")&&(g["content-type"]="application/json");let P=Date.now(),p=await d(`${r}${s}`,{method:e,headers:g,body:o===void 0?void 0:JSON.stringify(o)}),E=await p.text(),A=E?x(E):void 0;if(u.push({method:e,path:s,status:p.status,ms:Date.now()-P}),!p.ok){let h=p.status===401&&i?R(i):null;throw h!==null&&h<=0?new T(e,s,p.status,{error:"access_token_expired",error_description:`this run's access token expired ${-h}s ago: a test user's token is made for one run \u2014 run the tests again (\`palbase test\` makes new ones)`}):new T(e,s,p.status,A)}return A}let y={requests:u,get:(r,e)=>c(t,"GET",r,void 0,e),post:(r,e,s)=>c(t,"POST",r,e,s),patch:(r,e,s)=>c(t,"PATCH",r,e,s),put:(r,e,s)=>c(t,"PUT",r,e,s),delete:(r,e)=>c(t,"DELETE",r,void 0,e),query:(r,e,s)=>c(t,"QUERY",r,e,s),async signInAs(r){let e=n.identities?.[r];if(!e){let s=Object.keys(n.identities??{});throw s.length===0?new Error(`no test user named ${JSON.stringify(r)}: this run has no test users \u2014 none were named (the machine's test users, or \`palbase test --users <names>\`), or they could not be made: the run's log says which`):new Error(`no test user named ${JSON.stringify(r)} \u2014 this run has: ${s.join(", ")}. A run makes the test users its machine names (palbase machines:update <machine> --test-users <names>) or \`palbase test --users <names>\` names`)}return e.accessToken?(i=e.accessToken,{id:e.id??"",email:e.email}):y.signIn(e)},async signIn(r){if(a===void 0)throw new Error("PALBASE_TEST_API_KEY is not set: a sign-in at the project's auth is made with a publishable key of the project's (palbase apps:sdkconfig shows one) \u2014 a run's test users come with their tokens: api.signInAs(name)");let e=o=>c(l,"POST","/auth/login",r,o?{headers:o}:{},!1),s;try{s=await e()}catch(o){let f=o instanceof T&&o.status===403?S(o.body):null;if(!f)throw o;s=await e(await k(f))}return i=s.access_token,s.user??{id:""}},async signOut(){await c(l,"POST","/auth/logout",void 0),i=null},asAnonymous(){i=null}};return y}function b(n){if(!n)return{};let t;try{t=JSON.parse(n)}catch{throw new Error("PALBASE_TEST_IDENTITIES is set but is not JSON: the run that started these tests handed over a broken set of test users")}if(t===null||typeof t!="object"||Array.isArray(t))throw new Error("PALBASE_TEST_IDENTITIES is not an object of test users by name");return t}var $={get stack(){return!!process.env.PALBASE_TEST_BASE_URL},minted(n){return Object.prototype.hasOwnProperty.call(b(process.env.PALBASE_TEST_IDENTITIES),n)}},w,U=new Proxy({},{get(n,t){return w??=L({baseUrl:process.env.PALBASE_TEST_BASE_URL??"",apiKey:process.env.PALBASE_TEST_API_KEY??"",authUrl:process.env.PALBASE_TEST_AUTH_URL,identities:b(process.env.PALBASE_TEST_IDENTITIES)}),Reflect.get(w,t,w)}});export{T as a,L as b,$ as c,U as d};
@@ -1,2 +0,0 @@
1
- /*! palbase 0.10.2 — Palbase SDK License (see LICENSE) */
2
- import{a as D}from"./O5CJJFEU.js";import{a as A,b as P,c as R,d as x,e as O,f as j,g as M,h as C,i as L,j as U,k as K,l as N}from"./BMCC423W.js";import{a as $,b as I}from"./QTAPRS4Q.js";import{b as S,d as w}from"./SCXHU5B7.js";var ae=/^pb_[a-z0-9]+_s/;function v(n){if(D())throw new TypeError("palbase/server runs on a server: its secret key never goes into a browser or a mobile app \u2014 apps use palbase/web (or the native SDKs) with the publishable key");if(typeof n.secretKey!="string"||!ae.test(n.secretKey))throw new TypeError("secretKey is the project secret key (pb_<ref>_s\u2026); a publishable key belongs in palbase/web");let e=I(w({baseUrl:n.url,credentials:{secretKey:n.secretKey},fetch:n.fetch,solveProofOfWork:n.solveProofOfWork??!1}),n.retry??$);return{ai:A.client(e),analytics:P.client(e),auth:R.client(e),cache:x.client(e),flags:O.client(e),machines:j.client(e),notify:M.client(e),purchases:C.client(e),realtime:L.client(e),secrets:U.client(e),storage:K.client(e),stores:N.client(e),transport:e,url:new URL(n.url).origin}}var f=class extends Error{code;constructor(e,t){super(t),this.name="WebhookSignatureError",this.code=e}},ue=300,de=/^[0-9]{1,12}$/,le=/^[0-9a-f]{64}$/,W=8,fe=/^whsec_[0-9A-Za-z]{32,}$/,he=1024*1024;async function B(n){if(typeof n.secret!="string"||!fe.test(n.secret))throw new TypeError("secret is the webhook endpoint secret (whsec_\u2026), not an API key");await F(n);let e;try{e=JSON.parse(new TextDecoder("utf-8",{fatal:!0}).decode(z(n.body)))}catch{throw new f("invalid_body","the signed body is not JSON")}if(!be(e))throw new f("invalid_body","the signed body is not a Palbase event (id, type, project_id, created_at, data)");return e}async function F(n){if(typeof n.secret!="string"||n.secret==="")throw new TypeError("secret is the key the request was signed with");let e=z(n.body),{t,v1:r}=H(n),s=await crypto.subtle.importKey("raw",new TextEncoder().encode(n.secret),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),o=new Uint8Array(await crypto.subtle.sign("HMAC",s,we(new TextEncoder().encode(`${t}.`),e))),a=!1;for(let u of r)me(o,ge(u))&&(a=!0);if(!a)throw new f("signature_mismatch","no v1 in Palbase-Signature is this body signed with this secret")}function H(n){let e=n.toleranceSeconds??ue;if(!Number.isFinite(e)||e<0)throw new TypeError(`toleranceSeconds is a number of seconds, 0 or more (got ${String(n.toleranceSeconds)})`);let t=(n.now??Date.now)();if(!Number.isFinite(t))throw new TypeError(`now() returns epoch milliseconds (got ${String(t)})`);let r=n.signature;if(typeof r!="string"||r.trim()==="")throw new f("missing_signature","the request carries no Palbase-Signature header");let s,o=[];for(let a of r.split(",")){let u=a.indexOf("=");if(u<=0)throw new f("malformed_signature","Palbase-Signature is t=<seconds>,v1=<hex>");let c=a.slice(0,u).trim(),i=a.slice(u+1).trim();if(c==="t"){if(s!==void 0||!de.test(i))throw new f("malformed_signature","Palbase-Signature carries one t, in unix seconds");s=i}else if(c==="v1"){if(!le.test(i))throw new f("malformed_signature","a v1 is 64 lowercase hex characters");if(o.length===W)throw new f("malformed_signature",`Palbase-Signature carries at most ${W} v1`);o.push(i)}}if(s===void 0||o.length===0)throw new f("malformed_signature","Palbase-Signature is t=<seconds>,v1=<hex>");if(Math.abs(t/1e3-Number(s))>e)throw new f("stale_timestamp",`the signature's time is more than ${e} s from now: an old delivery sent again is not taken`);return{t:s,v1:o}}async function pe(n,e,t={}){let r=await ye(n,t.maxBytes??he);return B({toleranceSeconds:t.toleranceSeconds,now:t.now,secret:e,signature:n.headers.get("palbase-signature"),body:r})}async function ye(n,e){let t=()=>new f("body_too_large",`the body is larger than ${e} bytes`),r=Number(n.headers.get("content-length")??"NaN");if(Number.isFinite(r)&&r>e)throw t();if(!n.body)return new Uint8Array(0);let s=n.body.getReader(),o=[],a=0;for(;;){let{done:i,value:l}=await s.read();if(i)break;if(a+=l.byteLength,a>e)throw await s.cancel().catch(()=>{}),t();o.push(l)}let u=new Uint8Array(a),c=0;for(let i of o)u.set(i,c),c+=i.byteLength;return u}function z(n){if(typeof n=="string")return new TextEncoder().encode(n);if(n instanceof Uint8Array)return n;if(n instanceof ArrayBuffer)return new Uint8Array(n);throw new TypeError("body is the raw request body (string or bytes): a parsed object is not what was signed \u2014 read the body before any JSON middleware")}function ge(n){let e=new Uint8Array(n.length/2);for(let t=0;t<e.length;t++)e[t]=parseInt(n.slice(t*2,t*2+2),16);return e}function me(n,e){if(n.byteLength!==e.byteLength)return!1;let t=0;for(let r=0;r<n.byteLength;r++)t|=n[r]^e[r];return t===0}function we(n,e){let t=new Uint8Array(n.byteLength+e.byteLength);return t.set(n,0),t.set(e,n.byteLength),t}function be(n){if(typeof n!="object"||n===null)return!1;let e=n;return typeof e.id=="string"&&typeof e.type=="string"&&typeof e.project_id=="string"&&typeof e.created_at=="string"&&"data"in e}var V=60,ke=60,Se=8*1024,ve=1e5;function E(n){let e=n instanceof Uint8Array?n:new Uint8Array(n),t="";for(let r of e)t+=String.fromCharCode(r);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function Y(n){let e=n.replace(/[\s=]+/g,"");if(!/^[A-Za-z0-9+/_-]*$/.test(e)||e.length%4===1)return;let t=atob(e.replace(/-/g,"+").replace(/_/g,"/").padEnd(Math.ceil(e.length/4)*4,"=")),r=new Uint8Array(t.length);for(let s=0;s<t.length;s++)r[s]=t.charCodeAt(s);return r}function J(n){let e=Y(n);if(e)try{let t=JSON.parse(new TextDecoder().decode(e));return typeof t=="object"&&t!==null&&!Array.isArray(t)?t:void 0}catch{return}}var b=class{byKey=new Map;held=0;sweptAt=Number.NEGATIVE_INFINITY;max;constructor(e=ve){this.max=e}spend(e,t){if(t>=e.until)throw new d("proof","the DPoP proof is not taken: it was checked too long ago");let r=this.byKey.get(e.jkt);if(r){this.forgetOld(e.jkt,r,t);let s=r.get(e.jti);if(s!==void 0&&s>t)throw new d("proof","the DPoP proof is not taken: it was used before");s!==void 0&&r.delete(e.jti)&&this.held--}this.held>=this.max&&this.makeRoom(t),r=this.byKey.get(e.jkt),r||(r=new Map,this.byKey.set(e.jkt,r)),r.set(e.jti,e.until),this.held++}forgetOld(e,t,r){for(let[s,o]of t){if(o>r)break;t.delete(s),this.held--}t.size===0&&this.byKey.delete(e)}makeRoom(e){if(e-this.sweptAt>=1e3){this.sweptAt=e;for(let[r,s]of this.byKey){for(let[o,a]of s)a>e||(s.delete(o),this.held--);s.size===0&&this.byKey.delete(r)}this.held=0;for(let r of this.byKey.values())this.held+=r.size}if(this.held<this.max)return;let t=[...this.byKey].sort((r,s)=>s[1].size-r[1].size);for(let[r,s]of t){if(this.held<=this.max*.9)break;this.byKey.delete(r),this.held-=s.size}}};async function Ee(n,e){let t=`{"crv":"P-256","kty":"EC","x":"${n}","y":"${e}"}`;return E(await crypto.subtle.digest("SHA-256",new TextEncoder().encode(t)))}function q(n){try{let e=new URL(n);return e.username!==""||e.password!==""?void 0:`${e.protocol}//${e.host}${e.pathname}`}catch{return}}async function G(n,e,t,r){let s=ie=>{throw new d("proof",`the DPoP proof is not taken: ${ie}`)},o=t.proof;if(!o)return s("there is none (a DPoP-bound token comes with a DPoP header)");if(o.length>Se)return s("it is too long");let a=o.split(".");if(a.length!==3)return s("it is not a JWS of three parts");let u=J(a[0]),c=J(a[1]);if(!u||!c)return s("its header or claims are not base64url JSON");if(u.typ!=="dpop+jwt")return s("its typ is not dpop+jwt");if(u.alg!=="ES256")return s(`it is signed with ${String(u.alg)}; only ES256 is taken`);let i=u.jwk;if(!i||typeof i!="object"||i.kty!=="EC"||i.crv!=="P-256"||typeof i.x!="string"||typeof i.y!="string")return s("its header carries no P-256 public key");if("d"in i)return s("its header carries a private key");if(await Ee(i.x,i.y)!==e)return s("it is signed by a key the token does not name");let l;try{l=await crypto.subtle.importKey("jwk",{kty:"EC",crv:"P-256",x:i.x,y:i.y,ext:!0},{name:"ECDSA",namedCurve:"P-256"},!1,["verify"])}catch{return s("its key is not a P-256 point")}let p=Y(a[2]);if(!p||!await crypto.subtle.verify({name:"ECDSA",hash:"SHA-256"},l,p,new TextEncoder().encode(`${a[0]}.${a[1]}`)).catch(()=>!1))return s("its signature does not verify");if(typeof c.htm!="string"||c.htm.toUpperCase()!==t.method.toUpperCase())return s(`it is for ${String(c.htm)}, not ${t.method}`);let h=typeof c.htu=="string"?q(c.htu):void 0,y=q(t.url);if(!h||!y||h!==y)return s(`it is for ${String(c.htu)}, not ${y??t.url}`);let re=r/1e3;if(typeof c.iat!="number"||Math.abs(c.iat-re)>V)return s("it was not made within a minute of now");let se=E(await crypto.subtle.digest("SHA-256",new TextEncoder().encode(n)));if(c.ath!==se)return s("it is not for this token (ath)");if(typeof c.jti!="string"||c.jti===""||c.jti.length>256)return s("it has no jti (or one longer than 256)");let oe=E(await crypto.subtle.digest("SHA-256",new TextEncoder().encode(c.jti)));return{jkt:e,jti:oe,until:(c.iat+V+ke)*1e3+1}}var d=class extends Error{reason;constructor(e,t){super(t),this.name="TokenError",this.reason=e}},g=class extends Error{constructor(e,t){super(e,t),this.name="AuthUnavailable"}},_e=new Set(["invalid_api_key","missing_api_key","secret_key_in_browser"]);function Te(n){return async e=>{let t=w({baseUrl:n.url,credentials:{secretKey:n.secretKey,accessToken:()=>e},fetch:n.fetch});try{return await t.send({op:"SessionStanding",method:"GET",path:"/auth/session/standing",auth:[["ApiKeyAuth","userJWT"]],answers:{204:"empty"}}),!0}catch(r){if(r instanceof S&&r.status===401&&!_e.has(r.code))return!1;throw new g("a session's standing could not be told",{cause:r})}}}var _={ES256:{kty:"EC",import:{name:"ECDSA",namedCurve:"P-256"},verify:{name:"ECDSA",hash:"SHA-256"}},PS256:{kty:"RSA",import:{name:"RSA-PSS",hash:"SHA-256"},verify:{name:"RSA-PSS",saltLength:32}}},X=60,Ae=16*1024,Pe=10*6e4,Re=5e4,Z=n=>typeof n=="string"&&Object.hasOwn(_,n),T=class{issuer;projectId;fetchImpl;now;maxKeyAgeMs;refetchIntervalMs;fetchTimeoutMs;standing;standingTtlMs;standingStaleMs;standings=new Map;asking=new Map;keys=new Map;keysUntil=0;lastFetch=Number.NEGATIVE_INFINITY;lastFailure=void 0;inflight=null;seenProofs=new b;constructor(e){let t=new URL(e.issuer);if(t.origin!==e.issuer.replace(/\/$/,""))throw new TypeError(`issuer is the project's origin (https://<ref>.<domain>), not ${e.issuer}`);if(typeof e.projectId!="string"||e.projectId==="")throw new TypeError("projectId is the project id the tokens carry");if(this.issuer=t.origin,this.projectId=e.projectId,this.fetchImpl=e.fetch??globalThis.fetch,this.now=e.now??Date.now,this.maxKeyAgeMs=e.maxKeyAgeMs??60*6e4,this.refetchIntervalMs=e.refetchIntervalMs??3e4,this.fetchTimeoutMs=e.fetchTimeoutMs??5e3,typeof e.standing!="function")throw new TypeError("standing is how the verifier asks whether a token's session stands");this.standing=e.standing,this.standingTtlMs=e.standingTtlMs??1e3,this.standingStaleMs=e.standingStaleMs??15e3}async verify(e,t){if(typeof e!="string"||e.length===0||e.length>Ae)throw new d("malformed","the token is not a JWT");let r=e.split(".");if(r.length!==3)throw new d("malformed","the token is not a JWS of three parts");let s=Q(r[0]),o=Q(r[1]);if(!s||!o)throw new d("malformed","the token header or claims are not base64url JSON");let a=s.alg;if(!Z(a))throw new d("algorithm",`the token is signed with ${String(a)}; only ES256 and PS256 are taken`);let u=s.kid;if(typeof u!="string"||u==="")throw new d("malformed","the token header names no kid");let c=await this.key(u);if(!c)throw new d("unknown_key",`no key ${u} in the project's key set`);if(c.alg!==a)throw new d("algorithm",`key ${u} is ${c.alg}, the token says ${a}`);let i=te(r[2]);if(!i)throw new d("malformed","the signature is not base64url");let l=new TextEncoder().encode(`${r[0]}.${r[1]}`);if(!await crypto.subtle.verify(_[c.alg].verify,c.key,i,l).catch(()=>!1))throw new d("signature","the signature does not verify");let m=this.check(o),h=o.cnf,y;if(h!==void 0){if(!t)throw new d("bound","the token is bound to a key (DPoP): it comes with the DPoP scheme and a proof, not as a Bearer");if(typeof h!="object"||h===null||typeof h.jkt!="string"||h.jkt==="")throw new d("bound","the token is bound to a key it does not name (cnf.jkt)");y=await G(e,h.jkt,t,this.now())}else if(t)throw new d("not_bound","the DPoP scheme came with a token bound to no key");if(!await this.stands(e,m.sessionId))throw new d("session_ended","the token's session has ended (signed out, revoked, or its user banned)");return y&&this.seenProofs.spend(y,this.now()),m}async verifyRequest(e){let t=e.headers instanceof Headers?e.headers:Oe(e.headers),r=t.get("authorization")??"";if(r==="")return null;let s=/^(bearer|dpop)\s+(\S+)$/i.exec(r);if(!s)throw new d("malformed","the Authorization header is not a Bearer or DPoP token");let o=s[1].toLowerCase()==="dpop",a=new URL(e.url,`http://${t.get("host")??"localhost"}`);return this.verify(s[2],o?{proof:t.get("dpop"),method:e.method,url:ee(t,a)}:void 0)}async stands(e,t){let r=this.now(),s=this.standings.get(t);if(s&&r<s.until)return s.live;let o=this.asking.get(t);o||(o=this.standing(e,t).finally(()=>this.asking.delete(t)),this.asking.set(t,o));let a;try{a=await o}catch(c){let i=this.standings.get(t),l=this.now();if(i&&l<i.at+this.standingStaleMs)return i.until=Math.min(l+this.standingTtlMs,i.at+this.standingStaleMs),i.live;throw new g("a session's standing cannot be read now",{cause:c})}let u=this.now();return this.standings.delete(t),this.standings.set(t,{live:a,at:u,until:u+this.standingTtlMs}),this.standings.size>Re&&this.standings.delete(this.standings.keys().next().value),a}check(e){if(e.iss!==this.issuer)throw new d("issuer",`the token was issued by ${String(e.iss)}, not this project`);if(!(Array.isArray(e.aud)?e.aud:[e.aud]).includes(this.issuer))throw new d("audience","the token is not meant for this project");if(e.project_id!==this.projectId)throw new d("project","the token names another project");let r=this.now()/1e3;if(typeof e.exp!="number"||e.exp+X<=r)throw new d("expired","the token has expired");if(e.nbf!==void 0&&(typeof e.nbf!="number"||e.nbf-X>r))throw new d("not_yet_valid","the token is not valid yet");if(typeof e.sub!="string"||e.sub===""||typeof e.auth_time!="number")throw new d("not_a_user","the token is not a user's");if(typeof e.session_id!="string"||e.session_id==="")throw new d("not_a_session","the token is no session's");let s=e.custom_claims;if(s!==void 0&&(typeof s!="object"||s===null||s.type!==void 0))throw new d("not_a_session","the token is a custom one (an exchange or an approval), not a session's");return{id:e.sub,email:typeof e.email=="string"?e.email:null,emailVerified:e.email_verified===!0,role:typeof e.role=="string"?e.role:null,sessionId:typeof e.session_id=="string"?e.session_id:null,claims:Object.freeze({...e})}}async key(e){let t=this.now();if(t>=this.keysUntil){let s=this.keys.size>0&&t<this.keysUntil+Pe;if(this.lastFailure!==void 0&&t-this.lastFetch<this.refetchIntervalMs){if(!s)throw new g("the project's key set cannot be read now",{cause:this.lastFailure})}else try{await this.refresh()}catch(o){if(!s)throw o}}let r=this.keys.get(e);return!r&&t-this.lastFetch>=this.refetchIntervalMs&&(await this.refresh(),r=this.keys.get(e)),r}refresh(){if(this.inflight)return this.inflight;let e=this.load().finally(()=>{this.inflight=null});return this.inflight=e,e}async load(){this.lastFetch=this.now();let e,t;try{if(t=await this.fetchImpl(`${this.issuer}/.well-known/jwks.json`,{redirect:"error",headers:{accept:"application/json"},signal:AbortSignal.timeout(this.fetchTimeoutMs)}),!t.ok)throw new Error(`the project's key set answered ${t.status}`);e=await t.json()}catch(o){throw this.lastFailure=o,new g("the project's key set cannot be read now",{cause:o})}this.lastFailure=void 0;let r=new Map;for(let o of Array.isArray(e.keys)?e.keys:[]){let a=o.alg;if(typeof o.kid!="string"||!Z(a))continue;let u=_[a];if(o.kty!==u.kty||o.use!==void 0&&o.use!=="sig")continue;let{d:c,...i}=o;try{r.set(o.kid,{alg:a,key:await crypto.subtle.importKey("jwk",i,u.import,!1,["verify"])})}catch{}}this.keys=r;let s=je(t.headers.get("cache-control"))??this.maxKeyAgeMs;this.keysUntil=this.now()+Math.min(Math.max(s,this.refetchIntervalMs),this.maxKeyAgeMs)}};function ee(n,e){let t=n.get("x-forwarded-proto")?.split(",")[0]?.trim().toLowerCase(),r=t==="https"||t==="http"?t:e.protocol.slice(0,-1),s=n.get("host")??e.host;return xe.test(s)?`${r}://${s}${e.pathname}`:""}var xe=/^(?:[a-z0-9-]+(?:\.[a-z0-9-]+)*|\[[0-9a-f:.]+\])(?::[0-9]{1,5})?$/i;function Oe(n){let e=new Headers;for(let[t,r]of Object.entries(n))if(r!==void 0)for(let s of Array.isArray(r)?r:[r])e.append(t,s);return e}function je(n){let e=/(?:^|,)\s*max-age=(\d+)/i.exec(n??"");return e?Number(e[1])*1e3:void 0}function te(n){if(!/^[A-Za-z0-9_-]*$/.test(n)||n.length%4===1)return null;let e=atob(n.replace(/-/g,"+").replace(/_/g,"/").padEnd(Math.ceil(n.length/4)*4,"=")),t=new Uint8Array(e.length);for(let r=0;r<e.length;r++)t[r]=e.charCodeAt(r);return t}function Q(n){let e=te(n);if(!e)return null;try{let t=JSON.parse(new TextDecoder("utf-8",{fatal:!0}).decode(e));return typeof t=="object"&&t!==null&&!Array.isArray(t)?t:null}catch{return null}}function Me(n,e,t=Date.now()){let r=Object.hasOwn(n.entitlements,e)?n.entitlements[e]:void 0;return r?.active?r.expires_at===null?!0:Date.parse(r.expires_at)>(typeof t=="number"?t:t.getTime()):!1}var k=class extends Error{receipt;constructor(e){super(`the reservation ${JSON.stringify(e.idempotency_key)} is ${e.status} already: its job is not run again (a new one is a new Idempotency-Key)`),this.name="ReservationSettled",this.receipt=e}};async function Ce(n,e,t,r){let{idempotencyKey:s,amount:o,ttlSeconds:a,metadata:u}=t;if(typeof s!="string"||s==="")throw new TypeError("idempotencyKey names the reservation for good: a retry with it is the same hold, never a second");let c={amount:o,...t.credits!==void 0?{credits:t.credits}:{limit:t.limit},...a!==void 0?{ttl_seconds:a}:{},...u!==void 0?{metadata:u}:{}},i=(await n.purchases.holdSpend({path:{customer_id:e},headers:{"Idempotency-Key":s},body:c})).data;if(i.status!=="held")throw new k(i);let l={customer_id:i.customer,spend_id:i.id},p;try{p=await r(i)}catch(m){throw await n.purchases.releaseSpend({path:l}).catch(()=>{}),m}return await n.purchases.commitSpend({path:l}),p}async function*Le(n){let e,t=new Set;do{let r=(await n(e)).data;yield r;let s=r.next_cursor??void 0;if(s!==void 0&&t.has(s))throw new Error(`the listing answered a cursor it had answered before (${s})`);s!==void 0&&t.add(s),e=s}while(e)}var Ue=/^[A-Za-z_][A-Za-z0-9_]{0,127}$/,Ke=/^(PALBASE_|NODE_|BUN_|NPM_|LD_|DYLD_|GIT_|SSH_)|^(PATH|HOME|SHELL|USER|TMPDIR|TMP|TEMP|PWD|IFS|ENV|BASH_ENV|SSL_CERT_FILE|SSL_CERT_DIR|CURL_CA_BUNDLE|REQUESTS_CA_BUNDLE|NO_PROXY|ALL_PROXY|HTTP_PROXY|HTTPS_PROXY|PG\w*)$/i;function ne(n){return!Ue.test(n)||Ke.test(n)||/_PROXY$/i.test(n)}var Ne=/^(PORT|PALBASE_(URL|PROJECT_ID|MACHINE|REPLICAS|SECRET_KEY|WEBHOOK_SECRET|PLATFORM_SECRET|CONTRACT_OUT|SECRETS|DB_[A-Z0-9_]+))$/;function $e(n){return Ne.test(n)}async function Ie(n={}){let e=n.env??process.env,t=e.PALBASE_SECRETS;if(t===void 0||t==="")return;if(t!=="development"&&t!=="production")throw new TypeError(`PALBASE_SECRETS is development or production, not ${t}`);let r=n.url??e.PALBASE_URL??"",s=n.secretKey??e.PALBASE_SECRET_KEY??"";if(r===""||s==="")throw new TypeError("PALBASE_SECRETS reads the project's Secrets with PALBASE_URL and PALBASE_SECRET_KEY: give both (palbase env:pull writes them)");let o=v({url:new URL(r).origin,secretKey:s,fetch:n.fetch}),{data:a}=await o.secrets.getEnvironmentValues({path:{environment:t}}),u=[],c=[],i=[];for(let[l,p]of Object.entries(a.values)){if(ne(l)){i.push(l);continue}if(e[l]!==void 0&&e[l]!==""){c.push(l);continue}e[l]=p,u.push(l)}return{set:t,loaded:u.sort(),kept:c.sort(),refused:i.sort()}}export{v as a,f as b,B as c,F as d,H as e,pe as f,d as g,g as h,Te as i,T as j,ee as k,Me as l,k as m,Ce as n,Le as o,ne as p,$e as q,Ie as r};