@cube-nl/dmp-sdk 0.0.0-stage → 1.0.0-rc.4
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/LICENSE +21 -0
- package/README.md +720 -2
- package/dist/app.d.ts +87 -0
- package/dist/app.js +245 -0
- package/dist/auth.d.ts +117 -0
- package/dist/auth.js +321 -0
- package/dist/browser.d.ts +1 -0
- package/dist/browser.js +3 -0
- package/dist/client.d.ts +102 -0
- package/dist/client.js +220 -0
- package/dist/cube-sdk.js +2 -0
- package/dist/errors.d.ts +48 -0
- package/dist/errors.js +68 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +6 -0
- package/dist/preview.d.ts +78 -0
- package/dist/preview.js +803 -0
- package/dist/query.d.ts +9 -0
- package/dist/query.js +121 -0
- package/dist/types.d.ts +166 -0
- package/dist/types.js +6 -0
- package/dist/version.d.ts +4 -0
- package/dist/version.js +4 -0
- package/package.json +38 -4
package/dist/cube-sdk.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! @cube-nl/dmp-sdk 1.0.0-rc.4 cube-sdk.js (window.CubeApp) | MIT */
|
|
2
|
+
"use strict";(()=>{var Ae=Object.defineProperty;var ve=(e,t,n)=>t in e?Ae(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var k=(e,t,n)=>ve(e,typeof t!="symbol"?t+"":t,n);var R=class extends Error{constructor(n,r,s){let i=s!==null&&typeof s=="object"?s:{},o=A(i.detail)??A(i.error_description);super(o??`HTTP ${n}`);k(this,"status");k(this,"code");k(this,"reason");k(this,"type");k(this,"title");k(this,"detail");k(this,"errors");k(this,"retryAfter");k(this,"recordVersion");k(this,"allow");k(this,"headers");k(this,"body");this.name="DmpError",this.status=n,this.code=A(i.code)??A(i.error),this.reason=A(i.reason),this.type=A(i.type),this.title=A(i.title),this.detail=o,this.errors=$e(i.errors)?i.errors:null,this.retryAfter=De(r.get("Retry-After")),this.recordVersion=r.get("X-Dmp-Record-Version"),this.allow=r.has("Allow")?(r.get("Allow")??"").split(",").map(a=>a.trim()).filter(a=>a!==""):null,this.headers=r,this.body=s}get isProblem(){return this.code!==null}};function A(e){return typeof e=="string"?e:null}function $e(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function De(e){if(e===null||e.trim()==="")return null;let t=Number(e);if(Number.isFinite(t))return t;let n=Date.parse(e);return Number.isNaN(n)?null:Math.max(0,Math.ceil((n-Date.now())/1e3))}function te(e){return{async getAccessToken(){let t=typeof e=="function"?await e():e;return t===void 0||t===""?null:t}}}var U="cube-dmp-sdk.pending-login";function re(e){let t=e.fetch??globalThis.fetch.bind(globalThis),n=e.tokenStore??M(),r=e.pendingStore??Ue(),s=(e.refreshLeewaySeconds??30)*1e3,i=typeof e.scopes=="string"?e.scopes:e.scopes.join(" "),o=e.metadata??{},a=Oe(o)?Promise.resolve({...o,issuer:o.issuer??e.issuer}):null,p=null,d=()=>(a??(a=xe(e.issuer,t).then(c=>({...c,...o}),c=>{throw a=null,c})),a),m=async(c,u)=>{let{token_endpoint:l}=await d(),f=new URLSearchParams(c);e.clientSecret!==void 0&&f.set("client_secret",e.clientSecret);let y=await t(l,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/x-www-form-urlencoded"},body:f.toString()}),b=await ie(y);if(!y.ok||b===null||typeof b!="object"||typeof b.access_token!="string")throw new R(y.status,y.headers,b);let T={accessToken:b.access_token,refreshToken:typeof b.refresh_token=="string"?b.refresh_token:null,expiresAt:typeof b.expires_in=="number"?Date.now()+b.expires_in*1e3:null,scope:typeof b.scope=="string"?b.scope:u};return await n.set(T),T},g=()=>(p??(p=(async()=>{let c=await n.get();if(c?.refreshToken==null)throw new TypeError("No refresh token: sign in again.");try{return await m({grant_type:"refresh_token",refresh_token:c.refreshToken,client_id:e.clientId},c.scope)}catch(u){throw u instanceof R&&u.status>=400&&u.status<500&&await n.set(null),u}})().finally(()=>{p=null})),p),h=async(c={})=>{let u=await d();if(!(u.code_challenge_methods_supported??[]).includes("S256"))throw new TypeError("The authorization server does not offer PKCE S256; this SDK will not sign in without it.");let l=ne(64),f=c.state??ne(32),y=se(await crypto.subtle.digest("SHA-256",new TextEncoder().encode(l))),b={verifier:l,state:f,redirectUri:e.redirectUri,clientId:e.clientId};r.setItem(U,JSON.stringify(b));let T=new URL(u.authorization_endpoint);return T.search=new URLSearchParams({...c.extraParams,response_type:"code",client_id:e.clientId,redirect_uri:e.redirectUri,scope:i,state:f,code_challenge:y,code_challenge_method:"S256"}).toString(),T.toString()};return{discover:d,authorizationUrl:h,async signIn(c={}){let u=ee();if(u===null)throw new TypeError("signIn() needs a browser; use authorizationUrl() and redirect yourself.");u.assign(await h(c))},async handleCallback(c){let u=c??ee()?.href;if(u===void 0)throw new TypeError("handleCallback() needs the callback URL outside a browser.");let l=new URL(u),f=new URLSearchParams(l.search);if(!Me(l,e.redirectUri)||!f.has("code")&&!f.has("error"))return null;if(c===void 0){for(let b of["code","state","error","error_description","iss"])l.searchParams.delete(b);globalThis.history?.replaceState(null,"",l.pathname+l.search+l.hash)}let y=Ee(r.getItem(U));if(y===null||f.get("state")!==y.state)throw new TypeError("The state does not match: this callback does not belong to a sign-in started here.");if(r.removeItem(U),f.has("error"))throw new R(400,new Headers,{error:f.get("error")??"access_denied",error_description:f.get("error_description")??void 0});return m({grant_type:"authorization_code",code:f.get("code")??"",redirect_uri:y.redirectUri,client_id:y.clientId,code_verifier:y.verifier},i)},refresh:g,async getTokens(){return n.get()},async setTokens(c){await n.set(c)},async signOut(){await n.set(null),r.removeItem(U)},async getAccessToken(){let c=await n.get();if(c===null)return null;if(c.expiresAt!==null&&c.expiresAt-s<=Date.now()&&c.refreshToken!==null)try{return(await g()).accessToken}catch{return(await n.get())?.accessToken??null}return c.accessToken},async onUnauthorized(){if((await n.get())?.refreshToken==null)return!1;try{return await g(),!0}catch{return!1}}}}function Oe(e){return typeof e.authorization_endpoint=="string"&&typeof e.token_endpoint=="string"}async function xe(e,t=globalThis.fetch.bind(globalThis)){let n=`${e.replace(/\/+$/,"")}/.well-known/oauth-authorization-server`,r=await t(n,{headers:{Accept:"application/json"}}),s=await ie(r);if(!r.ok||s===null||typeof s!="object")throw new R(r.status,r.headers,s);return s}function M(e=null){let t=e;return{get:()=>t,set:n=>{t=n}}}function Pe(){let e=new Map;return{getItem:t=>e.get(t)??null,setItem:(t,n)=>{e.set(t,n)},removeItem:t=>{e.delete(t)}}}function Ue(){try{let e=globalThis.sessionStorage;if(e!==void 0)return e}catch{}return Pe()}function ee(){let e=globalThis.location;return e!==void 0&&typeof e.assign=="function"?e:null}function Me(e,t){let n=new URL(t),r=s=>s.replace(/\/+$/,"")||"/";return e.origin===n.origin&&r(e.pathname)===r(n.pathname)}function Ee(e){if(e===null)return null;try{let t=JSON.parse(e);return typeof t.verifier=="string"&&typeof t.state=="string"&&typeof t.redirectUri=="string"&&typeof t.clientId=="string"?t:null}catch{return null}}async function ie(e){let t=await e.text();if(t==="")return null;try{return JSON.parse(t)}catch{return t}}function ne(e){return se(crypto.getRandomValues(new Uint8Array(e)))}function se(e){let t=e instanceof Uint8Array?e:new Uint8Array(e),n="";for(let r of t)n+=String.fromCharCode(r);return btoa(n).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}var Ce=new Set(["in","between"]);function v(e,t={}){let n=new URLSearchParams;je(n,e,t.fields),Ie(n,t.filter),O(n,"sort",t.sort),O(n,"include",t.include),O(n,"with_count",t.withCount),Ne(n,t.has),O(n,"fn",t.fn),O(n,"group",t.group),t.page?.size!==void 0&&n.append("page[size]",String(t.page.size)),t.page?.number!==void 0&&n.append("page[number]",String(t.page.number));for(let[r,s]of Object.entries(t.params??{}))n.append(r,s);return n.toString().replace(/%5B/gi,"[").replace(/%5D/gi,"]").replace(/%2C/gi,",").replace(/%28/gi,"(").replace(/%29/gi,")")}function je(e,t,n){if(n===void 0)return;let r=ze(n)?{[t]:n}:n;for(let[s,i]of Object.entries(r))i.length>0&&e.append(`fields[${s}]`,i.join(","))}function Ie(e,t){for(let[n,r]of Object.entries(t??{})){if(Array.isArray(r))throw new TypeError(`filter.${n}: a bare list names no operator and cube/api refuses it. Write { in: [...] } (or { between: [from, to] }).`);if(r==null)throw new TypeError(`filter.${n}: write { is: 'null' } or { is: 'not_null' } to filter on null.`);if(typeof r!="object"){e.append(`filter[${n}]`,q(r));continue}for(let[s,i]of Object.entries(r))i!==void 0&&e.append(`filter[${n}][${s}]`,Le(n,s,i))}}function Le(e,t,n){if(n===null){if(t!=="is")throw new TypeError(`filter.${e}.${t}: null is only meaningful under 'is'.`);return"null"}if(Array.isArray(n)){if(!Ce.has(t))throw new TypeError(`filter.${e}.${t}: takes one value; only 'in' and 'between' take a list.`);return n.map(q).join(",")}return q(n)}function Ne(e,t){for(let[n,r]of Object.entries(t??{}))r===!0?e.append(`has[${n}]`,"1"):typeof r=="number"?e.append(`has[${n}][min]`,String(r)):(r.min!==void 0&&e.append(`has[${n}][min]`,String(r.min)),r.max!==void 0&&e.append(`has[${n}][max]`,String(r.max)))}function O(e,t,n){if(n===void 0)return;let r=typeof n=="string"?n:n.filter(s=>s!=="").join(",");r!==""&&e.append(t,r)}function q(e){return typeof e=="boolean"?e?"1":"0":String(e)}function ze(e){return Array.isArray(e)}var Fe=/^[a-z][a-z0-9_]*$/;function ae(e){let t=e.baseUrl.replace(/\/+$/,""),n=e.fetch??globalThis.fetch.bind(globalThis),r=e.auth===void 0?null:typeof e.auth=="string"||typeof e.auth=="function"?te(e.auth):e.auth,s=e.cache??(typeof document<"u"?"no-cache":void 0),i=async(a,p,{query:d,body:m,headers:g={},signal:h}={})=>{let c=`${t}${p.startsWith("/")?p:`/${p}`}${d?`?${d}`:""}`,u=async()=>{let b=r===null?null:await r.getAccessToken(),T={method:a,headers:{Accept:"application/json",...e.headers,...b!==null?{Authorization:`Bearer ${b}`}:{},...m!==void 0?{"Content-Type":"application/json"}:{},...g}};m!==void 0&&(T.body=JSON.stringify(m)),s!==void 0&&(T.cache=s),h!==void 0&&(T.signal=h);let Se=Date.now(),Z=await n(c,T);return e.onResponse?.({method:a,url:c,status:Z.status,ms:Date.now()-Se}),Z},l=await u();l.status===401&&r?.onUnauthorized!==void 0&&await r.onUnauthorized()&&(await l.body?.cancel().catch(()=>{}),l=await u());let f=await Qe(l);if(!l.ok)throw new R(l.status,l.headers,f);let y=Ve(f)?f:null;return{status:l.status,data:y!==null?y.data:f,meta:y?.meta??null,headers:l.headers,etag:l.headers.get("ETag"),recordVersion:l.headers.get("X-Dmp-Record-Version"),location:l.headers.get("Location"),replayed:l.headers.get("Idempotency-Replayed")==="true",rateLimit:Ke(l.headers)}};return{async capabilities(a={}){let p=(await i("GET","/_capabilities",C({},a))).data;return{...p,resources:p.resources.map(qe)}},resources:a=>{if(!Fe.test(a))throw new TypeError(`'${a}' is not a resource name: cube/api names match [a-z][a-z0-9_]*.`);let p=`/${a}`,d=u=>{let l=String(u);if(l==="")throw new TypeError(`${a}: an empty id would address the collection, not a record.`);if(le(l))throw new TypeError(`${a}: '${l}' is not an id: it would address another path, not a record.`);return`${p}/${encodeURIComponent(l)}`},m=u=>u===void 0?"":v(a,{fields:u}),g=async(u={},l={})=>await i("GET",p,C({query:v(a,u)},l)),h=async function*(u={},l={}){let f=u.page?.number??1;for(;;){let y=await g({...u,page:{...u.page,number:f}},l);yield y;let b=y.meta.page;if(b==null||b.number>=b.pages||y.data.length===0)return;f=b.number+1}},c=async(u,l,f)=>{let y=await i("POST",u,{query:m(f.fields),body:l??{},headers:E(f)});return{...y,data:y.status===204?null:y.data,accepted:y.status===202}};return{name:a,list:g,pages:h,async*all(u={},l={}){for await(let f of h(u,l))yield*f.data},get:(u,l={},f={})=>i("GET",d(u),C({query:v(a,l)},f)),aggregate:(u,l={})=>i("GET",`${p}/_aggregate`,C({query:v(a,u)},l)),create:(u,l={})=>i("POST",p,{query:m(l.fields),body:u,headers:E(l)}),update:(u,l,f={})=>i("PATCH",d(u),{query:m(f.fields),body:l,headers:E(f)}),delete:async(u,l={})=>({...await i("DELETE",d(u),{headers:E(l)}),data:null}),operation:(u,l,f,y={})=>c(`${d(u)}/${encodeURIComponent(oe(l))}`,f,y),collectionOperation:(u,l,f={})=>c(`${p}/_operations/${encodeURIComponent(oe(u))}`,l,f)}},request:i}}function qe(e){return{...e,operations:(e.operations??[]).map(t=>({...t,input:Q(t.input)})),aggregatable:Q(e.aggregatable),relations:Q(e.relations)}}function Q(e){return Array.isArray(e)||e===void 0||e===null?{}:e}function oe(e){if(e===""||e.startsWith("_"))throw new TypeError(`'${e}' is not an operation name: the '_' namespace is reserved by the API.`);if(le(e))throw new TypeError(`'${e}' is not an operation name: it would address another path.`);return e}function le(e){return e==="."||e===".."}function E(e){let t={};if(e.idempotencyKey!==void 0&&(t["Idempotency-Key"]=e.idempotencyKey),e.ifMatch!==void 0){if(e.ifMatch.startsWith("W/"))throw new TypeError("ifMatch takes recordVersion (X-Dmp-Record-Version), not the weak ETag.");t["If-Match"]=e.ifMatch}return t}function C(e,t){return t.signal===void 0?e:{...e,signal:t.signal}}async function Qe(e){let t=await e.text();if(t==="")return null;try{return JSON.parse(t)}catch{return t}}function Ve(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)&&"data"in e}function Ke(e){let t=n=>{let r=e.get(n);return r===null||r===""||!Number.isFinite(Number(r))?null:Number(r)};return{limit:t("X-RateLimit-Limit"),remaining:t("X-RateLimit-Remaining"),reset:t("X-RateLimit-Reset")}}var ue=/^[a-z][a-z0-9_]*$/,ce=["eq","neq","gt","gte","lt","lte","in","like","ilike","is","between"],Ge=[[">=","gte"],["<=","lte"],["<>","neq"],[">","gt"],["<","lt"],["=","eq"]],H=["count","sum","avg","min","max"],de=["day","week","month","quarter","year"],V=20,K=5,ge=1e3,G=200,We=50,W=200,Je="1.0",j=class{constructor(t,n=null,r=()=>{}){k(this,"manifest",n);k(this,"guard",r);k(this,"tables",new Map);for(let[s,i]of Object.entries(He(t))){if(typeof i!="object"||i===null||Array.isArray(i))continue;let o=typeof i.schema?.name=="string"&&i.schema.name!==""?i.schema.name:s;ue.test(o)&&this.tables.set(o,Xe(o,i))}}resourceNames(){return[...this.tables.keys()]}resource(t){if(!ue.test(t))throw new TypeError(`'${t}' is not a resource name: cube/api names match [a-z][a-z0-9_]*.`);let n=(i,o)=>Promise.resolve().then(()=>o(this.authorize(t,i))),r=(i={})=>n("list",o=>Ye(o,i)),s=async function*(i={}){let o=i.page?.number??1;for(;;){let a=await r({...i,page:{...i.page,number:o}});if(yield a,a.meta.page.number>=a.meta.page.pages||a.data.length===0)return;o=a.meta.page.number+1}};return{name:t,list:r,pages:s,async*all(i={}){for await(let o of s(i))yield*o.data},get:(i,o={})=>n("get",a=>Ze(a,i,o)),aggregate:i=>n("aggregate",o=>on(o,i)),create:(i,o={})=>n("create",a=>cn(a,i,o)),update:(i,o,a={})=>n("update",p=>dn(p,i,o,a)),delete:(i,o={})=>n("delete",a=>gn(a,i,o)),operation:(i,o,a,p)=>n("get",d=>{throw L(d,i),me(o)}),collectionOperation:i=>n("list",()=>{throw me(i)})}}authorize(t,n){this.guard();let r=this.tables.get(t);if(r===void 0)throw w(404,"not_found",`Unknown API resource [${t}].`);let s=this.manifest?.uses;if(s!==void 0&&!(s[t]??[]).includes(n))throw w(403,"client_scope_exceeded",`This app may not ${n} '${t}': add it to "uses" in cube-app.json.`);return r}};function He(e){let t=e.resources;return Object.keys(e).length===1&&x(t)&&!("rows"in t)&&!("schema"in t)?t:e}function Xe(e,t){let n=t.schema??{},r=(t.rows??[]).map(g=>B(g)),s=new Map,i=Array.isArray(n.fields)?n.fields.map(g=>[g.name,g]):Object.entries(n.fields??{});for(let[g,h]of i)s.set(g,Be(g,h));for(let g of s.size===0?r:[])for(let h of Object.keys(g))s.has(h)||s.set(h,{name:h,types:[],nullable:!0});let o=[...s.keys()],a=(x(n.choices)?Object.entries(n.choices):[]).filter(g=>x(g[1])&&Array.isArray(g[1].values)),p=new Set(["id",...n.readonly??[],...i.filter(([,g])=>g.readonly===!0).map(([g])=>g),...a.filter(([,g])=>g.writable===!1).map(([g])=>g)]),d=new Map;for(let[g,h]of i)Array.isArray(h.enum)&&d.set(g,h.enum);for(let[g,h]of a)d.set(g,h.values);let m=g=>Array.isArray(g)?g.filter(h=>typeof h=="string"&&s.has(h)):null;return{name:e,fields:s,filterable:m(n.filters)??o,groupable:m(n.groupable)??o,aggregatable:new Map(x(n.aggregatable)?Object.entries(n.aggregatable).filter(([g,h])=>s.has(g)&&Array.isArray(h)).map(([g,h])=>[g,[...h]]):o.map(g=>[g,[...H]])),choices:d,writable:new Set(o.filter(g=>!p.has(g))),summary:(m(n.summary_fields)??[]).length>0?m(n.summary_fields):o,rows:r,versions:new Map(r.map(g=>[S(g.id),1])),replays:new Map}}function Be(e,t){let n=t.type,r=typeof n=="string"?n.split("|"):Array.isArray(n)?[...n]:[];return{name:e,types:r.filter(s=>s!=="null"&&s!==""),nullable:t.nullable===!0||r.includes("null")}}function P(e,t){return new URLSearchParams(v(e.name,t))}function Ye(e,t){let n=P(e,t),r=I(e,n),s=he(e,n),i=rn(e,n),{size:o,number:a}=sn(n),p=ye(e,n),d=e.rows.filter(c=>s.every(u=>we(e,c,u)));i.length>0&&(d=[...d].sort((c,u)=>{for(let{field:l,descending:f}of i){let y=_(c[l],u[l]);if(y!==0)return f?-y:y}return 0}));let m=d.length,g=d.slice((a-1)*o,a*o).map(c=>N(c,r)),h={size:o,number:a,total:m,pages:Math.max(1,Math.ceil(m/o))};return{...D(200,g,$(e,h,p)),meta:{...$(e,h,p),page:h}}}function Ze(e,t,n){let r=P(e,n),s=I(e,r),i=ye(e,r),o=L(e,t);return D(200,N(o,s),$(e,null,i),z(e,o))}function I(e,t){let n=`fields[${e.name}]`,r=t.get(n);if(r===null||r==="")return e.summary;let s=r.split(",").map(o=>o.trim()),i=s.filter(o=>!e.fields.has(o));if(i.length>0)throw w(422,"query_invalid",`Unknown ${n}: ${i.join(", ")}.`,{[n]:[`Allowed values: ${[...e.fields.keys()].join(", ")}.`]});return s}function ye(e,t){let n=r=>(t.get(r)??"").split(",").filter(s=>s!=="");return[...new Set([...n("include"),...n("with_count")])]}function he(e,t){let n=[],r=[];t.forEach((s,i)=>r.push([i,s]));for(let[s,i]of r){let o=/^filter\[([^\]]+)\](?:\[([^\]]+)\])?$/.exec(s);if(o===null)continue;let a=o[1];if(!e.filterable.includes(a))throw w(422,"query_invalid",`Unknown filter: ${a}.`,{filter:[`Allowed values: ${e.filterable.join(", ")}.`]},"not_filterable");n.push(o[2]===void 0?en(a,i):nn(a,o[2],i))}if(n.length>V)throw w(422,"query_invalid",`A query takes at most ${V} filters.`,{filter:[`Sent ${n.length}; the limit is ${V}.`]});return n}function en(e,t){for(let[n,r]of Ge)if(t.startsWith(n))return{field:e,operator:r,value:t.slice(n.length)};return{field:e,operator:"eq",value:t}}function nn(e,t,n){if(!ce.includes(t))throw w(422,"query_invalid",`Unknown filter operator: ${t}.`,{filter:[`Allowed operators: ${ce.join(", ")}.`]});if(t==="between"&&n.split(",").length!==2)throw w(422,"query_invalid",`filter[${e}][between] takes exactly 2 values.`,{filter:[`Write filter[${e}][between]=low,high. Received ${n.split(",").length} value(s).`]});if(t==="in"&&n.split(",").length>G)throw w(422,"query_invalid",`filter[${e}][in] takes at most ${G} values.`,{filter:[`Sent ${n.split(",").length}; the limit is ${G}.`]});if(t==="is"&&n!=="null"&&n!=="not_null")throw w(422,"query_invalid",`filter[${e}][is] takes null or not_null.`,{filter:[`Received: ${n}.`]});return{field:e,operator:t,value:n}}function we(e,t,n){let r=t[n.field],s=i=>yn(e.fields.get(n.field),i.trim());switch(n.operator){case"eq":return J(r,s(n.value));case"neq":return r!=null&&!J(r,s(n.value));case"gt":return r!=null&&_(r,s(n.value))>0;case"gte":return r!=null&&_(r,s(n.value))>=0;case"lt":return r!=null&&_(r,s(n.value))<0;case"lte":return r!=null&&_(r,s(n.value))<=0;case"in":return n.value.split(",").some(i=>J(r,s(i)));case"between":{let[i,o]=n.value.split(",").map(s);return r!=null&&_(r,i)>=0&&_(r,o)<=0}case"like":case"ilike":return typeof r=="string"&&tn(r,n.value);case"is":return n.value==="null"?r==null:r!=null;default:return!1}}function tn(e,t){let n=t.split("%").map(r=>r.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")).join(".*");return new RegExp(n,"iu").test(e)}function rn(e,t){let n=t.get("sort");if(n===null||n==="")return[];let r=n.split(",").map(i=>i.trim()).filter(i=>i!=="").map(i=>({field:i.replace(/^[-+]/,""),descending:i.startsWith("-")})),s=r.map(i=>i.field).filter(i=>!e.fields.has(i));if(s.length>0)throw w(422,"query_invalid",`Unknown sort: ${s.join(", ")}.`,{sort:[`Allowed values: ${[...e.fields.keys()].join(", ")}.`]},"not_sortable");return r}function sn(e){let t=r=>{let s=e.get(r);if(s===null||s==="")return null;if(!/^\d+$/.test(s))throw w(422,"query_invalid",`${r} must be a positive integer.`,{[r]:["Expected a positive integer."]});return Number(s)},n=t("page[size]")??We;if(n>W)throw w(422,"cardinality_exceeded",`page[size] may not exceed ${W}.`,{"page.size":[`The maximum page size is ${W}.`]});return{size:Math.max(1,n),number:Math.max(1,t("page[number]")??1)}}function on(e,t){let n=P(e,t),r=(n.get("fn")??"").trim();if(r==="")throw w(422,"query_invalid","fn is required: name at least one aggregation function.",{fn:[`Expected e.g. fn=count or fn=sum(field). ${X(e)}`]});let s=r.split(",").map(c=>c.trim()),i=s.map(c=>an(e,c));pe(s,"fn");let o=(n.get("group")??"").trim(),a=o===""?[]:o.split(",").map(c=>c.trim());if(a.length>K)throw w(422,"cardinality_exceeded",`group may not exceed ${K} dimensions, ${a.length} given.`,{group:[`Use at most ${K} dimensions.`]});let p=a.map(c=>ln(e,c));pe(a,"group");let d=he(e,n),m=new Map;for(let c of e.rows.filter(u=>d.every(l=>we(e,u,l)))){let u={};for(let[y,b]of a.entries())u[b]=p[y](c);let l=JSON.stringify(Object.values(u)),f=m.get(l)??{key:u,rows:[]};f.rows.push(c),m.set(l,f)}if(a.length===0&&m.size===0&&m.set("[]",{key:{},rows:[]}),m.size>ge)throw w(422,"cardinality_exceeded","The grouping produces more groups than the configured maximum.",{groepen:["The grouping produces more groups than the configured maximum.",`Allowed values: groepeer grover, of filter eerst; maximaal ${ge} groepen.`]},"too_many_groups");let h=[...m.values()].sort((c,u)=>{for(let l of a){let f=_(c.key[l],u.key[l]);if(f!==0)return f}return 0}).map(({key:c,rows:u})=>{let l={...c};for(let[f,y]of s.entries())l[y]=i[f](u);return l});return D(200,h,{...$(e,null,[]),groups:h.length})}function pe(e,t){let n=[...new Set(e.filter((r,s)=>e.indexOf(r)!==s))];if(n.length>0)throw w(422,"query_invalid",`Duplicate ${t} entry: ${n.join(", ")}.`,{[t]:[`List each ${t} entry once.`]})}function X(e){return e.aggregatable.size===0?"This resource has no aggregatable fields; only bare count is available.":`Aggregatable fields: ${[...e.aggregatable.keys()].join(", ")}.`}function an(e,t){let n=/^([a-z]+)\(([a-z0-9_]+)\)$/.exec(t);if(n===null&&!/^[a-z]+$/.test(t))throw w(422,"query_invalid",`Unparseable fn entry: ${t}.`,{fn:["Expected `count` or `function(field)`, e.g. sum(field)."]});let r=n===null?t:n[1],s=n===null?null:n[2];if(!H.includes(r))throw w(422,"query_invalid",`Unknown aggregation function: ${r}.`,{fn:[`Allowed values: ${H.join(", ")}.`]});if(s===null){if(r!=="count")throw w(422,"query_invalid",`${r} must be applied to a field, e.g. ${r}(field).`,{fn:[X(e)]});return o=>o.length}let i=e.aggregatable.get(s);if(i===void 0)throw w(422,"query_invalid",`Unknown aggregatable field: ${s}.`,{fn:[X(e)]});if(!i.includes(r))throw w(422,"query_invalid",`${r} is not allowed on ${s}.`,{fn:[`Allowed functions for ${s}: ${i.join(", ")}.`]});return o=>{let a=o.map(d=>d[s]).filter(d=>d!=null),p=a.map(Number).filter(d=>Number.isFinite(d));switch(r){case"count":return o.length;case"sum":return p.length===0?null:p.reduce((d,m)=>d+m,0);case"avg":return p.length===0?null:p.reduce((d,m)=>d+m,0)/p.length;case"min":return a.length===0?null:a.reduce((d,m)=>_(m,d)<0?m:d);default:return a.length===0?null:a.reduce((d,m)=>_(m,d)>0?m:d)}}}function ln(e,t){let n=t.split(".");if(n.length>2)throw w(422,"query_invalid",`Unparseable group entry: ${t}.`,{group:["Expected `field` or `field.unit`, e.g. published_at.month."]});let[r,s]=n;if(!e.groupable.includes(r))throw w(422,"query_invalid",`Unknown group: ${r}.`,{group:[`Allowed values: ${e.groupable.join(", ")}.`]},"not_groupable");if(s===void 0)return i=>i[r]??null;if(!de.includes(s))throw w(422,"query_invalid",`Unknown truncation unit: ${s}.`,{group:[`Allowed values: ${de.join(", ")}.`]});return i=>un(i[r],s)}function un(e,t){let n=typeof e=="string"?/^(\d{4})-(\d{2})-(\d{2})/.exec(e):null;if(n===null)return null;let[r,s,i]=[Number(n[1]),Number(n[2]),Number(n[3])],o=new Date(Date.UTC(r,s-1,i));if(o.getUTCFullYear()!==r||o.getUTCMonth()!==s-1||o.getUTCDate()!==i)return null;switch(t){case"day":return n[0];case"week":{let a=new Date(o.getTime()+(3-(o.getUTCDay()+6)%7)*864e5),p=a.getUTCFullYear(),d=1+Math.floor((a.getTime()-Date.UTC(p,0,1))/(7*864e5));return`${p}-W${String(d).padStart(2,"0")}`}case"month":return n[0].slice(0,7);case"quarter":return`${n[1]}-Q${Math.ceil(s/3)}`;case"year":return n[1];default:return null}}function cn(e,t,n){let r=n.fields===void 0?e.summary:I(e,P(e,{fields:n.fields})),s=JSON.stringify([t,n.fields??null]);if(n.idempotencyKey!==void 0){let p=e.replays.get(n.idempotencyKey);if(p!==void 0){if(p.body!==s)throw w(422,"validation_failed","This Idempotency-Key was already used with a different request body. Use a new key, or resend the original body.");return{...p.response,replayed:!0}}}let i=be(e,t),o={id:mn(e),...i};for(let p of e.fields.keys())p in o||(o[p]=null);e.rows.push(o),e.versions.set(S(o.id),1);let a=D(201,N(o,r),$(e,null,[]),z(e,o));return n.idempotencyKey!==void 0&&e.replays.set(n.idempotencyKey,{body:s,response:a}),a}function dn(e,t,n,r){let s=r.fields===void 0?e.summary:I(e,P(e,{fields:r.fields})),i=L(e,t);return ke(e,i,r.ifMatch),Object.assign(i,be(e,n)),e.versions.set(S(i.id),(e.versions.get(S(i.id))??1)+1),D(200,N(i,s),$(e,null,[]),z(e,i))}function gn(e,t,n){let r=L(e,t);return ke(e,r,n.ifMatch),e.rows.splice(e.rows.indexOf(r),1),e.versions.delete(S(r.id)),D(204,null,null)}function be(e,t){let n=[...e.writable],r=n.length===0?"This resource declares no writable fields.":`Writable fields: ${n.join(", ")}.`;if(!x(t)||Object.keys(t).length===0)throw w(422,"validation_failed","The request body must be a JSON object naming at least one writable field.",{body:[r]});let s=Object.keys(t).filter(o=>!e.writable.has(o));if(s.length>0)throw w(422,"query_invalid",`Unknown or read-only fields: ${s.join(", ")}.`,Object.fromEntries(s.map(o=>[o,[r]])));let i={};for(let[o,a]of Object.entries(t)){let p=e.fields.get(o),d=fn(p,a)??pn(e,p,o,a);d!==null&&(i[o]=[d])}if(Object.keys(i).length>0)throw w(422,"validation_failed","The request body failed validation.",i);return B(t)}function pn(e,t,n,r){let s=e.choices.get(n);return s===void 0||r===null&&t?.nullable===!0||s.includes(r)?null:`The selected ${n.replace(/_/g," ")} is invalid.`}function fn(e,t){return e===void 0||e.types.length!==1?null:t===null?e.nullable?null:fe(e):(()=>{switch(e.types[0]){case"string":return typeof t=="string";case"integer":return typeof t=="number"&&Number.isInteger(t)||typeof t=="string"&&/^[+-]?\d+$/.test(t.trim());case"number":return typeof t=="number"&&Number.isFinite(t)||typeof t=="string"&&t.trim()!==""&&Number.isFinite(Number(t));case"boolean":return[!0,!1,0,1,"0","1"].includes(t);case"array":case"object":return typeof t=="object";default:return!0}})()?null:fe(e)}function fe(e){return`The ${e.name.replace(/_/g," ")} field must be ${{string:"a string",integer:"an integer",number:"a number",boolean:"true or false",array:"an array",object:"an array"}[e.types[0]]??"valid"}.`}function ke(e,t,n){if(n===void 0)return;if(n.startsWith("W/"))throw new TypeError("ifMatch takes recordVersion (X-Dmp-Record-Version), not the weak ETag.");let r=z(e,t);if(n!==r)throw w(409,"conflict","The record changed since the version you sent.",void 0,void 0,{"X-Dmp-Record-Version":r})}function mn(e){let t=e.rows.map(n=>n.id);return t.length>0&&t.every(n=>typeof n=="string")&&t.some(n=>!/^\d+$/.test(n))?crypto.randomUUID():t.reduce((n,r)=>Math.max(n,Number(r)||0),0)+1}function L(e,t){let n=S(t);if(n===""||n==="."||n==="..")throw new TypeError(`${e.name}: '${n}' is not an id.`);let r=e.rows.find(s=>S(s.id)===n);if(r===void 0)throw w(404,"not_found",`No such ${e.name} record.`);return r}function me(e){return w(404,"not_found",`No such operation [${e}]. The preview runs no custom operations.`)}function N(e,t){let n={};for(let r of t)n[r]=r in e?B(e[r]):null;return n}function z(e,t){return`"preview-${S(t.id)}-${e.versions.get(S(t.id))??1}"`}function $(e,t,n){return{resource:e.name,contract:Je,page:t,groups:null,omitted:n,pruned:[],forbidden_fields:[]}}function D(e,t,n,r=null){let s=new Headers({"X-Dmp-Preview":"true"});return r!==null&&s.set("X-Dmp-Record-Version",r),{status:e,data:t,meta:n,headers:s,etag:null,recordVersion:r,location:null,replayed:!1,rateLimit:{limit:null,remaining:null,reset:null}}}function w(e,t,n,r,s,i={}){return new R(e,new Headers({"Content-Type":"application/problem+json",...i}),{type:`https://docs.cube.nl/dmp-api/problems/${t}`,title:t.replace(/_/g," "),status:e,code:t,...s!==void 0?{reason:s}:{},detail:n,...r!==void 0?{errors:r}:{}})}function yn(e,t){let n=e?.types[0];if((n==="integer"||n==="number")&&t!==""&&Number.isFinite(Number(t)))return Number(t);if(n==="boolean"){if(t==="1"||t==="true")return!0;if(t==="0"||t==="false")return!1}return t}function J(e,t){return e==null?!1:typeof e=="boolean"&&typeof t!="boolean"?(e?"1":"0")===String(t)||String(e)===String(t):typeof e=="number"&&typeof t=="string"?String(e)===t:e===t||typeof e!="object"&&String(e)===String(t)}function _(e,t){if(e==null||t==null)return e==null?t==null?0:-1:1;if(typeof e=="number"&&typeof t=="number")return e-t;if(typeof e=="boolean"||typeof t=="boolean")return+(e===!0||e===1||e==="1")-+(t===!0||t===1||t==="1");let n=String(e),r=String(t);return n<r?-1:n>r?1:0}function S(e){return e==null?"":String(e)}function x(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function B(e){return e===void 0?e:JSON.parse(JSON.stringify(e))}var Re="1.0.0-rc.4";var wn="./cube-app.config.json",bn="./cube-mock.json",kn="./cube-app.json",Rn={id:"preview",name:"Voorbeeldgebruiker",scopes:[]},Te=null;async function Tn(e={}){let t=e.fetch??globalThis.fetch.bind(globalThis),n=e.pageUrl??xn(),r=await Y(t,F(wn,n));return r===null?_n(e,t,n):Sn($n(r),e,t,n)}async function _n(e,t,n){let r=e.mock??Te??await Y(t,F(bn,n))??{},s=e.manifest===!1?null:e.manifest??await Y(t,F(kn,n)),i=!0,o=new j(r,s,()=>{if(!i)throw new R(401,new Headers({"Content-Type":"application/problem+json"}),{type:"https://docs.cube.nl/dmp-api/problems/token_invalid",title:"token invalid",status:401,code:"token_invalid",detail:"Unauthenticated."})});return{mode:"preview",signInError:null,user:()=>i?{...Rn}:null,async signIn(){i=!0},async signOut(){i=!1},resource:a=>o.resource(a)}}async function Sn(e,t,n,r){let s=e.dmpUrl.replace(/\/+$/,""),i=null,o=Dn(t.tokenStorage==="memory"?M():On(`cube-app.tokens.${e.clientId}`),m=>{i=m}),a=re({issuer:e.issuer??s,clientId:e.clientId,redirectUri:F(e.redirectUri,r),scopes:e.scopes,fetch:n,tokenStore:o,...e.metadata!==void 0?{metadata:e.metadata}:{}}),p=ae({baseUrl:e.apiUrl??`${s}/apps/api/v1`,auth:a,fetch:n}),d=null;i=await o.get();try{await a.handleCallback()}catch(m){d=m instanceof Error?m:new Error(String(m))}return i===null&&d===null&&t.autoSignIn===!0&&await a.signIn(),{mode:"live",get signInError(){return d},user:()=>i===null?null:An(i),async signIn(){d=null,await a.signIn()},async signOut(){await a.signOut()},resource:m=>p.resources(m)}}function An(e){let t=vn(e.accessToken),n=t?.sub,r=Array.isArray(t?.scopes)?t.scopes.map(String):null;return{id:typeof n=="string"||typeof n=="number"?String(n):null,name:null,scopes:r??(e.scope??"").split(" ").filter(s=>s!=="")}}function vn(e){let t=e.split(".")[1];if(t===void 0)return null;try{let n=t.replace(/-/g,"+").replace(/_/g,"/"),r=atob(n.padEnd(n.length+(4-n.length%4)%4,"=")),s=new TextDecoder().decode(Uint8Array.from(r,o=>o.charCodeAt(0))),i=JSON.parse(s);return i!==null&&typeof i=="object"?i:null}catch{return null}}function $n(e){let t=["dmpUrl","clientId","redirectUri"].filter(r=>typeof e[r]!="string"||e[r]===""),n=e.scopes;if(typeof n=="string"||Array.isArray(n)&&n.every(r=>typeof r=="string")||t.push("scopes"),t.length>0)throw new TypeError(`cube-app.config.json is incomplete: ${t.join(", ")} missing or not a string.`);return e}async function Y(e,t){let n;try{n=await e(t,{headers:{Accept:"application/json"},...typeof document<"u"?{cache:"no-store"}:{}})}catch{return null}if(n.status===404||n.status===410)return null;if(!n.ok)throw new Error(`${t} answered ${n.status}.`);try{let r=JSON.parse(await n.text());return r!==null&&typeof r=="object"&&!Array.isArray(r)?r:null}catch{return null}}function Dn(e,t){return{async get(){let n=await e.get();return t(n),n},async set(n){await e.set(n),t(n)}}}function On(e){let t=(()=>{try{return globalThis.sessionStorage??null}catch{return null}})();return t===null?M():{get(){try{let n=t.getItem(e);return n===null?null:JSON.parse(n)}catch{return null}},set(n){try{n===null?t.removeItem(e):t.setItem(e,JSON.stringify(n))}catch{}}}}function xn(){let e=globalThis;return e.document?.baseURI??e.location?.href??"http://localhost/"}function F(e,t){return new URL(e,t).href}function Pn(e){Te=e}var _e=Object.freeze({connect:Tn,mock:Pn,version:Re,sdk:"1"});globalThis.CubeApp=_e;})();
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The codes cube/api promises (README, "Errors"). The list is closed on the API side; `code` on a
|
|
3
|
+
* DmpError is one of these, an OAuth `error` from the token endpoint, or null for a response that
|
|
4
|
+
* was not a problem at all (a proxy's 502 page, say). Decide on `code`, never on `detail`.
|
|
5
|
+
*/
|
|
6
|
+
export type ErrorCode = 'token_invalid' | 'resource_forbidden' | 'client_scope_exceeded' | 'client_inactive' | 'client_without_user' | 'act_as_forbidden' | 'act_as_unknown' | 'not_found' | 'unsupported_action' | 'conflict' | 'query_invalid' | 'validation_failed' | 'cardinality_exceeded' | 'field_forbidden' | 'rate_limited' | (string & {});
|
|
7
|
+
/** The finer classification under `code`, when the engine gives one. Absent means "none finer". */
|
|
8
|
+
export type ErrorReason = 'resource_forbidden' | 'field_forbidden' | 'unknown_resource' | 'too_many_groups' | 'too_many_dimensions' | 'too_many_relations' | 'limit_too_high' | 'unknown_field' | 'unknown_operator' | 'not_filterable' | 'not_sortable' | 'unknown_sort_direction' | 'not_groupable' | 'not_aggregatable' | 'unknown_function' | 'unknown_truncation' | 'unknown_relation' | 'include_too_deep' | 'cursor_invalid' | 'not_countable' | 'empty_range' | 'value_shape_invalid' | 'contract_unsupported' | (string & {});
|
|
9
|
+
/** An RFC 9457 problem document as cube/api writes it, or an RFC 6749 OAuth error. */
|
|
10
|
+
export interface ProblemBody {
|
|
11
|
+
type?: string;
|
|
12
|
+
title?: string;
|
|
13
|
+
status?: number;
|
|
14
|
+
code?: string;
|
|
15
|
+
reason?: string;
|
|
16
|
+
detail?: string;
|
|
17
|
+
errors?: Record<string, string[]>;
|
|
18
|
+
error?: string;
|
|
19
|
+
error_description?: string;
|
|
20
|
+
[extension: string]: unknown;
|
|
21
|
+
}
|
|
22
|
+
/** A refusal. Thrown for every non-2xx answer (and for a 304 nobody asked for). */
|
|
23
|
+
export declare class DmpError extends Error {
|
|
24
|
+
readonly status: number;
|
|
25
|
+
readonly code: ErrorCode | null;
|
|
26
|
+
readonly reason: ErrorReason | null;
|
|
27
|
+
readonly type: string | null;
|
|
28
|
+
readonly title: string | null;
|
|
29
|
+
readonly detail: string | null;
|
|
30
|
+
/** Per field or parameter: `errors.sort`, `errors['Idempotency-Key']`. */
|
|
31
|
+
readonly errors: Record<string, string[]> | null;
|
|
32
|
+
/** Seconds, from `Retry-After` on a 429. */
|
|
33
|
+
readonly retryAfter: number | null;
|
|
34
|
+
/** The record's current version on a 409 `conflict`: fetch-free retry material. */
|
|
35
|
+
readonly recordVersion: string | null;
|
|
36
|
+
/**
|
|
37
|
+
* `Allow` on a 405. cube/api lists the resource's actions here (`list, get, create`), not HTTP
|
|
38
|
+
* methods. Null in a browser on another origin: the API does not expose `Allow` through CORS.
|
|
39
|
+
*/
|
|
40
|
+
readonly allow: string[] | null;
|
|
41
|
+
readonly headers: Headers;
|
|
42
|
+
/** The parsed body, or the raw text when it was not JSON. */
|
|
43
|
+
readonly body: unknown;
|
|
44
|
+
constructor(status: number, headers: Headers, body: unknown);
|
|
45
|
+
/** True when the body was an actual problem document with a `code`, not just an HTTP status. */
|
|
46
|
+
get isProblem(): boolean;
|
|
47
|
+
}
|
|
48
|
+
export declare function isDmpError(value: unknown): value is DmpError;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** A refusal. Thrown for every non-2xx answer (and for a 304 nobody asked for). */
|
|
2
|
+
export class DmpError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
code;
|
|
5
|
+
reason;
|
|
6
|
+
type;
|
|
7
|
+
title;
|
|
8
|
+
detail;
|
|
9
|
+
/** Per field or parameter: `errors.sort`, `errors['Idempotency-Key']`. */
|
|
10
|
+
errors;
|
|
11
|
+
/** Seconds, from `Retry-After` on a 429. */
|
|
12
|
+
retryAfter;
|
|
13
|
+
/** The record's current version on a 409 `conflict`: fetch-free retry material. */
|
|
14
|
+
recordVersion;
|
|
15
|
+
/**
|
|
16
|
+
* `Allow` on a 405. cube/api lists the resource's actions here (`list, get, create`), not HTTP
|
|
17
|
+
* methods. Null in a browser on another origin: the API does not expose `Allow` through CORS.
|
|
18
|
+
*/
|
|
19
|
+
allow;
|
|
20
|
+
headers;
|
|
21
|
+
/** The parsed body, or the raw text when it was not JSON. */
|
|
22
|
+
body;
|
|
23
|
+
constructor(status, headers, body) {
|
|
24
|
+
const problem = body !== null && typeof body === 'object' ? body : {};
|
|
25
|
+
const detail = str(problem.detail) ?? str(problem.error_description);
|
|
26
|
+
super(detail ?? `HTTP ${status}`);
|
|
27
|
+
this.name = 'DmpError';
|
|
28
|
+
this.status = status;
|
|
29
|
+
this.code = str(problem.code) ?? str(problem.error);
|
|
30
|
+
this.reason = str(problem.reason);
|
|
31
|
+
this.type = str(problem.type);
|
|
32
|
+
this.title = str(problem.title);
|
|
33
|
+
this.detail = detail;
|
|
34
|
+
this.errors = isErrors(problem.errors) ? problem.errors : null;
|
|
35
|
+
this.retryAfter = seconds(headers.get('Retry-After'));
|
|
36
|
+
this.recordVersion = headers.get('X-Dmp-Record-Version');
|
|
37
|
+
this.allow = headers.has('Allow')
|
|
38
|
+
? (headers.get('Allow') ?? '').split(',').map((method) => method.trim()).filter((method) => method !== '')
|
|
39
|
+
: null;
|
|
40
|
+
this.headers = headers;
|
|
41
|
+
this.body = body;
|
|
42
|
+
}
|
|
43
|
+
/** True when the body was an actual problem document with a `code`, not just an HTTP status. */
|
|
44
|
+
get isProblem() {
|
|
45
|
+
return this.code !== null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function isDmpError(value) {
|
|
49
|
+
return value instanceof DmpError;
|
|
50
|
+
}
|
|
51
|
+
function str(value) {
|
|
52
|
+
return typeof value === 'string' ? value : null;
|
|
53
|
+
}
|
|
54
|
+
function isErrors(value) {
|
|
55
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
56
|
+
}
|
|
57
|
+
function seconds(value) {
|
|
58
|
+
if (value === null || value.trim() === '') {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const asNumber = Number(value);
|
|
62
|
+
if (Number.isFinite(asNumber)) {
|
|
63
|
+
return asNumber;
|
|
64
|
+
}
|
|
65
|
+
// RFC 9110 also allows an HTTP date. cube/api sends seconds, a proxy in front of it may not.
|
|
66
|
+
const date = Date.parse(value);
|
|
67
|
+
return Number.isNaN(date) ? null : Math.max(0, Math.ceil((date - Date.now()) / 1000));
|
|
68
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createClient, newIdempotencyKey, type ClientOptions, type CreateOptions, type DmpClient, type OperationOptions, type RequestOptions, type ResourceClient, type WriteOptions, } from './client.js';
|
|
2
|
+
export { bearer, createOAuth, discoverMetadata, memoryKeyValueStore, memoryTokenStore, registerClient, type AuthStrategy, type AuthorizationServerMetadata, type KeyValueStore, type OAuthOptions, type OAuthSession, type RegistrationRequest, type TokenSource, type TokenStore, type Tokens, } from './auth.js';
|
|
3
|
+
export { DmpError, isDmpError, type ErrorCode, type ErrorReason, type ProblemBody } from './errors.js';
|
|
4
|
+
export { buildQuery } from './query.js';
|
|
5
|
+
export type * from './types.js';
|
|
6
|
+
export { CubeApp, type ConnectOptions, type CubeAppConfig, type CubeAppSession, type CubeAppStatic, type CubeAppUser, } from './app.js';
|
|
7
|
+
export type { AppManifest, MockData, MockField, MockResource, MockSchema } from './preview.js';
|
|
8
|
+
export { VERSION } from './version.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { createClient, newIdempotencyKey, } from './client.js';
|
|
2
|
+
export { bearer, createOAuth, discoverMetadata, memoryKeyValueStore, memoryTokenStore, registerClient, } from './auth.js';
|
|
3
|
+
export { DmpError, isDmpError } from './errors.js';
|
|
4
|
+
export { buildQuery } from './query.js';
|
|
5
|
+
export { CubeApp, } from './app.js';
|
|
6
|
+
export { VERSION } from './version.js';
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { ResourceClient } from './client.js';
|
|
2
|
+
import type { DmpRecord } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* One field as `dmp.describe` (and so `cube.mock_data`) writes it: a JSON Schema. `type` is a
|
|
5
|
+
* name, or a list of names where `'null'` makes it nullable. `_capabilities`' own field shape
|
|
6
|
+
* (`{ name, type: 'string|integer', nullable, readonly }`) is read too.
|
|
7
|
+
*/
|
|
8
|
+
export interface MockField {
|
|
9
|
+
type?: string | readonly string[];
|
|
10
|
+
format?: string;
|
|
11
|
+
enum?: readonly unknown[];
|
|
12
|
+
description?: string | null;
|
|
13
|
+
nullable?: boolean;
|
|
14
|
+
readonly?: boolean;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
/** The `schema` half of one resource in `cube.mock_data`'s output: the `dmp.describe` payload. */
|
|
18
|
+
export interface MockSchema {
|
|
19
|
+
name?: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
actions?: readonly string[];
|
|
22
|
+
/** The filterable fields. Absent: every field. */
|
|
23
|
+
filters?: readonly string[];
|
|
24
|
+
/** Fields a write may not set. `id` never is writable, listed or not. */
|
|
25
|
+
readonly?: readonly string[];
|
|
26
|
+
/** What a row carries when the query names no `fields`, as on the REST face. Absent: every field. */
|
|
27
|
+
summary_fields?: readonly string[];
|
|
28
|
+
/** The fields `group` may name. Absent: every field. */
|
|
29
|
+
groupable?: readonly string[];
|
|
30
|
+
/** Per field the functions `fn` may apply. Absent: every field, every function. Bare `count` is always allowed. */
|
|
31
|
+
aggregatable?: Record<string, readonly string[]>;
|
|
32
|
+
/** The allowed values of each choice field, and whether a write may set it (apps#93). */
|
|
33
|
+
choices?: Record<string, {
|
|
34
|
+
values: readonly unknown[];
|
|
35
|
+
writable: boolean;
|
|
36
|
+
}>;
|
|
37
|
+
max_results?: number;
|
|
38
|
+
fields?: Record<string, MockField> | ReadonlyArray<MockField & {
|
|
39
|
+
name: string;
|
|
40
|
+
}>;
|
|
41
|
+
sample_row?: DmpRecord;
|
|
42
|
+
[key: string]: unknown;
|
|
43
|
+
}
|
|
44
|
+
export interface MockResource {
|
|
45
|
+
schema?: MockSchema;
|
|
46
|
+
rows?: readonly DmpRecord[];
|
|
47
|
+
}
|
|
48
|
+
/** `cube.mock_data`'s output as is: resource name => `{ schema, rows }`. */
|
|
49
|
+
export type MockData = Record<string, MockResource>;
|
|
50
|
+
/** The part of `cube-app.json` the preview reads: what the app declared it uses. */
|
|
51
|
+
export interface AppManifest {
|
|
52
|
+
name?: string;
|
|
53
|
+
description?: string;
|
|
54
|
+
uses?: Record<string, readonly string[]>;
|
|
55
|
+
sdk?: string;
|
|
56
|
+
[key: string]: unknown;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The preview's data: every resource of the mock data, in memory, changed by create, update and
|
|
60
|
+
* delete until the page is reloaded. It answers as the REST face does — the same envelope, the same
|
|
61
|
+
* paging, the same refusals as `DmpError` — and never touches the network.
|
|
62
|
+
*/
|
|
63
|
+
export declare class PreviewStore {
|
|
64
|
+
private readonly manifest;
|
|
65
|
+
/** Runs before every call; throw to refuse it (the preview's signed-out state). */
|
|
66
|
+
private readonly guard;
|
|
67
|
+
private readonly tables;
|
|
68
|
+
constructor(data: MockData, manifest?: AppManifest | null,
|
|
69
|
+
/** Runs before every call; throw to refuse it (the preview's signed-out state). */
|
|
70
|
+
guard?: () => void);
|
|
71
|
+
resourceNames(): string[];
|
|
72
|
+
resource<T extends DmpRecord = DmpRecord>(name: string): ResourceClient<T>;
|
|
73
|
+
/**
|
|
74
|
+
* Unknown resource first (404, as the REST face answers it), then the manifest: live, the app's
|
|
75
|
+
* OAuth client is capped to its `uses`, so the preview refuses what the live app would.
|
|
76
|
+
*/
|
|
77
|
+
private authorize;
|
|
78
|
+
}
|