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.
- package/dist/ai/index.js +2 -2
- package/dist/backend/engine.js +2 -2
- package/dist/backend/env.js +1 -1
- package/dist/backend/index.js +11 -11
- package/dist/backend/stack.js +1 -1
- package/dist/backend/test.js +2 -2
- package/dist/chunks/26UTOC32.js +2 -0
- package/dist/chunks/{IW34A4TV.js → 3453XVQL.js} +2 -2
- package/dist/chunks/{7PBQ3LI2.js → 3VY3ZZCI.js} +1 -1
- package/dist/chunks/{MAPLN2FY.js → 43VM626B.js} +1 -1
- package/dist/chunks/{BMCC423W.js → 7WIMZTYB.js} +2 -2
- package/dist/chunks/{ZOAWYN6L.js → AFFE3Q5O.js} +2 -2
- package/dist/chunks/{RYWIWH4U.js → BUAGC2PT.js} +1 -1
- package/dist/chunks/{5TVS7F23.js → BUYFMLTF.js} +2 -2
- package/dist/chunks/{O5CJJFEU.js → EEWP43KI.js} +1 -1
- package/dist/chunks/EWBRYA7P.js +2 -0
- package/dist/chunks/{S4F256W2.js → FVLHT7UQ.js} +2 -2
- package/dist/chunks/{SCXHU5B7.js → KNPQNBC2.js} +2 -2
- package/dist/chunks/{D2UQQWJC.js → KW6ODEYS.js} +1 -1
- package/dist/chunks/{5NZGW67E.js → LYEEW4UG.js} +2 -2
- package/dist/chunks/{SBVBCENU.js → NGE3D2K4.js} +2 -2
- package/dist/chunks/Q3S76MYJ.js +2 -0
- package/dist/chunks/{QTAPRS4Q.js → R3EIGCWV.js} +2 -2
- package/dist/chunks/{XJEZB4MI.js → S2KRJF6J.js} +2 -2
- package/dist/chunks/{6JUQZ4D4.js → V2FEPVVD.js} +2 -2
- package/dist/chunks/WLAO2QSR.js +20 -0
- package/dist/chunks/{L4SCG6HT.js → ZZG7ICDG.js} +2 -2
- package/dist/cli.js +124 -122
- package/dist/refuse/ai.js +1 -1
- package/dist/refuse/backend.js +1 -1
- package/dist/refuse/server.js +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/testing.js +2 -2
- package/dist/web/index.js +2 -2
- package/dist/web/message.js +2 -2
- package/dist/web/palbe.js +2 -2
- package/dist/web/paywall.js +2 -2
- package/dist/web/push-sw.js +1 -1
- package/dist/web/ssr.js +2 -2
- package/dist/web/vite.js +2 -2
- package/package.json +2 -2
- package/types/backend/src/database.d.ts +8 -1
- package/types/backend/src/engine-db.d.ts +3 -1
- package/types/backend/src/engine.d.ts +2 -0
- package/types/backend/src/idempotency.d.ts +136 -0
- package/types/internal/src/modules/auth.d.ts +25 -8
- package/types/internal/src/modules/purchases.d.ts +39 -3
- package/types/internal/src/modules/secrets.d.ts +138 -3
- package/types/internal/src/modules/stores.d.ts +30 -1
- package/types/server/src/fields.d.ts +62 -0
- package/types/server/src/index.d.ts +1 -0
- package/types/server/src/server.d.ts +8 -0
- package/types/server/src/testing.d.ts +8 -0
- package/dist/chunks/I2TY2GLY.js +0 -2
- package/dist/chunks/PKBCMWTM.js +0 -2
- package/dist/chunks/QCKQXIQV.js +0 -20
package/dist/refuse/ai.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! palbase 0.
|
|
1
|
+
/*! palbase 0.11.1 — Palbase SDK License (see LICENSE) */
|
|
2
2
|
throw new Error("palbase/ai is for servers: it speaks with the project secret key, which never goes into a browser or a mobile app \u2014 call your backend, and let it ask the model");
|
package/dist/refuse/backend.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! palbase 0.
|
|
1
|
+
/*! palbase 0.11.1 — Palbase SDK License (see LICENSE) */
|
|
2
2
|
throw new Error("palbase/backend is a server framework: it runs on your backend, never in a browser or a mobile app \u2014 apps use palbase/web (or the native SDKs)");
|
package/dist/refuse/server.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! palbase 0.
|
|
1
|
+
/*! palbase 0.11.1 — Palbase SDK License (see LICENSE) */
|
|
2
2
|
throw new Error("palbase/server is for servers: 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");
|
package/dist/server/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! palbase 0.
|
|
2
|
-
import{a as
|
|
1
|
+
/*! palbase 0.11.1 — Palbase SDK License (see LICENSE) */
|
|
2
|
+
import{a as e,b as f,c as g,d as h,e as i,f as j,g as k,h as l,i as m,j as n,k as o,l as p,m as q,n as r,o as s,p as t,q as u,r as v}from"../chunks/Q3S76MYJ.js";import{a as c,b as d}from"../chunks/26UTOC32.js";import"../chunks/EEWP43KI.js";import{m as b}from"../chunks/7WIMZTYB.js";import"../chunks/R3EIGCWV.js";import{b as a}from"../chunks/KNPQNBC2.js";import"../chunks/BUAGC2PT.js";import"../chunks/43VM626B.js";export{l as AuthUnavailable,b as EVENT_TYPES,c as FieldError,d as Fields,a as PalbaseError,q as ReservationSettled,k as TokenError,n as TokenVerifier,f as WebhookSignatureError,e as createServer,p as isEntitled,v as loadSecrets,s as pages,u as platformOwn,t as processOwn,o as requestAddress,m as sessionStanding,i as signatureHeader,h as verifySignature,g as verifyWebhook,j as verifyWebhookRequest,r as withReservation};
|
package/dist/server/testing.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! palbase 0.
|
|
2
|
-
import{a,b,c,d}from"../chunks/
|
|
1
|
+
/*! palbase 0.11.1 — Palbase SDK License (see LICENSE) */
|
|
2
|
+
import{a,b,c,d,e}from"../chunks/EWBRYA7P.js";import"../chunks/26UTOC32.js";import"../chunks/BUAGC2PT.js";import"../chunks/43VM626B.js";export{b as TestApiError,e as api,c as createTestApi,a as testFields,d as testRun};
|
package/dist/web/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! palbase 0.
|
|
2
|
-
import{a as h,b as g,c as C,d as w,e as v,f as b,g as E,h as T,i as P,j as S,k as x,l as k,m as R,n as I,o as L,p as M,q as G}from"../chunks/
|
|
1
|
+
/*! palbase 0.11.1 — Palbase SDK License (see LICENSE) */
|
|
2
|
+
import{a as h,b as g,c as C,d as w,e as v,f as b,g as E,h as T,i as P,j as S,k as x,l as k,m as R,n as I,o as L,p as M,q as G}from"../chunks/FVLHT7UQ.js";import{a as V,b as O,f as A}from"../chunks/V2FEPVVD.js";import"../chunks/ZZG7ICDG.js";import"../chunks/3453XVQL.js";import"../chunks/3VY3ZZCI.js";import"../chunks/LYEEW4UG.js";import"../chunks/7WIMZTYB.js";import"../chunks/R3EIGCWV.js";import{b as u}from"../chunks/KNPQNBC2.js";import"../chunks/BUAGC2PT.js";import"../chunks/43VM626B.js";var D="oai-events";async function N(e){if(typeof e?.start!="function")throw new TypeError("connectVoice: start(sdp) is the round trip to the app's backend, which starts the voice (chats.voice.start) and answers { id, sdp }");let o=e.rtc??B(),t=e.signal;t?.throwIfAborted();let s=e.audio??F(),c=e.microphone===void 0,a=e.microphone??await o.getUserMedia({audio:!0});if(t?.aborted){if(c)for(let r of a.getTracks())r.stop();throw t.reason}let n=new o.RTCPeerConnection,p=!1,d=()=>{if(!p){if(p=!0,f.close(),n.close(),c)for(let r of a.getTracks())r.stop();s.srcObject!==null&&(s.srcObject=null)}};n.addEventListener("track",r=>{s.srcObject=r.streams[0]??new MediaStream([r.track]),s.play?.()?.catch?.(()=>{})}),n.addEventListener("connectionstatechange",()=>e.onState?.(n.connectionState));for(let r of a.getAudioTracks())n.addTrack(r,a);let f=n.createDataChannel(D);f.addEventListener("message",r=>{let i=U(r.data);i!==void 0&&e.onError?.(i)});let y=()=>d();t?.addEventListener("abort",y,{once:!0});try{await n.setLocalDescription(await n.createOffer()),t?.throwIfAborted(),await H(n,e.iceTimeoutMs??1e4,t);let r=n.localDescription?.sdp;if(!r)throw new Error("connectVoice: the peer connection made no offer");let i=await e.start(r);if(t?.throwIfAborted(),typeof i?.id!="string"||i.id===""||typeof i.sdp!="string"||i.sdp==="")throw new TypeError("connectVoice: start answered no voice \u2014 it is { id, sdp }, chats.voice.start's answer as the backend got it");return await n.setRemoteDescription({type:"answer",sdp:i.sdp}),t?.removeEventListener("abort",y),{id:i.id,peer:n,audio:s,microphone:a,get muted(){return a.getAudioTracks().every(l=>!l.enabled)},mute(l){for(let m of a.getAudioTracks())m.enabled=!l},close:d}}catch(r){throw t?.removeEventListener("abort",y),d(),t?.aborted?t.reason:r}}function H(e,o,t){return e.iceGatheringState==="complete"?Promise.resolve():t?.aborted?Promise.reject(t.reason):new Promise((s,c)=>{let a=()=>{clearTimeout(d),e.removeEventListener("icegatheringstatechange",n),t?.removeEventListener("abort",p)},n=()=>{e.iceGatheringState==="complete"&&(a(),s())},p=()=>{a(),c(t?.reason)},d=setTimeout(()=>{a(),c(new Error(`connectVoice: the network candidates were not gathered in ${o} ms`))},o);e.addEventListener("icegatheringstatechange",n),t?.addEventListener("abort",p,{once:!0})})}function U(e){if(typeof e=="string")try{let o=JSON.parse(e);return o.type!=="error"?void 0:typeof o.error?.message=="string"?o.error.message:typeof o.error?.code=="string"?o.error.code:"the voice failed"}catch{return}}function B(){let e=globalThis;if(typeof e.RTCPeerConnection!="function"||typeof e.navigator?.mediaDevices?.getUserMedia!="function")throw new TypeError("connectVoice: this runtime has no WebRTC (RTCPeerConnection, navigator.mediaDevices): a chat's voice is a browser's");let o=e.navigator.mediaDevices;return{RTCPeerConnection:e.RTCPeerConnection,getUserMedia:t=>o.getUserMedia(t)}}function F(){let e=globalThis.document;if(!e)throw new TypeError("connectVoice: give audio \u2014 the element the voice plays in \u2014 where there is no document");let o=e.createElement("audio");return o.autoplay=!0,o}export{E as Analytics,V as BackendError,w as Channel,T as Consent,M as Flags,A as GatewayError,x as InApp,P as Inbox,b as Installation,R as Notify,u as PalbaseError,I as Purchases,v as RealtimeSocket,S as SESSION_START,h as SessionChangedError,k as WebPush,N as connectVoice,G as createClient,O as isBackendError,C as localStore,g as memoryStore,L as redemptionToken};
|
package/dist/web/message.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! palbase 0.
|
|
2
|
-
import{a as i}from"../chunks/
|
|
1
|
+
/*! palbase 0.11.1 — Palbase SDK License (see LICENSE) */
|
|
2
|
+
import{a as i}from"../chunks/3453XVQL.js";import{a as e,b as s,c as t,d as p,e as o,f as a,h as r,j as n,k as y,p as M,q as l,t as g}from"../chunks/3VY3ZZCI.js";import"../chunks/43VM626B.js";export{n as MESSAGE_COMPONENTS,a as MESSAGE_ICONS,r as MESSAGE_SCHEMA,s as assetId,y as countdownValues,e as fontFamily,o as htmlDocument,t as prepareAssets,i as presentMessage,g as renderMessage,M as resolveMessage,l as resolveMessageWithPaths,p as unzip};
|
package/dist/web/palbe.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! palbase 0.
|
|
2
|
-
import{c as e,d as r,i as t}from"../chunks/
|
|
1
|
+
/*! palbase 0.11.1 — Palbase SDK License (see LICENSE) */
|
|
2
|
+
import{c as e,d as r,i as t}from"../chunks/V2FEPVVD.js";import"../chunks/LYEEW4UG.js";import"../chunks/R3EIGCWV.js";import"../chunks/KNPQNBC2.js";import"../chunks/BUAGC2PT.js";import"../chunks/43VM626B.js";export{e as RESERVED_CLIENT_NAMES,t as __registerBackend,r as isClientName};
|
package/dist/web/paywall.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! palbase 0.
|
|
2
|
-
import{a as f,b as d,c as m,d as u,e as A,f as c,g as R}from"../chunks/
|
|
1
|
+
/*! palbase 0.11.1 — Palbase SDK License (see LICENSE) */
|
|
2
|
+
import{a as f,b as d,c as m,d as u,e as A,f as c,g as R}from"../chunks/ZZG7ICDG.js";import{a as e,b as a,c as t,d as l,e as r,f as o,g as p,i as y,k as s,l as P,n,o as i,s as w}from"../chunks/3VY3ZZCI.js";import"../chunks/KNPQNBC2.js";import"../chunks/BUAGC2PT.js";import"../chunks/43VM626B.js";export{y as PAYWALL_COMPONENTS,o as PAYWALL_ICONS,p as PAYWALL_SCHEMA,c as Paywall,a as assetId,f as attributesHeader,s as countdownValues,R as defaultPaywall,e as fontFamily,u as formatPeriod,m as formatPrice,r as htmlDocument,A as packageValues,P as packagesOf,d as parseDuration,t as prepareAssets,w as renderPaywall,n as resolvePaywall,i as resolvePaywallWithPaths,l as unzip};
|
package/dist/web/push-sw.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! palbase 0.
|
|
1
|
+
/*! palbase 0.11.1 — Palbase SDK License (see LICENSE) */
|
|
2
2
|
"use strict";(()=>{function f(e,s){if(e)try{let i=new URL(e,s);if(i.protocol==="https:"||i.protocol==="http:"&&(i.hostname==="localhost"||i.hostname==="127.0.0.1"))return i.href}catch{}}function g(e){let s=new URL(e.location.href).searchParams,i=s.get("url")??"",l=s.get("key")??"",r=(n,t)=>!n||!i?Promise.resolve():e.fetch(`${i.replace(/\/$/,"")}/notify/receipts`,{method:"POST",headers:{"content-type":"application/json",apikey:l},body:JSON.stringify({receipts:[{receipt:n,status:t}]}),keepalive:!0}).catch(()=>{}),a=async(n,t)=>{for(let o of await e.clients.matchAll({type:"window",includeUncontrolled:!0}))o.postMessage({type:"palbase.push",event:n,url:t.url,data:t.data})};e.addEventListener("push",(n=>{let t={};try{t=n.data?.json()??{}}catch{}let o={url:t.url,receipt:t.receipt,data:t.data};n.waitUntil(e.registration.showNotification(t.title??"",{body:t.body,image:t.image,tag:t.tag,data:o}).then(()=>Promise.all([r(t.receipt,"delivered"),a("received",o)])))})),e.addEventListener("notificationclick",(n=>{n.notification.close();let t=n.notification.data??{},o=f(t.url,e.location.origin),c=async()=>{if(!o)return;let d=(await e.clients.matchAll({type:"window",includeUncontrolled:!0})).find(u=>u.url===o);d?await d.focus():await e.clients.openWindow(o)};n.waitUntil(Promise.all([r(t.receipt,"opened"),c(),a("opened",t)]))})),e.addEventListener("notificationclose",(n=>{n.waitUntil(r(n.notification.data?.receipt,"dismissed"))}))}typeof self<"u"&&typeof self.registration=="object"&&!("document"in self)&&g(self);})();
|
package/dist/web/ssr.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! palbase 0.
|
|
2
|
-
import{q as f}from"../chunks/
|
|
1
|
+
/*! palbase 0.11.1 — Palbase SDK License (see LICENSE) */
|
|
2
|
+
import{q as f}from"../chunks/FVLHT7UQ.js";import"../chunks/V2FEPVVD.js";import"../chunks/ZZG7ICDG.js";import"../chunks/3453XVQL.js";import"../chunks/3VY3ZZCI.js";import"../chunks/LYEEW4UG.js";import"../chunks/7WIMZTYB.js";import"../chunks/R3EIGCWV.js";import"../chunks/KNPQNBC2.js";import"../chunks/BUAGC2PT.js";import"../chunks/43VM626B.js";var C=Object.freeze({"Cache-Control":"private, no-cache, no-store, must-revalidate, max-age=0",Expires:"0",Pragma:"no-cache"}),x={path:"/",sameSite:"lax",secure:!0,httpOnly:!1,maxAge:400*24*60*60},u=3180,k=5*6e4;function d(t){let e=/^pb_([a-z0-9]+)_c/.exec(t)?.[1];if(e===void 0)throw new TypeError("publishableKey is the project publishable key (pb_<ref>_c\u2026)");return`pb-${e}-session`}var y=t=>{let e=new TextEncoder().encode(JSON.stringify(t)),o="";for(let s of e)o+=String.fromCharCode(s);return btoa(o).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")},b=t=>{try{let e=atob(t.replace(/-/g,"+").replace(/_/g,"/")),o=JSON.parse(new TextDecoder().decode(Uint8Array.from(e,s=>s.charCodeAt(0))));return typeof o.accessToken!="string"||typeof o.refreshToken!="string"||typeof o.expiresAt!="number"||typeof o.userId!="string"?null:o}catch{return null}};function m(t,e,o={},s=!1){let c={...x,...o},g=async()=>new Map((await e.getAll()).map(n=>[n.name,n.value])),S=n=>n===t||n.startsWith(`${t}.`)&&/^\d+$/.test(n.slice(t.length+1)),h=async n=>{if(!e.setAll)return;let i=n===null?[]:n.length<=u?[[t,n]]:Array.from({length:Math.ceil(n.length/u)},(r,l)=>[`${t}.${l}`,n.slice(l*u,(l+1)*u)]),p=new Set(i.map(([r])=>r)),a=i.map(([r,l])=>({name:r,value:l,options:c}));for(let r of(await g()).keys())S(r)&&!p.has(r)&&a.push({name:r,value:"",options:{...c,maxAge:0}});a.length>0&&await e.setAll(a,{...C})};return{async load(){let n=await g(),i=n.get(t);if(i===void 0){let p="";for(let a=0;n.has(`${t}.${a}`);a++)p+=n.get(`${t}.${a}`);i=p}return i?b(i):null},save:n=>h(y(n)),clear:()=>h(null),shared:s}}function A(t){let{cookies:e,cookieOptions:o,...s}=t,c=t.refresh??(e.setAll?"before":"never");return f({...s,refresh:c,storage:m(d(t.publishableKey),e,o)})}function O(){let t=()=>{let e=globalThis.document;if(!e)throw new Error("documentCookies: there is no document here \u2014 a server gives its request's cookies to createServerClient");return e};return{getAll:()=>t().cookie.split(";").map(e=>e.trim()).filter(e=>e.includes("=")).map(e=>{let o=e.indexOf("=");return{name:e.slice(0,o),value:e.slice(o+1)}}),setAll(e){for(let o of e){let s=o.options,c=[`Path=${s.path??"/"}`,s.domain?`Domain=${s.domain}`:"",s.maxAge!==void 0?`Max-Age=${s.maxAge}`:"",s.expires?`Expires=${s.expires.toUTCString()}`:"",s.sameSite?`SameSite=${s.sameSite.charAt(0).toUpperCase()}${s.sameSite.slice(1)}`:"",s.secure?"Secure":""].filter(Boolean);t().cookie=[`${o.name}=${o.value}`,...c].join("; ")}}}}function $(t){let{cookieOptions:e,...o}=t,s=globalThis.location?.protocol==="https:";return f({...o,storage:m(d(t.publishableKey),O(),{secure:s,...e},!0)})}async function v(t){let e=await t.auth.currentSession();if(!e||(e.expiresAt-Date.now()<=k&&await t.auth.refreshSession().catch(()=>{}),!await t.auth.currentSession()))return null;try{return(await t.auth.api.verifyTokenUser()).data}catch(o){if(o.status===401)return null;throw o}}export{C as NO_STORE_HEADERS,x as SESSION_COOKIE_OPTIONS,m as cookieSessionStore,$ as createBrowserClient,A as createServerClient,O as documentCookies,v as keepSession,d as sessionCookieName};
|
package/dist/web/vite.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
/*! palbase 0.
|
|
2
|
-
import"../chunks/
|
|
1
|
+
/*! palbase 0.11.1 — Palbase SDK License (see LICENSE) */
|
|
2
|
+
import"../chunks/43VM626B.js";import{execFile as $}from"node:child_process";import{existsSync as y,mkdtempSync as k,readFileSync as S,rmSync as x,writeFileSync as P}from"node:fs";import{createRequire as O}from"node:module";import{tmpdir as W}from"node:os";import{dirname as g,join as o,relative as u,resolve as v,sep as d}from"node:path";var E=/\.(?:ts|js|mts|mjs)$/;function F(n){if(!/^[a-z][a-z0-9-]*$/.test(n.app))throw new TypeError(`palbase({ app }): ${JSON.stringify(n.app)} is an app's slug (palbase apps:list)`);let c=process.cwd(),p="",i="",f=()=>v(c,n.dir??"palbase"),m=()=>{let e=n.environments[p];if(!e)throw new Error(`palbase: the mode "${p}" is not built against any environment \u2014 say which in vite.config: palbase({ environments: { ${JSON.stringify(p)}: '<environment slug>' } })`);return e};return{name:"palbase",enforce:"pre",config(e,s){p=s.mode,i=s.command??"";let r=e.build?.rollupOptions?.onwarn;return{build:{rollupOptions:{onwarn(t,a){let l=String(t.exporter??t.id??"");if(t.code==="MISSING_EXPORT"&&l.includes(`${d}environments${d}`))throw new Error(`palbase: ${t.message} \u2014 ${m()}'s backend has no such route (palbase apps:sync brings what it has)`);r?r(t,a):a(t)}}}}},configResolved(e){c=e.root,p=e.mode,i=e.command??i},async buildStart(){i!=="build"||n.typecheck===!1||await j(c,m(),n.tsconfig)},resolveId(e,s){if(!s)return null;let r=f();if(e==="palbase"||e.startsWith("palbase/")){let w=u(r,s);return w.startsWith("..")||w===""?null:this.resolve(e,o(c,"index.html"),{skipSelf:!0})}if(!(e.startsWith(".")||e.startsWith("/")))return null;let t=v(g(s),e);if(E.test(t)||(t+=".ts"),g(t)!==r&&g(t)!==o(r,"palbe"))return null;let a=u(r,t),l=`${n.app}.ts`;if(a!==l&&!a.startsWith(`palbe${d}`))return null;let h=m(),b=o(r,"environments",h,a===l?l:o("palbe",a.slice(`palbe${d}`.length)));if(!y(b))throw new Error(`palbase: ${u(c,b)} is not there \u2014 palbase apps:sync writes it (is ${n.app} set in ${h}, and has ${h} that machine?)`);return b}}}async function j(n,c,p){let i=p?v(n,p):["tsconfig.app.json","tsconfig.json"].map(e=>o(n,e)).find(y);if(!i)return;if(!y(i))throw new Error(`palbase({ tsconfig }): ${u(n,i)} is not there`);let f;try{let e=O(o(n,"package.json")).resolve("typescript/package.json"),s=JSON.parse(S(e,"utf8")).bin?.tsc;if(!s)throw new Error("no tsc");f=o(g(e),s)}catch{throw new Error("palbase: the build type-checks the app against its environment with the app's TypeScript, and the app has none (npm i -D typescript) \u2014 or say palbase({ typecheck: false })")}let m=k(o(W(),"palbase-typecheck-"));try{let e=o(m,"tsconfig.json");P(e,JSON.stringify({extends:i,compilerOptions:{noEmit:!0,composite:!1,incremental:!1,moduleSuffixes:[`.${c}`,""]}}));let s=await new Promise(r=>$(process.execPath,[f,"-p",e,"--pretty","false"],{cwd:n,maxBuffer:16<<20},(t,a,l)=>r({code:t?typeof t.code=="number"?t.code:1:0,text:`${a}${l}`.trim()})));if(s.code!==0)throw new Error(`palbase: the app does not type-check against ${c}, the environment this build is made for \u2014 a route its backend does not have, or a call that does not fit it:
|
|
3
3
|
${s.text}`)}finally{x(m,{recursive:!0,force:!0})}}export{F as palbase};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "palbase",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "Palbase for JavaScript: the web client (palbase/web), the server SDK (palbase/server), the backend framework (palbase/backend), AI (palbase/ai) and the CLI (npx palbase).",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"homepage": "https://palbase.studio",
|
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
"dependencies": {
|
|
136
136
|
"@types/node": "26.6.4",
|
|
137
137
|
"bun-types": "1.4.2",
|
|
138
|
-
"palgres": "0.
|
|
138
|
+
"palgres": "0.11.1",
|
|
139
139
|
"reflect-metadata": "^0.2.2",
|
|
140
140
|
"typescript": "~6.0.3",
|
|
141
141
|
"zod": "^4.6.5"
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type AppDatabase, type Binding, type Embed, type Plan, type Pool, type Schema, type SearchWorker, type SqlDriver } from 'palgres';
|
|
2
2
|
import { type SchemaDef } from 'palgres/dsl';
|
|
3
|
+
import { type Fields } from '../../server/src/index.js';
|
|
4
|
+
import type { KeptAnswers } from './idempotency.js';
|
|
3
5
|
import type { Schemas, Tables } from './db-env.js';
|
|
4
6
|
/** A connection pool: Bun's SQL and postgres.js both are one. */
|
|
5
7
|
export interface SqlPool extends Pool {
|
|
@@ -137,6 +139,8 @@ export interface OpenDatabase {
|
|
|
137
139
|
}>;
|
|
138
140
|
/** What embeds the rows of the tables that declare search, when the schema has one; not started. */
|
|
139
141
|
search?: SearchWorker;
|
|
142
|
+
/** How an Idempotency-Key's answer is kept: sealed when the app encrypts a column (idempotency.ts). */
|
|
143
|
+
keptAnswers: KeptAnswers;
|
|
140
144
|
close(): Promise<void>;
|
|
141
145
|
}
|
|
142
146
|
/**
|
|
@@ -158,6 +162,9 @@ export declare function planLines(p: Plan): string[];
|
|
|
158
162
|
* schema with no version is the developer's to push (SchemaNotPushed).
|
|
159
163
|
*/
|
|
160
164
|
export declare function openDatabase(config: DatabaseConfig, env: Record<string, string | undefined>, secretKey: string, where: {
|
|
161
|
-
onPlatform: boolean;
|
|
165
|
+
onPlatform: boolean;
|
|
166
|
+
/** What embeds when the app names no embedder: the project's ai module. */
|
|
162
167
|
embed: Embed;
|
|
168
|
+
/** The project's field keys (palbase/server's fields): loaded only when the schema encrypts a column, or a migration takes one's encryption on or off. */
|
|
169
|
+
fields: Fields;
|
|
163
170
|
}): Promise<OpenDatabase>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type DatabaseDiagnosticsOptions, type DBClient, type SqlDriver } from 'palgres';
|
|
1
|
+
import { type DatabaseDiagnosticsOptions, type DBClient, type FieldsSource, type SqlDriver } from 'palgres';
|
|
2
2
|
import type { SchemaDef } from 'palgres/dsl';
|
|
3
3
|
export type { SqlDriver };
|
|
4
4
|
export interface RequestDatabaseIdentity {
|
|
@@ -14,6 +14,8 @@ export interface RequestDatabaseOptions {
|
|
|
14
14
|
schemas: readonly SchemaDef[];
|
|
15
15
|
diagnostics?: DatabaseDiagnosticsOptions;
|
|
16
16
|
signal?: AbortSignal;
|
|
17
|
+
/** What seals and opens the schemas' .encrypted() columns: palbase/server/testing's testFields() in a test. */
|
|
18
|
+
fields?: FieldsSource;
|
|
17
19
|
}
|
|
18
20
|
export interface RequestDatabaseHandle {
|
|
19
21
|
/** The string-keyed client: what withServices({ Database }) takes. */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type PalbaseServer, type TokenUser, type TokenVerifier, type WebhookEvent } from '../../server/src/index.js';
|
|
2
2
|
import { type AuthSpec, type RateLimitConfig } from './decorators.js';
|
|
3
|
+
import { type KeptAnswers } from './idempotency.js';
|
|
3
4
|
import { type ContractOptions } from './openapi.js';
|
|
4
5
|
import { type StringsTable } from './i18n.js';
|
|
5
6
|
import type { RequestTelemetry } from './requests.js';
|
|
@@ -66,6 +67,7 @@ export interface AppOptions {
|
|
|
66
67
|
database?: {
|
|
67
68
|
app: RequestDatabases;
|
|
68
69
|
requests: 'caller' | 'service';
|
|
70
|
+
keptAnswers?: KeptAnswers;
|
|
69
71
|
};
|
|
70
72
|
/**
|
|
71
73
|
* The app's strings (palbase/strings/, loadStringsTable): t() answers each request in the first of its
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { type EnvServiceDatabase, type TablesOf } from 'palgres';
|
|
2
|
+
import type { StandardSchemaV1 } from './decorators.js';
|
|
3
|
+
import type { RequestDatabases } from './engine.js';
|
|
4
|
+
/** The writes a key is taken on: what may change something, and be sent again. */
|
|
5
|
+
export declare const KEYED_METHODS: ReadonlySet<string>;
|
|
6
|
+
/** The header parameter every keyed write of the contract carries — the platform's modules' own words. */
|
|
7
|
+
export declare const IDEMPOTENCY_KEY_PARAMETER: {
|
|
8
|
+
readonly description: 'Makes the write safe to send again (at most 255 characters). A retry with the same key and the same request gets the first answer back, marked `Idempotent-Replayed: true`, and changes nothing; the same key with a different request is refused with 422, and while the first request still runs a retry gets 409 with Retry-After. Answers are kept for 24 hours.';
|
|
9
|
+
readonly in: 'header';
|
|
10
|
+
readonly name: 'Idempotency-Key';
|
|
11
|
+
readonly required: false;
|
|
12
|
+
readonly schema: {
|
|
13
|
+
readonly maxLength: 255;
|
|
14
|
+
readonly type: 'string';
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The framework's own tables in the app's database: migrated with the app's
|
|
19
|
+
* schemas, by the deploy's migration step, as the tables' owner.
|
|
20
|
+
*/
|
|
21
|
+
export declare const FRAMEWORK_SCHEMA: import("palgres/dsl").SchemaDef<{
|
|
22
|
+
idempotency_keys: import("palgres/dsl").TableDef<{
|
|
23
|
+
/** The caller's key, as theirs: a digest of who sent it and the key. */
|
|
24
|
+
key: import("palgres/dsl").ColumnBuilder<"text", false, false, never, unknown, unknown, import("palgres/dsl").ColumnWrite, "primary">;
|
|
25
|
+
/** What made the request the one it is: a digest of its method, path and query, caller and body. */
|
|
26
|
+
fingerprint: import("palgres/dsl").ColumnBuilder<"text", false, false, never, unknown, unknown, import("palgres/dsl").ColumnWrite, import("palgres/dsl").ColumnKey>;
|
|
27
|
+
started_at: import("palgres/dsl").ColumnBuilder<"timestamp", false, true, never, unknown, unknown, import("palgres/dsl").ColumnWrite, import("palgres/dsl").ColumnKey>;
|
|
28
|
+
completed_at: import("palgres/dsl").ColumnBuilder<"timestamp", true, false, never, unknown, unknown, import("palgres/dsl").ColumnWrite, import("palgres/dsl").ColumnKey>;
|
|
29
|
+
status_code: import("palgres/dsl").ColumnBuilder<"integer", true, false, never, unknown, unknown, import("palgres/dsl").ColumnWrite, import("palgres/dsl").ColumnKey>;
|
|
30
|
+
/** The answer's headers, as [name, value] pairs. */
|
|
31
|
+
response_headers: import("palgres/dsl").ColumnBuilder<"jsonb", true, false, never, [string, string][], unknown, import("palgres/dsl").ColumnWrite, import("palgres/dsl").ColumnKey>;
|
|
32
|
+
/** The answer's body, base64; null when it had none, was over 64 KiB or was still being produced. */
|
|
33
|
+
response_body: import("palgres/dsl").ColumnBuilder<"text", true, false, never, unknown, unknown, import("palgres/dsl").ColumnWrite, import("palgres/dsl").ColumnKey>;
|
|
34
|
+
}> & {
|
|
35
|
+
name: "idempotency_keys";
|
|
36
|
+
} & import("palgres/dsl").TableKeys<readonly (readonly "key"[])[]>;
|
|
37
|
+
}>;
|
|
38
|
+
type KeysTable = TablesOf<typeof FRAMEWORK_SCHEMA>['idempotency_keys'];
|
|
39
|
+
type KeysDatabase = EnvServiceDatabase<Record<never, never>, {
|
|
40
|
+
palbase: {
|
|
41
|
+
idempotency_keys: KeysTable;
|
|
42
|
+
};
|
|
43
|
+
}>;
|
|
44
|
+
type Row = Awaited<ReturnType<KeysDatabase['palbase']['idempotency_keys']['findMany']>>[number];
|
|
45
|
+
/** Who sent a request, as its key is theirs: the role it runs as, and its user's id — for nobody signed in, the address the edge names (a:<address>). */
|
|
46
|
+
export interface Caller {
|
|
47
|
+
role: string;
|
|
48
|
+
uid: string;
|
|
49
|
+
}
|
|
50
|
+
/** A keyed write, as it arrived. */
|
|
51
|
+
export interface KeyedRequest {
|
|
52
|
+
key: string;
|
|
53
|
+
caller: Caller;
|
|
54
|
+
method: string;
|
|
55
|
+
/** Its path and query, as sent. */
|
|
56
|
+
uri: string;
|
|
57
|
+
body: Uint8Array;
|
|
58
|
+
}
|
|
59
|
+
/** How the engine answers: its refusals' envelope, a failure's answer, and whether the handler had begun. */
|
|
60
|
+
export interface Answers {
|
|
61
|
+
requestId: string;
|
|
62
|
+
refusal(r: {
|
|
63
|
+
status: number;
|
|
64
|
+
code: string;
|
|
65
|
+
message: string;
|
|
66
|
+
retryAfter?: number;
|
|
67
|
+
}): Response;
|
|
68
|
+
failure(e: unknown): Response;
|
|
69
|
+
ran(): boolean;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Whether a route's writes take a key: a write the client itself sends the
|
|
73
|
+
* app. An @Upload route's is sent by storage, which calls it once the file is
|
|
74
|
+
* there (its route says so: meta.upload).
|
|
75
|
+
*/
|
|
76
|
+
export declare function takesKey(route: {
|
|
77
|
+
method: string;
|
|
78
|
+
meta: object;
|
|
79
|
+
}): boolean;
|
|
80
|
+
/** The key a write carries, undefined for a request it does not apply to. */
|
|
81
|
+
export declare function keyOf(method: string, headers: Headers): string | undefined;
|
|
82
|
+
/** The key as its caller's: two callers' same key are two rows, and neither can take — or block for a day — the other's. */
|
|
83
|
+
export declare function recordKey(caller: Caller, key: string): string;
|
|
84
|
+
/** What makes two requests the same one: what they do, to what, who asks, and with what. */
|
|
85
|
+
export declare function fingerprint(r: KeyedRequest): string;
|
|
86
|
+
/**
|
|
87
|
+
* What a key's row says of a request that finds it: another request's
|
|
88
|
+
* (reused), its answer to send again (replay), or a claim still running —
|
|
89
|
+
* taken over if it went stale.
|
|
90
|
+
*/
|
|
91
|
+
export declare function verdictOf(row: Pick<Row, 'fingerprint' | 'completed_at'>, fp: string): 'reused' | 'replay' | 'running';
|
|
92
|
+
/**
|
|
93
|
+
* Whether an answer is kept against its key once the request failed: one
|
|
94
|
+
* that settles it (not a 5xx, a 429, a 499), given by the handler — what was
|
|
95
|
+
* answered before it ran is not, and the retry runs afresh.
|
|
96
|
+
*/
|
|
97
|
+
export declare function keeps(status: number, ran: boolean): boolean;
|
|
98
|
+
/** The Idempotency-Key a route's @Headers schema declares, if it does: the framework reads that header on every keyed write. */
|
|
99
|
+
export declare function declaresKey(schema: StandardSchemaV1): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* How a kept answer's body is kept: sealed with the project's field keys when
|
|
102
|
+
* the app encrypts a column — an answer may hold a field's value, and the
|
|
103
|
+
* table is the app's database, which encryption keeps fields out of — and
|
|
104
|
+
* opened when it is replayed.
|
|
105
|
+
*/
|
|
106
|
+
export interface KeptAnswers {
|
|
107
|
+
seal(body: string): string;
|
|
108
|
+
open(kept: string): Promise<string>;
|
|
109
|
+
}
|
|
110
|
+
export declare class IdempotencyKeys {
|
|
111
|
+
private readonly db;
|
|
112
|
+
private readonly answers;
|
|
113
|
+
constructor(db: RequestDatabases, answers?: KeptAnswers);
|
|
114
|
+
/** A keyed write: its first answer again, a refusal, or `run` under its claim — the answer written by `record` in the request's transaction. */
|
|
115
|
+
serve(r: KeyedRequest, run: (record: (built: Response) => Promise<Response>) => Promise<Response>, answers: Answers): Promise<Response>;
|
|
116
|
+
/**
|
|
117
|
+
* The rows a day old, taken away: now and then before a claim, in a
|
|
118
|
+
* transaction of its own — a sweep that fails, or waits on a row, costs no
|
|
119
|
+
* write its key.
|
|
120
|
+
*/
|
|
121
|
+
sweep(): Promise<number>;
|
|
122
|
+
private begin;
|
|
123
|
+
private record;
|
|
124
|
+
private keep;
|
|
125
|
+
/**
|
|
126
|
+
* Frees the key of a request that failed, so its retry runs — at once,
|
|
127
|
+
* before the failure is answered. When the database is what failed it may
|
|
128
|
+
* refuse this too, and a claim left standing makes every retry wait out
|
|
129
|
+
* StaleAfter on top of the outage: tried again in the background until the
|
|
130
|
+
* database takes it, or the claim is stale anyway.
|
|
131
|
+
*/
|
|
132
|
+
private release;
|
|
133
|
+
private releaseOnce;
|
|
134
|
+
private asService;
|
|
135
|
+
}
|
|
136
|
+
export {};
|
|
@@ -1014,7 +1014,7 @@ export type AuthSettings = {
|
|
|
1014
1014
|
*/
|
|
1015
1015
|
risk_strict_step_up: number;
|
|
1016
1016
|
/**
|
|
1017
|
-
* The changes to how an account signs in that its user is mailed about, each with its own template (auth.security_<kind>): a password changed, the email address changed (told to the address the account had), a provider linked or unlinked, a second factor added or removed, a passkey added or removed.
|
|
1017
|
+
* The changes to how an account signs in that its user is mailed about, each with its own template (auth.security_<kind>): a password changed, the email address changed (told to the address the account had), a provider linked or unlinked, a second factor added or removed, a passkey added or removed. By default a second factor or a passkey added or removed (mfa_factor_enrolled, mfa_factor_removed, passkey_added, passkey_removed); the others as the project chooses. Mail goes to a confirmed address only, in the user's locale.
|
|
1018
1018
|
*/
|
|
1019
1019
|
security_notifications?: ("password_changed" | "email_changed" | "identity_linked" | "identity_unlinked" | "mfa_factor_enrolled" | "mfa_factor_removed" | "passkey_added" | "passkey_removed")[];
|
|
1020
1020
|
/**
|
|
@@ -1097,7 +1097,7 @@ export type AuthSettingsUpdateRequest = {
|
|
|
1097
1097
|
*/
|
|
1098
1098
|
risk_strict_step_up?: number;
|
|
1099
1099
|
/**
|
|
1100
|
-
* The changes to how an account signs in that its user is mailed about, each with its own template (auth.security_<kind>): a password changed, the email address changed (told to the address the account had), a provider linked or unlinked, a second factor added or removed, a passkey added or removed.
|
|
1100
|
+
* The changes to how an account signs in that its user is mailed about, each with its own template (auth.security_<kind>): a password changed, the email address changed (told to the address the account had), a provider linked or unlinked, a second factor added or removed, a passkey added or removed. By default a second factor or a passkey added or removed (mfa_factor_enrolled, mfa_factor_removed, passkey_added, passkey_removed); the others as the project chooses. Mail goes to a confirmed address only, in the user's locale. Present (even []) replaces the kinds; absent keeps them.
|
|
1101
1101
|
*/
|
|
1102
1102
|
security_notifications?: ("password_changed" | "email_changed" | "identity_linked" | "identity_unlinked" | "mfa_factor_enrolled" | "mfa_factor_removed" | "passkey_added" | "passkey_removed")[];
|
|
1103
1103
|
/**
|
|
@@ -3168,12 +3168,29 @@ export type StatusSent = {
|
|
|
3168
3168
|
export type StatusVerified = {
|
|
3169
3169
|
status: "verified";
|
|
3170
3170
|
};
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3171
|
+
/**
|
|
3172
|
+
* A TOTP step-up sends its `code`; a WebAuthn step-up sends the assertion of `POST /auth/step-up/webauthn/begin`'s ceremony at the top level of the same body.
|
|
3173
|
+
*
|
|
3174
|
+
*/
|
|
3175
|
+
export type StepUpRequest = StepUpTOTPRequest | StepUpWebAuthnRequest;
|
|
3176
|
+
export type StepUpTOTPRequest = {
|
|
3177
|
+
code: string;
|
|
3178
|
+
method: "totp";
|
|
3179
|
+
};
|
|
3180
|
+
/**
|
|
3181
|
+
* `method` beside the PublicKeyCredential JSON a browser makes in `navigator.credentials.get()` (as `WebAuthnAssertionResponse`); all binary fields are base64url-encoded.
|
|
3182
|
+
*
|
|
3183
|
+
*/
|
|
3184
|
+
export type StepUpWebAuthnRequest = {
|
|
3185
|
+
authenticatorAttachment?: string;
|
|
3186
|
+
clientExtensionResults?: {
|
|
3187
|
+
[key: string]: unknown;
|
|
3188
|
+
};
|
|
3189
|
+
id: string;
|
|
3190
|
+
method: "webauthn";
|
|
3191
|
+
rawId: string;
|
|
3192
|
+
response: WebAuthnAuthenticatorAssertionResponse;
|
|
3193
|
+
type: "public-key";
|
|
3177
3194
|
};
|
|
3178
3195
|
export type StepUpResult = {
|
|
3179
3196
|
access_token: string;
|
|
@@ -8,7 +8,7 @@ export type AppReview = {
|
|
|
8
8
|
*/
|
|
9
9
|
note?: string;
|
|
10
10
|
/**
|
|
11
|
-
* The
|
|
11
|
+
* The screenshot App Review is shown of the product in the app: the sha256 (sha256:<hex> or the hex alone) of an image uploaded to the stores module for review (palbase stores:review-screenshot, PUT /stores/apps/{app_id}/screenshots?for=review). A plan gives it to App Store Connect, and Apple then has what it needs of the product.
|
|
12
12
|
*/
|
|
13
13
|
screenshot?: string;
|
|
14
14
|
};
|
|
@@ -232,6 +232,10 @@ export type ApplePrice = {
|
|
|
232
232
|
* The storefront (USA).
|
|
233
233
|
*/
|
|
234
234
|
territory: string;
|
|
235
|
+
/**
|
|
236
|
+
* A coming price's first day (UTC); none for one in effect.
|
|
237
|
+
*/
|
|
238
|
+
from?: string;
|
|
235
239
|
};
|
|
236
240
|
/**
|
|
237
241
|
* Whether the environment sells in the App Store, check by check.
|
|
@@ -271,6 +275,7 @@ export type AppleStepResult = {
|
|
|
271
275
|
* The App Store's product id (or the subscription group's reference name).
|
|
272
276
|
*/
|
|
273
277
|
store_id: string;
|
|
278
|
+
next_price?: ApplePrice;
|
|
274
279
|
};
|
|
275
280
|
/**
|
|
276
281
|
* What the stores module answered of a plan's Google Play part, once it wrote it (pending until then): Play's state, step by step.
|
|
@@ -645,6 +650,12 @@ export type Channel = {
|
|
|
645
650
|
* Stripe's tax code for the product (the web's alone).
|
|
646
651
|
*/
|
|
647
652
|
tax_code?: string;
|
|
653
|
+
/**
|
|
654
|
+
* Ids the store already sells the product's plans by — made in App Store Connect or the Play Console by hand, or by another tool — by plan key: the plan is that product at the store, found by its id and never made there (a plan's link step), and its purchases, recorded before, are the catalog's from then on. On the App Store each plan is a product of its own; on Google Play a subscription's plans are base plans of one product (every plan mapped, to the same product). Not for web.
|
|
655
|
+
*/
|
|
656
|
+
store_ids?: {
|
|
657
|
+
[key: string]: StoreID;
|
|
658
|
+
};
|
|
648
659
|
};
|
|
649
660
|
/**
|
|
650
661
|
* A chart, period by period.
|
|
@@ -3966,6 +3977,10 @@ export type Plan = {
|
|
|
3966
3977
|
*/
|
|
3967
3978
|
period?: string;
|
|
3968
3979
|
price: Price;
|
|
3980
|
+
/**
|
|
3981
|
+
* The plan off sale, its product sold by its other plans; a plan someone bought is retired, not left out (plan_has_buyers).
|
|
3982
|
+
*/
|
|
3983
|
+
retired?: boolean;
|
|
3969
3984
|
};
|
|
3970
3985
|
/**
|
|
3971
3986
|
* A confirmation, with the hash of the plan shown.
|
|
@@ -3995,7 +4010,7 @@ export type PlanStep = {
|
|
|
3995
4010
|
*/
|
|
3996
4011
|
note?: string;
|
|
3997
4012
|
/**
|
|
3998
|
-
* What it does: create_product, update_product, create_price (Stripe); create_subscription_group, create_subscription, create_in_app_purchase (the App Store); create_subscription, create_onetime_product (Google Play).
|
|
4013
|
+
* What it does: create_product, update_product, create_price (Stripe); create_subscription_group, create_subscription, create_in_app_purchase, update_subscription, update_in_app_purchase (what the catalog changed of a product App Store Connect has: its text, its price — a subscription's from the day after tomorrow (UTC) on, its subscribers keeping theirs; an in-app purchase's now —, its review screenshot and note), link_subscription, link_in_app_purchase (a product App Store Connect already sells, found by its id, nothing made or changed) (the App Store); create_subscription, create_onetime_product, update_subscription, update_onetime_product, link_subscription, link_onetime_product (Google Play).
|
|
3999
4014
|
*/
|
|
4000
4015
|
op: string;
|
|
4001
4016
|
/**
|
|
@@ -4154,6 +4169,10 @@ export type Product = {
|
|
|
4154
4169
|
* How much of each limit it gives.
|
|
4155
4170
|
*/
|
|
4156
4171
|
quota?: QuotaAllowance[];
|
|
4172
|
+
/**
|
|
4173
|
+
* Off sale: no store is asked to sell it and no one new buys it, while whoever bought it keeps what it gives. A product someone bought never leaves the catalog — a save that leaves it out, or gives it another key, is refused (product_has_buyers).
|
|
4174
|
+
*/
|
|
4175
|
+
retired?: boolean;
|
|
4157
4176
|
/**
|
|
4158
4177
|
* A subscription's level in its group, 1 or more: higher is more.
|
|
4159
4178
|
*/
|
|
@@ -5490,7 +5509,7 @@ export type AppleOfferRequest = {
|
|
|
5490
5509
|
environment?: string;
|
|
5491
5510
|
};
|
|
5492
5511
|
/**
|
|
5493
|
-
* What StoreKit buys the promotional offer with, for this customer's purchases: Apple's subscription offer signature.
|
|
5512
|
+
* What StoreKit buys the promotional offer with, for this customer's purchases and their Apple account: Apple's subscription offer signature.
|
|
5494
5513
|
*/
|
|
5495
5514
|
export type AppleOfferSignature = {
|
|
5496
5515
|
/**
|
|
@@ -5521,6 +5540,10 @@ export type AppleOfferSignature = {
|
|
|
5521
5540
|
* ES256, base64.
|
|
5522
5541
|
*/
|
|
5523
5542
|
signature: string;
|
|
5543
|
+
/**
|
|
5544
|
+
* The same offer as a compact JWS (ES256 under key_id), naming one of the customer's App Store transactions (transactionId): Apple buys it for the Apple account holding that transaction alone. StoreKit buys with it since iOS 26 (Product.PurchaseOption.promotionalOffer(_:compactJWS:), back-deployed to iOS 15), and Xcode's StoreKit testing takes it alone for a key Xcode generated.
|
|
5545
|
+
*/
|
|
5546
|
+
compact_jws: string;
|
|
5524
5547
|
};
|
|
5525
5548
|
/**
|
|
5526
5549
|
* What came of an offer.
|
|
@@ -5817,6 +5840,19 @@ export type CouponRedeemedEvent = {
|
|
|
5817
5840
|
*/
|
|
5818
5841
|
one_time: boolean;
|
|
5819
5842
|
};
|
|
5843
|
+
/**
|
|
5844
|
+
* A plan's product at a store.
|
|
5845
|
+
*/
|
|
5846
|
+
export type StoreID = {
|
|
5847
|
+
/**
|
|
5848
|
+
* A Google Play subscription's base plan id.
|
|
5849
|
+
*/
|
|
5850
|
+
base_plan?: string;
|
|
5851
|
+
/**
|
|
5852
|
+
* The store's product id.
|
|
5853
|
+
*/
|
|
5854
|
+
product: string;
|
|
5855
|
+
};
|
|
5820
5856
|
/** The events this module sends the project's webhooks (x-palbase-events), by type: each one's payload, the event's data. */
|
|
5821
5857
|
export interface Events {
|
|
5822
5858
|
/**
|