Comparing version 1.3.1 to 1.4.0-beta.0
@@ -1,2 +0,5 @@ | ||
var Ce=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(e,r)=>(typeof require<"u"?require:e)[r]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw new Error('Dynamic require of "'+t+'" is not supported')});var Pe=(t,e,r)=>{if(!e.has(t))throw TypeError("Cannot "+r)};var i=(t,e,r)=>(Pe(t,e,"read from private field"),r?r.call(t):e.get(t)),c=(t,e,r)=>{if(e.has(t))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(t):e.set(t,r)},f=(t,e,r,s)=>(Pe(t,e,"write to private field"),s?s.call(t,r):e.set(t,r),r),Ne=(t,e,r,s)=>({set _(n){f(t,e,n,r)},get _(){return i(t,e,s)}}),l=(t,e,r)=>(Pe(t,e,"access private method"),r);var ke={default:new URL("https://db.fauna.com"),local:new URL("http://localhost:8443"),localhost:new URL("http://localhost:8443")};var C=class extends Error{constructor(...e){super(...e)}},d=class extends C{httpStatus;code;queryInfo;constraint_failures;constructor(e,r){super(e.error.message),Error.captureStackTrace&&Error.captureStackTrace(this,d),this.name="ServiceError",this.code=e.error.code,this.httpStatus=r;let s={txn_ts:e.txn_ts,summary:e.summary,query_tags:e.query_tags,stats:e.stats};this.queryInfo=s,this.constraint_failures=e.error.constraint_failures}},F=class extends d{constructor(e,r){super(e,r),Error.captureStackTrace&&Error.captureStackTrace(this,F),this.name="QueryRuntimeError"}},P=class extends d{constructor(e,r){super(e,r),Error.captureStackTrace&&Error.captureStackTrace(this,P),this.name="QueryCheckError"}},N=class extends d{constructor(e,r){super(e,r),Error.captureStackTrace&&Error.captureStackTrace(this,N),this.name="InvalidRequestError"}},ne=class extends d{abort;constructor(e,r){super(e,r),Error.captureStackTrace&&Error.captureStackTrace(this,P),this.name="AbortError",this.abort=e.error.abort}},M=class extends d{constructor(e,r){super(e,r),Error.captureStackTrace&&Error.captureStackTrace(this,M),this.name="AuthenticationError"}},W=class extends d{constructor(e,r){super(e,r),Error.captureStackTrace&&Error.captureStackTrace(this,W),this.name="AuthorizationError"}},se=class extends d{constructor(e,r){super(e,r),Error.captureStackTrace&&Error.captureStackTrace(this,N),this.name="ContendedTransactionError"}},k=class extends d{constructor(e,r){super(e,r),Error.captureStackTrace&&Error.captureStackTrace(this,k),this.name="ThrottlingError"}},v=class extends d{stats;constructor(e,r){super(e,r),Error.captureStackTrace&&Error.captureStackTrace(this,v),this.name="QueryTimeoutError",this.stats=e.stats}},j=class extends d{constructor(e,r){super(e,r),Error.captureStackTrace&&Error.captureStackTrace(this,j),this.name="ServiceInternalError"}},$=class extends d{constructor(e,r){super(e,r),Error.captureStackTrace&&Error.captureStackTrace(this,$),this.name="ServiceTimeoutError"}},w=class extends C{constructor(e,r){super(e,r),Error.captureStackTrace&&Error.captureStackTrace(this,w),this.name="ClientError"}},A=class extends C{constructor(e,r){super(e,r),Error.captureStackTrace&&Error.captureStackTrace(this,A),this.name="ClientClosedError"}},x=class extends C{constructor(e,r){super(e,r),Error.captureStackTrace&&Error.captureStackTrace(this,x),this.name="NetworkError"}},_=class extends C{httpStatus;constructor(e){super(e.message),Error.captureStackTrace&&Error.captureStackTrace(this,_),this.name="ProtocolError",this.httpStatus=e.httpStatus}};var oe=class{#e;#t;constructor({url:e,fetch_keepalive:r}){this.#e=new URL("/query/1",e).toString(),this.#t=r}async request({data:e,headers:r,method:s,client_timeout_ms:n}){let o=AbortSignal.timeout===void 0?(()=>{let T=new AbortController,m=T.signal;return setTimeout(()=>T.abort(),n),m})():AbortSignal.timeout(n),a=await fetch(this.#e,{method:s,headers:{...r,"Content-Type":"application/json"},body:JSON.stringify(e),signal:o,keepalive:this.#t}).catch(T=>{throw new x("The network connection encountered a problem.",{cause:T})}),u=a.status,p={};a.headers.forEach((T,m)=>p[m]=T);let b=await a.text();return{status:u,body:b,headers:p}}close(){}};var L;try{L=Ce("node:http2")}catch{L=void 0}var J,ie,ae,ce,I,y,pe,ve,K,me,fe,je,ye,$e,U=class{constructor({http2_session_idle_ms:e,url:r,http2_max_streams:s}){c(this,K);c(this,fe);c(this,ye);c(this,ie,void 0);c(this,ae,void 0);c(this,ce,void 0);c(this,I,0);c(this,y,void 0);if(L===void 0)throw new Error("Your platform does not support Node's http2 library");f(this,ie,e),f(this,ae,s),f(this,ce,r),f(this,y,null)}static getClient(e){var n;let r=l(n=U,pe,ve).call(n,e);i(U,J).has(r)||i(U,J).set(r,new U(e));let s=i(U,J).get(r);return Ne(s,I)._++,s}async request(e){let r=0,s;do try{return await l(this,ye,$e).call(this,e)}catch(n){if(n?.code!=="ERR_HTTP2_GOAWAY_SESSION")throw new x("The network connection encountered a problem.",{cause:n});s=n,r++}while(r<3);throw new x("The network connection encountered a problem.",{cause:s})}close(){this.isClosed()||(Ne(this,I)._--,i(this,I)===0&&i(this,y)&&!i(this,y).closed&&i(this,y).close())}isClosed(){return i(this,I)===0}},G=U;J=new WeakMap,ie=new WeakMap,ae=new WeakMap,ce=new WeakMap,I=new WeakMap,y=new WeakMap,pe=new WeakSet,ve=function({http2_session_idle_ms:e,url:r}){return`${r}|${e}`},K=new WeakSet,me=function(){f(this,I,0),i(this,y)&&!i(this,y).closed&&i(this,y).close()},fe=new WeakSet,je=function(){if(!i(this,y)||i(this,y).closed||i(this,y).destroyed){let e=L.connect(i(this,ce),{peerMaxConcurrentStreams:i(this,ae)}).once("error",()=>l(this,K,me).call(this)).once("goaway",()=>l(this,K,me).call(this));e.setTimeout(i(this,ie),()=>{l(this,K,me).call(this)}),f(this,y,e)}return i(this,y)},ye=new WeakSet,$e=function({client_timeout_ms:e,data:r,headers:s,method:n}){return new Promise((o,a)=>{let u,p=b=>{let T=Number(b[L.constants.HTTP2_HEADER_STATUS]),m="";u.on("data",z=>{m+=z}),u.on("end",()=>{o({status:T,body:m,headers:b})})};try{let b={...s,[L.constants.HTTP2_HEADER_PATH]:"/query/1",[L.constants.HTTP2_HEADER_METHOD]:n};u=l(this,fe,je).call(this).request(b).setEncoding("utf8").on("error",m=>{a(m)}).on("response",p),u.write(JSON.stringify(r),"utf8"),u.setTimeout(e,()=>{u.destroy(new Error("Client timeout"))}),u.end()}catch(b){a(b)}})},c(G,pe),c(G,J,new Map);var Ae=t=>mt()?G.getClient(t):new oe(t),Ie=t=>t instanceof Object&&"body"in t&&"headers"in t&&"status"in t,mt=()=>{if(typeof process<"u"&&process&&process.release?.name==="node")try{return Ce("node:http2"),!0}catch{return!1}return!1};var pt=/(?:\d{4}|[\u2212-]\d{4,}|\+\d{5,})/,ft=/(?:0[1-9]|1[0-2])/,yt=/(?:0[1-9]|[12]\d|3[01])/,Le=/(?:[01][0-9]|2[0-3])/,ue=/(?:[0-5][0-9])/,gt=/(?:\.\d+)/,De=new RegExp(`(${pt.source}-(${ft.source})-(${yt.source}))`),Tt=new RegExp(`(${Le.source}:${ue.source}:${ue.source}${gt.source}?)`),ht=new RegExp(`([zZ]|[+\u2212-]${Le.source}(?::?${ue.source}|:${ue.source}:${ue.source}))`),Ue=new RegExp(`^${De.source}$`),Ge=new RegExp(`^${De.source}`),Be=new RegExp(`^${De.source}T${Tt.source}${ht.source}$`);var E=class{isoString;constructor(e){this.isoString=e}static from(e){if(typeof e!="string")throw new TypeError(`Expected string but received ${typeof e}: ${e}`);if(Be.exec(e)===null)throw new RangeError(`(regex) Expected an ISO date string but received '${e}'`);return new E(e)}static fromDate(e){return new E(e.toISOString())}toDate(){let e=new Date(this.isoString);if(e.toString()==="Invalid Date")throw new RangeError("Fauna Date could not be converted to Javascript Date");return e}toString(){return`TimeStub("${this.isoString}")`}},Q=class{dateString;constructor(e){this.dateString=e}static from(e){if(typeof e!="string")throw new TypeError(`Expected string but received ${typeof e}: ${e}`);let r=Ue.exec(e);if(r===null)throw new RangeError(`Expected a plain date string but received '${e}'`);return new Q(r[0])}static fromDate(e){let r=e.toISOString(),s=Ge.exec(r);if(s===null)throw new w(`Failed to parse date '${e}'`);return new Q(s[0])}toDate(){let e=new Date(this.dateString+"T00:00:00Z");if(e.toString()==="Invalid Date")throw new RangeError("Fauna Date could not be converted to Javascript Date");return e}toString(){return`DateStub("${this.dateString}")`}};var D=class{coll;id;constructor({coll:e,id:r}){this.id=r,typeof e=="string"?this.coll=new V(e):this.coll=e}},Z=class extends D{ts;constructor(e){let{coll:r,id:s,ts:n,...o}=e;super({coll:r,id:s}),this.ts=n,Object.assign(this,o)}toObject(){return{...this}}},B=class{coll;name;constructor({coll:e,name:r}){this.name=r,typeof e=="string"?this.coll=new V(e):this.coll=e}},ee=class extends B{ts;data;constructor(e){let{coll:r,name:s,ts:n,data:o,...a}=e;super({coll:r,name:s}),this.ts=n,this.data=o||{},Object.assign(this,a)}toObject(){return{...this}}},V=class{name;constructor(e){this.name=e}},te=class{ref;cause;constructor(e,r){this.ref=e,this.cause=r}};var O=class{data;after;constructor({data:e,after:r}){this.data=e,this.after=r}},q=class{after;constructor(e){this.after=e}},R=class{#e;constructor(e,r,s){if(s=s??{},r instanceof Function)this.#e=wt(e,r,s);else if(r instanceof O||r instanceof q)this.#e=Xe(e,r,s);else throw new TypeError(`Expected 'Page<T> | EmbeddedSet | (() => Promise<T | Page<T> | EmbeddedSet>)', but received ${JSON.stringify(r)}`)}static fromQuery(e,r,s){return new R(e,async()=>(await e.query(r,s)).data,s)}static fromPageable(e,r,s){return new R(e,r,s)}flatten(){return new Ve(this)}async next(){return this.#e.next()}async return(){return this.#e.return()}async throw(e){return this.#e.throw(e)}[Symbol.asyncIterator](){return this}},Ve=class{#e;constructor(e){this.#e=xt(e)}async next(){return this.#e.next()}async return(){return this.#e.return()}async throw(e){return this.#e.throw(e)}[Symbol.asyncIterator](){return this}};async function*Xe(t,e,r){let s=e;for(s instanceof O&&(yield s.data);s.after;){let n=qe`Set.paginate(${s.after})`;s=(await t.query(n,r)).data,yield s.data}}async function*wt(t,e,r){let s=await e();if(s instanceof O||s instanceof q){for await(let n of Xe(t,s,r))yield n;return}yield[s]}async function*xt(t){for await(let e of t)for(let r of e)yield r}var H=class{static encode(e){return ge(e)}static decode(e,r){return JSON.parse(e,(s,n)=>{if(n==null)return null;if(n["@mod"])return new V(n["@mod"]);if(n["@doc"]){if(typeof n["@doc"]=="string"){let[a,u]=n["@doc"].split(":");return new D({coll:a,id:u})}let o=n["@doc"];return o.id?new Z(o):new ee(o)}else if(n["@ref"]){let o=n["@ref"],a;return o.id?a=new D(o):a=new B(o),"exists"in o&&o.exists===!1?new te(a,o.cause):a}else{if(n["@set"])return typeof n["@set"]=="string"?new q(n["@set"]):new O(n["@set"]);if(n["@int"])return Number(n["@int"]);if(n["@long"]){let o=BigInt(n["@long"]);return r.long_type==="number"?((o>Number.MAX_SAFE_INTEGER||o<Number.MIN_SAFE_INTEGER)&&console.warn("Value is too large to be represented as a number. Returning as Number with loss of precision. Use long_type 'bigint' instead."),Number(o)):o}else{if(n["@double"])return Number(n["@double"]);if(n["@date"])return Q.from(n["@date"]);if(n["@time"])return E.from(n["@time"]);if(n["@object"])return n["@object"]}}return n})}},Je=BigInt("-9223372036854775808"),Ke=BigInt("9223372036854775807"),Ye=-(2**31),ze=2**31-1,g={bigint:t=>{if(t<Je||t>Ke)throw new RangeError("BigInt value exceeds max magnitude for a 64-bit Fauna long. Use a 'number' to represent doubles beyond that limit.");return t>=Ye&&t<=ze?{"@int":t.toString()}:{"@long":t.toString()}},number:t=>{if(t===Number.POSITIVE_INFINITY||t===Number.NEGATIVE_INFINITY)throw new RangeError(`Cannot convert ${t} to a Fauna type.`);return Number.isInteger(t)?t>=Ye&&t<=ze?{"@int":t.toString()}:Number.isSafeInteger(t)?{"@long":t.toString()}:{"@double":t.toString()}:{"@double":t.toString()}},string:t=>t,object:t=>{let e=!1,r={};for(let s in t)s.startsWith("@")&&(e=!0),t[s]!==void 0&&(r[s]=ge(t[s]));return e?{"@object":r}:r},array:t=>{let e=[];for(let r in t)e.push(ge(t[r]));return e},date:t=>({"@time":t.toISOString()}),faunadate:t=>({"@date":t.dateString}),faunatime:t=>({"@time":t.isoString}),module:t=>({"@mod":t.name}),documentReference:t=>({"@ref":{id:t.id,coll:{"@mod":t.coll.name}}}),document:t=>({"@ref":{id:t.id,coll:{"@mod":t.coll.name}}}),namedDocumentReference:t=>({"@ref":{name:t.name,coll:{"@mod":t.coll.name}}}),namedDocument:t=>({"@ref":{name:t.name,coll:{"@mod":t.coll.name}}}),set:t=>{throw new w("Page could not be encoded. Fauna does not accept encoded Set values, yet. Use Page.data and Page.after as arguments, instead.")}},ge=t=>{if(t===void 0)throw new TypeError("Passing undefined as a QueryValue is not supported");switch(typeof t){case"bigint":return g.bigint(t);case"string":return g.string(t);case"number":return g.number(t);case"boolean":return t;case"object":return t==null?null:Array.isArray(t)?g.array(t):t instanceof Date?g.date(t):t instanceof Q?g.faunadate(t):t instanceof E?g.faunatime(t):t instanceof V?g.module(t):t instanceof Z?g.document(t):t instanceof D?g.documentReference(t):t instanceof ee?g.namedDocument(t):t instanceof B?g.namedDocumentReference(t):t instanceof te?ge(t.ref):t instanceof O||t instanceof q?g.set(t):g.object(t)}};function qe(t,...e){return new X(t,...e)}var X=class{#e;#t;constructor(e,...r){if(e.length===0||e.length!==r.length+1)throw new Error("invalid query constructed");this.#e=e,this.#t=r}toQuery(e={}){return{...this.#r(e),...e}}#r(e){if(this.#e.length===1)return{query:{fql:[this.#e[0]]},arguments:{}};let r={};return{query:{fql:this.#e.flatMap((n,o)=>{if(o===this.#e.length-1)return n===""?[]:[n];let a=this.#t[o],u;if(a instanceof X){let p=a.toQuery(e);u=p.query,r={...r,...p.arguments}}else u={value:H.encode(a)};return[n,u].filter(p=>p!=="")})},arguments:r}}};var Ze="1.3.1";var Te;try{Te=Ce("node:os")}catch{Te=void 0}var rt=()=>{let t={driver:["javascript",Ze].join("-"),env:"unknown",os:"unknown",runtime:"unknown"};try{let e=typeof window>"u"&&typeof process<"u"&&process.versions!=null&&process.versions.node!=null,r=typeof window<"u"&&typeof window.document<"u",s=typeof self=="object"&&self.constructor&&self.constructor.name==="DedicatedWorkerGlobalScope";e?(t.runtime=["nodejs",process.version].join("-"),t.env=_t(),t.os=[Te.platform(),Te.release()].join("-")):s?(t.runtime=et(navigator),t.env="Service Worker",t.os=tt(navigator)):r?(t.runtime=et(navigator),t.env="browser",t.os=tt(navigator)):typeof EdgeRuntime!="string"&&(t.runtime="Vercel Edge Runtime",t.env="edge")}catch{}return Object.entries(t).filter(([e,r])=>r!=="unknown").map(e=>e.join("=")).join("; ")},et=t=>{let e=t.appName,r=""+parseFloat(t.appVersion),s,n,o;return(n=t.userAgent.indexOf("Opera"))!=-1?(e="Opera",r=t.userAgent.substring(n+6),(n=t.userAgent.indexOf("Version"))!=-1&&(r=t.userAgent.substring(n+8))):(n=t.userAgent.indexOf("MSIE"))!=-1?(e="Microsoft Internet Explorer",r=t.userAgent.substring(n+5)):e=="Netscape"&&t.userAgent.indexOf("Trident/")!=-1?(e="Microsoft Internet Explorer",r=t.userAgent.substring(n+5),(n=t.userAgent.indexOf("rv:"))!=-1&&(r=t.userAgent.substring(n+3))):(n=t.userAgent.indexOf("Chrome"))!=-1?(e="Chrome",r=t.userAgent.substring(n+7)):(n=t.userAgent.indexOf("Safari"))!=-1?(e="Safari",r=t.userAgent.substring(n+7),(n=t.userAgent.indexOf("Version"))!=-1&&(r=t.userAgent.substring(n+8)),t.userAgent.indexOf("CriOS")!=-1&&(e="Chrome")):(n=t.userAgent.indexOf("Firefox"))!=-1?(e="Firefox",r=t.userAgent.substring(n+8)):(s=t.userAgent.lastIndexOf(" ")+1)<(n=t.userAgent.lastIndexOf("/"))&&(e=t.userAgent.substring(s,n),r=t.userAgent.substring(n+1),e.toLowerCase()==e.toUpperCase()&&(e=t.appName)),(o=r.indexOf(";"))!=-1&&(r=r.substring(0,o)),(o=r.indexOf(" "))!=-1&&(r=r.substring(0,o)),(o=r.indexOf(")"))!=-1&&(r=r.substring(0,o)),[e,r].join("-")},tt=t=>{let e="unknown",r=[{s:"Windows 10",r:/(Windows 10.0|Windows NT 10.0)/},{s:"Windows 8.1",r:/(Windows 8.1|Windows NT 6.3)/},{s:"Windows 8",r:/(Windows 8|Windows NT 6.2)/},{s:"Windows 7",r:/(Windows 7|Windows NT 6.1)/},{s:"Windows Vista",r:/Windows NT 6.0/},{s:"Windows Server 2003",r:/Windows NT 5.2/},{s:"Windows XP",r:/(Windows NT 5.1|Windows XP)/},{s:"Windows 2000",r:/(Windows NT 5.0|Windows 2000)/},{s:"Windows ME",r:/(Win 9x 4.90|Windows ME)/},{s:"Windows 98",r:/(Windows 98|Win98)/},{s:"Windows 95",r:/(Windows 95|Win95|Windows_95)/},{s:"Windows NT 4.0",r:/(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/},{s:"Windows CE",r:/Windows CE/},{s:"Windows 3.11",r:/Win16/},{s:"Android",r:/Android/},{s:"Open BSD",r:/OpenBSD/},{s:"Sun OS",r:/SunOS/},{s:"Chrome OS",r:/CrOS/},{s:"Linux",r:/(Linux|X11(?!.*CrOS))/},{s:"iOS",r:/(iPhone|iPad|iPod)/},{s:"Mac OS X",r:/Mac OS X/},{s:"Mac OS",r:/(Mac OS|MacPPC|MacIntel|Mac_PowerPC|Macintosh)/},{s:"QNX",r:/QNX/},{s:"UNIX",r:/UNIX/},{s:"BeOS",r:/BeOS/},{s:"OS/2",r:/OS\/2/},{s:"Search Bot",r:/(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/}];for(let n in r){let o=r[n];if(o.r.test(t.userAgent)){e=o.s;break}}let s="unknown";if(/Windows/.test(e)){let n=/Windows (.*)/.exec(e);n&&(s=n[1]),e="Windows"}switch(e){case"Mac OS":case"Mac OS X":case"Android":{let n=/(?:Android|Mac OS|Mac OS X|MacPPC|MacIntel|Mac_PowerPC|Macintosh) ([._\d]+)/.exec(t.userAgent);n&&(s=n[1]);break}case"iOS":{let n=/OS (\d+)_(\d+)_?(\d+)?/.exec(t.appVersion);n&&(s=n[1]+"."+n[2]+"."+(n[3]??0));break}}return[e,s].join("-")},St=typeof window<"u"?window:typeof globalThis<"u"?globalThis:typeof global<"u"?global:self,_t=()=>{if(!(typeof process<"u"&&process&&process.env&&typeof process.env=="object"))return"unknown";let e=[{name:"Netlify",check:function(){return!!process.env.NETLIFY_IMAGES_CDN_DOMAIN}},{name:"Vercel",check:function(){return!!process.env.VERCEL}},{name:"Heroku",check:function(){return!!process.env.PATH&&process.env.PATH.indexOf(".heroku")!==-1}},{name:"AWS Lambda",check:function(){return!!process.env.AWS_LAMBDA_FUNCTION_VERSION}},{name:"GCP Cloud Functions",check:function(){return!!process.env._&&process.env._.indexOf("google")!==-1}},{name:"GCP Compute Instances",check:function(){return!!process.env.GOOGLE_CLOUD_PROJECT}},{name:"Azure Cloud Functions",check:function(){return!!process.env.WEBSITE_FUNCTIONS_AZUREMONITOR_CATEGORIES}},{name:"Azure Compute",check:function(){return!!process.env.ORYX_ENV_TYPE&&!!process.env.WEBSITE_INSTANCE_ID&&process.env.ORYX_ENV_TYPE==="AppService"}},{name:"Mongo Stitch",check:function(){return typeof St?.StitchError=="function"}},{name:"Render",check:function(){return!!process.env.RENDER_SERVICE_ID}},{name:"Begin",check:function(){return!!process.env.BEGIN_DATA_SCOPE_ID}}].find(r=>r.check());return e?e.name:"unknown"};var nt=t=>t instanceof Object&&"data"in t,st=t=>t instanceof Object&&"error"in t&&t.error instanceof Object&&"code"in t.error&&"message"in t.error;var He={client_timeout_buffer_ms:5e3,format:"tagged",http2_session_idle_ms:5e3,http2_max_streams:100,long_type:"number",fetch_keepalive:!1,query_timeout_ms:5e3,max_attempts:3,max_backoff:20},be,S,Y,h,re,de,Fe,le,Me,we,ot,xe,it,Se,at,_e,ct,Ee,ut,Qe,dt,We=class{constructor(e,r){c(this,de);c(this,le);c(this,we);c(this,xe);c(this,Se);c(this,_e);c(this,Ee);c(this,Qe);c(this,S,void 0);c(this,Y,void 0);c(this,h,void 0);c(this,re,!1);f(this,S,{...He,...e,secret:l(this,we,ot).call(this,e),endpoint:l(this,xe,it).call(this,e)}),l(this,Qe,dt).call(this),r?f(this,Y,r):f(this,Y,Ae({url:i(this,S).endpoint.toString(),http2_session_idle_ms:i(this,S).http2_session_idle_ms,http2_max_streams:i(this,S).http2_max_streams,fetch_keepalive:i(this,S).fetch_keepalive}))}get lastTxnTs(){return i(this,h)}set lastTxnTs(e){e!==void 0&&f(this,h,i(this,h)?Math.max(e,i(this,h)):e)}get clientConfiguration(){let{...e}=i(this,S);return e}close(){if(i(this,re))throw new A("Your client is closed. You cannot close it again.");i(this,Y).close(),f(this,re,!0)}paginate(e,r){return e instanceof X?R.fromQuery(this,e,r):R.fromPageable(this,e,r)}async query(e,r){if(i(this,re))throw new A("Your client is closed. No further requests can be issued.");let s=e.toQuery(r).query;return l(this,de,Fe).call(this,s,r)}},he=We;be=new WeakMap,S=new WeakMap,Y=new WeakMap,h=new WeakMap,re=new WeakMap,de=new WeakSet,Fe=async function(e,r,s=0){let n=this.clientConfiguration.max_backoff??He.max_backoff,o=this.clientConfiguration.max_attempts??He.max_attempts,a=Math.min(Math.random()*2**s,n)*1e3,u=p=>new Promise(b=>setTimeout(b,p));return s+=1,l(this,_e,ct).call(this,e,r,s).catch(p=>{if(p instanceof k&&s<o)return u(a).then(()=>l(this,de,Fe).call(this,e,r,s));throw p})},le=new WeakSet,Me=function(e){if(e instanceof w||e instanceof x||e instanceof _||e instanceof d)return e;if(Ie(e)){if(st(e.body)){let r=e.body,s=e.status;return l(this,Se,at).call(this,r,s)}return new _({message:`Response is in an unkown format: ${e.body}`,httpStatus:e.status})}return new w("A client level error occurred. Fauna was not called.",{cause:e})},we=new WeakSet,ot=function(e){let r;typeof process<"u"&&process&&typeof process=="object"&&process.env&&typeof process.env=="object"&&(r=process.env.FAUNA_SECRET);let s=e?.secret??r;if(s===void 0)throw new TypeError("You must provide a secret to the driver. Set it in an environmental variable named FAUNA_SECRET or pass it to the Client constructor.");return s},xe=new WeakSet,it=function(e){if(e&&"endpoint"in e&&e.endpoint===void 0)throw new TypeError("ClientConfiguration option endpoint must be defined.");let r;return typeof process<"u"&&process&&typeof process=="object"&&process.env&&typeof process.env=="object"&&(r=process.env.FAUNA_ENDPOINT?new URL(process.env.FAUNA_ENDPOINT):void 0),e?.endpoint??r??ke.default},Se=new WeakSet,at=function(e,r){switch(r){case 400:return Et.includes(e.error.code)?new P(e,r):e.error.code==="invalid_request"?new N(e,r):e.error.code==="abort"&&e.error.abort!==void 0?new ne(e,r):new F(e,r);case 401:return new M(e,r);case 403:return new W(e,r);case 409:return new se(e,r);case 429:return new k(e,r);case 440:return new v(e,r);case 500:return new j(e,r);case 503:return new $(e,r);default:return new d(e,r)}},_e=new WeakSet,ct=async function(e,r,s=0){try{let n={...i(this,S),...r},o={Authorization:`Bearer ${n.secret}`};l(this,Ee,ut).call(this,n,o);let a=n.format==="tagged",u=n.arguments?a?H.encode(n.arguments):n.arguments:void 0,p={query:e,arguments:u},b=n.query_timeout_ms+i(this,S).client_timeout_buffer_ms,T=await i(this,Y).request({client_timeout_ms:b,data:p,headers:o,method:"POST"}),m;try{if(m={...T,body:a?H.decode(T.body,{long_type:n.long_type}):JSON.parse(T.body)},m.body.query_tags){let Re=m.body.query_tags.split(",").map(lt=>lt.split("="));m.body.query_tags=Object.fromEntries(Re)}}catch(Re){throw new _({message:`Error parsing response as JSON: ${Re}`,httpStatus:T.status})}if(!nt(m.body))throw l(this,le,Me).call(this,m);let z=m.body.txn_ts;(i(this,h)===void 0&&z!==void 0||z!==void 0&&i(this,h)!==void 0&&i(this,h)<z)&&f(this,h,z);let Oe=m.body;return Oe.stats&&(Oe.stats.attempts=s),Oe}catch(n){throw l(this,le,Me).call(this,n)}},Ee=new WeakSet,ut=function(e,r){for(let s of Object.entries(e))if(["format","query_timeout_ms","linearized","max_contention_retries","traceparent","typecheck","query_tags"].includes(s[0])){let n,o=`x-${s[0].replaceAll("_","-")}`;s[0]==="query_tags"?n=Object.entries(s[1]).map(a=>a.join("=")).join(","):typeof s[1]=="string"?n=s[1]:n=String(s[1]),s[0]==="traceparent"&&(o=s[0]),r[o]=n}r["x-last-txn-ts"]===void 0&&i(this,h)!==void 0&&(r["x-last-txn-ts"]=i(this,h)),r["x-driver-env"]=i(We,be)},Qe=new WeakSet,dt=function(){let e=i(this,S);if(["client_timeout_buffer_ms","endpoint","format","http2_session_idle_ms","long_type","query_timeout_ms","fetch_keepalive","http2_max_streams"].forEach(s=>{if(e[s]===void 0)throw new TypeError(`ClientConfiguration option '${s}' must be defined.`)}),e.http2_max_streams<=0)throw new RangeError("'http2_max_streams' must be greater than zero.");if(e.client_timeout_buffer_ms<=0)throw new RangeError("'client_timeout_buffer_ms' must be greater than zero.");if(e.query_timeout_ms<=0)throw new RangeError("'query_timeout_ms' must be greater than zero.")},c(he,be,rt());var Et=["invalid_function_definition","invalid_identifier","invalid_query","invalid_syntax","invalid_type"];export{ne as AbortError,M as AuthenticationError,W as AuthorizationError,he as Client,A as ClientClosedError,w as ClientError,se as ContendedTransactionError,Q as DateStub,Z as Document,D as DocumentReference,q as EmbeddedSet,C as FaunaError,oe as FetchClient,N as InvalidRequestError,Ke as LONG_MAX,Je as LONG_MIN,V as Module,ee as NamedDocument,B as NamedDocumentReference,x as NetworkError,G as NodeHTTP2Client,te as NullDocument,O as Page,_ as ProtocolError,P as QueryCheckError,F as QueryRuntimeError,v as QueryTimeoutError,d as ServiceError,j as ServiceInternalError,$ as ServiceTimeoutError,R as SetIterator,H as TaggedTypeFormat,k as ThrottlingError,E as TimeStub,ke as endpoints,qe as fql,Ae as getDefaultHTTPClient,Ie as isHTTPResponse}; | ||
var Ie=(r=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(r,{get:(e,t)=>(typeof require<"u"?require:e)[t]}):r)(function(r){if(typeof require<"u")return require.apply(this,arguments);throw new Error('Dynamic require of "'+r+'" is not supported')});var De=(r,e,t)=>{if(!e.has(r))throw TypeError("Cannot "+t)};var a=(r,e,t)=>(De(r,e,"read from private field"),t?t.call(r):e.get(r)),u=(r,e,t)=>{if(e.has(r))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(r):e.set(r,t)},T=(r,e,t,s)=>(De(r,e,"write to private field"),s?s.call(r,t):e.set(r,t),t),ve=(r,e,t,s)=>({set _(n){T(r,e,n,t)},get _(){return a(r,e,s)}}),p=(r,e,t)=>(De(r,e,"access private method"),t);var qe={default:new URL("https://db.fauna.com"),local:new URL("http://localhost:8443"),localhost:new URL("http://localhost:8443")};var R=class extends Error{constructor(...e){super(...e)}},l=class extends R{httpStatus;code;queryInfo;constraint_failures;constructor(e,t){super(e.error.message),Error.captureStackTrace&&Error.captureStackTrace(this,l),this.name="ServiceError",this.code=e.error.code,this.httpStatus=t;let s={txn_ts:e.txn_ts,summary:e.summary,query_tags:e.query_tags,stats:e.stats};this.queryInfo=s,this.constraint_failures=e.error.constraint_failures}},B=class extends l{constructor(e,t){super(e,t),Error.captureStackTrace&&Error.captureStackTrace(this,B),this.name="QueryRuntimeError"}},q=class extends l{constructor(e,t){super(e,t),Error.captureStackTrace&&Error.captureStackTrace(this,q),this.name="QueryCheckError"}},V=class extends l{constructor(e,t){super(e,t),Error.captureStackTrace&&Error.captureStackTrace(this,V),this.name="InvalidRequestError"}},ce=class extends l{abort;constructor(e,t){super(e,t),Error.captureStackTrace&&Error.captureStackTrace(this,q),this.name="AbortError",this.abort=e.error.abort}},z=class extends l{constructor(e,t){super(e,t),Error.captureStackTrace&&Error.captureStackTrace(this,z),this.name="AuthenticationError"}},X=class extends l{constructor(e,t){super(e,t),Error.captureStackTrace&&Error.captureStackTrace(this,X),this.name="AuthorizationError"}},ue=class extends l{constructor(e,t){super(e,t),Error.captureStackTrace&&Error.captureStackTrace(this,V),this.name="ContendedTransactionError"}},F=class extends l{constructor(e,t){super(e,t),Error.captureStackTrace&&Error.captureStackTrace(this,F),this.name="ThrottlingError"}},Y=class extends l{stats;constructor(e,t){super(e,t),Error.captureStackTrace&&Error.captureStackTrace(this,Y),this.name="QueryTimeoutError",this.stats=e.stats}},J=class extends l{constructor(e,t){super(e,t),Error.captureStackTrace&&Error.captureStackTrace(this,J),this.name="ServiceInternalError"}},K=class extends l{constructor(e,t){super(e,t),Error.captureStackTrace&&Error.captureStackTrace(this,K),this.name="ServiceTimeoutError"}},b=class extends R{constructor(e,t){super(e,t),Error.captureStackTrace&&Error.captureStackTrace(this,b),this.name="ClientError"}},P=class extends R{constructor(e,t){super(e,t),Error.captureStackTrace&&Error.captureStackTrace(this,P),this.name="ClientClosedError"}},w=class extends R{constructor(e,t){super(e,t),Error.captureStackTrace&&Error.captureStackTrace(this,w),this.name="NetworkError"}},k=class extends R{httpStatus;constructor(e){super(e.message),Error.captureStackTrace&&Error.captureStackTrace(this,k),this.name="ProtocolError",this.httpStatus=e.httpStatus}};var de=class{#e;#t;#r;constructor({url:e,fetch_keepalive:t}){this.#e=new URL("/query/1",e).toString(),this.#t=new URL("/stream/1",e).toString(),this.#r=t}async request({data:e,headers:t,method:s,client_timeout_ms:n}){let o=AbortSignal.timeout===void 0?(()=>{let f=new AbortController,m=f.signal;return setTimeout(()=>f.abort(),n),m})():AbortSignal.timeout(n),i=await fetch(this.#e,{method:s,headers:{...t,"Content-Type":"application/json"},body:JSON.stringify(e),signal:o,keepalive:this.#r}).catch(f=>{throw new w("The network connection encountered a problem.",{cause:f})}),c=i.status,d={};i.headers.forEach((f,m)=>d[m]=f);let g=await i.text();return{status:c,body:g,headers:d}}stream({data:e,headers:t,method:s}){let n=new Request(this.#t,{method:s,headers:{...t,"Content-Type":"application/json"},body:JSON.stringify(e),keepalive:this.#r}),o=new AbortController,i={signal:o.signal};async function*c(){let d=await fetch(n,i).catch(y=>{throw new w("The network connection encountered a problem.",{cause:y})}),g=d.status;if(!(g>=200&&g<400)){let y=await d.json();throw new l(y,g)}let f=d.body;if(!f)throw new Error("Response body is undefined.");let m=f.getReader();for await(let y of St(m))yield y}return{read:c(),close:()=>{o.abort("Stream closed by the client.")}}}close(){}};async function*St(r){let e=new TextDecoder,t="";for await(let s of wt(r)){let n=e.decode(s),o=(t+n).split(` | ||
`);for(let i=0;i<o.length-1;i++)yield o[i].trim();t=o[o.length-1]}t.trim()!==""&&(yield t)}async function*wt(r){let e=!1;do{let t=await r.read();t.value!==void 0&&(yield t.value),e=t.done}while(!e)}var Q;try{Q=Ie("node:http2")}catch{Q=void 0}var ne,le,me,pe,M,S,be,Xe,se,Te,fe,Ve,Se,Ye,we,Je,Z=class{constructor({http2_session_idle_ms:e,url:t,http2_max_streams:s}){u(this,se);u(this,fe);u(this,Se);u(this,we);u(this,le,void 0);u(this,me,void 0);u(this,pe,void 0);u(this,M,0);u(this,S,void 0);if(Q===void 0)throw new Error("Your platform does not support Node's http2 library");T(this,le,e),T(this,me,s),T(this,pe,t),T(this,S,null)}static getClient(e){var n;let t=p(n=Z,be,Xe).call(n,e);a(Z,ne).has(t)||a(Z,ne).set(t,new Z(e));let s=a(Z,ne).get(t);return ve(s,M)._++,s}async request(e){let t=0,s;do try{return await p(this,Se,Ye).call(this,e)}catch(n){if(n?.code!=="ERR_HTTP2_GOAWAY_SESSION")throw new w("The network connection encountered a problem.",{cause:n});s=n,t++}while(t<3);throw new w("The network connection encountered a problem.",{cause:s})}stream(e){return p(this,we,Je).call(this,e)}close(){this.isClosed()||(ve(this,M)._--,a(this,M)===0&&a(this,S)&&!a(this,S).closed&&a(this,S).close())}isClosed(){return a(this,M)===0}},ee=Z;ne=new WeakMap,le=new WeakMap,me=new WeakMap,pe=new WeakMap,M=new WeakMap,S=new WeakMap,be=new WeakSet,Xe=function({http2_session_idle_ms:e,url:t}){return`${t}|${e}`},se=new WeakSet,Te=function(){T(this,M,0),a(this,S)&&!a(this,S).closed&&a(this,S).close()},fe=new WeakSet,Ve=function(){if(!a(this,S)||a(this,S).closed||a(this,S).destroyed){let e=Q.connect(a(this,pe),{peerMaxConcurrentStreams:a(this,me)}).once("error",()=>p(this,se,Te).call(this)).once("goaway",()=>p(this,se,Te).call(this));e.setTimeout(a(this,le),()=>{p(this,se,Te).call(this)}),T(this,S,e)}return a(this,S)},Se=new WeakSet,Ye=function({client_timeout_ms:e,data:t,headers:s,method:n}){return new Promise((o,i)=>{let c,d=g=>{let f=Number(g[Q.constants.HTTP2_HEADER_STATUS]),m="";c.on("data",y=>{m+=y}),c.on("end",()=>{o({status:f,body:m,headers:g})})};try{let g={...s,[Q.constants.HTTP2_HEADER_PATH]:"/query/1",[Q.constants.HTTP2_HEADER_METHOD]:n};c=p(this,fe,Ve).call(this).request(g).setEncoding("utf8").on("error",m=>{i(m)}).on("response",d),c.write(JSON.stringify(t),"utf8"),c.setTimeout(e,()=>{c.destroy(new Error("Client timeout"))}),c.end()}catch(g){i(g)}})},we=new WeakSet,Je=function({data:e,headers:t,method:s}){let n,o,i=()=>new Promise((y,C)=>{n=y,o=C}),c=i(),d,g=y=>{let C=Number(y[Q.constants.HTTP2_HEADER_STATUS]);if(C>=200&&C<400){let E="";d.on("data",v=>{let G=(E+v).split(` | ||
`);n(G.map(He=>He.trim()).slice(0,-1)),c=i(),E=G[G.length-1]}),d.on("end",()=>{n([E])})}else{let E="";d.on("data",v=>{E+=v}),d.on("end",()=>{o(new l(JSON.parse(E),C))})}},f=this;async function*m(){var v;let y={...t,[Q.constants.HTTP2_HEADER_PATH]:"/stream/1",[Q.constants.HTTP2_HEADER_METHOD]:s};d=p(v=f,fe,Ve).call(v).request(y).setEncoding("utf8").on("error",G=>{o(G)}).on("response",g);let E=JSON.stringify(e);for(d.write(E,"utf8"),d.end();;){let G=await c;for(let He of G)yield He}}return{read:m(),close:()=>{d&&d.close()}}},u(ee,be),u(ee,ne,new Map);var Fe=r=>xt()?ee.getClient(r):new de(r),Me=r=>r instanceof Object&&"body"in r&&"headers"in r&&"status"in r,We=r=>"stream"in r&&typeof r.stream=="function",xt=()=>{if(typeof process<"u"&&process&&process.release?.name==="node")try{return Ie("node:http2"),!0}catch{return!1}return!1};var Et=/(?:\d{4}|[\u2212-]\d{4,}|\+\d{5,})/,_t=/(?:0[1-9]|1[0-2])/,Ct=/(?:0[1-9]|[12]\d|3[01])/,Ke=/(?:[01][0-9]|2[0-3])/,ye=/(?:[0-5][0-9])/,Rt=/(?:\.\d+)/,je=new RegExp(`(${Et.source}-(${_t.source})-(${Ct.source}))`),Qt=new RegExp(`(${Ke.source}:${ye.source}:${ye.source}${Rt.source}?)`),Pt=new RegExp(`([zZ]|[+\u2212-]${Ke.source}(?::?${ye.source}|:${ye.source}:${ye.source}))`),Ze=new RegExp(`^${je.source}$`),et=new RegExp(`^${je.source}`),tt=new RegExp(`^${je.source}T${Qt.source}${Pt.source}$`);var O=class{isoString;constructor(e){this.isoString=e}static from(e){if(typeof e!="string")throw new TypeError(`Expected string but received ${typeof e}: ${e}`);if(tt.exec(e)===null)throw new RangeError(`(regex) Expected an ISO date string but received '${e}'`);return new O(e)}static fromDate(e){return new O(e.toISOString())}toDate(){let e=new Date(this.isoString);if(e.toString()==="Invalid Date")throw new RangeError("Fauna Date could not be converted to Javascript Date");return e}toString(){return`TimeStub("${this.isoString}")`}},A=class{dateString;constructor(e){this.dateString=e}static from(e){if(typeof e!="string")throw new TypeError(`Expected string but received ${typeof e}: ${e}`);let t=Ze.exec(e);if(t===null)throw new RangeError(`Expected a plain date string but received '${e}'`);return new A(t[0])}static fromDate(e){let t=e.toISOString(),s=et.exec(t);if(s===null)throw new b(`Failed to parse date '${e}'`);return new A(s[0])}toDate(){let e=new Date(this.dateString+"T00:00:00Z");if(e.toString()==="Invalid Date")throw new RangeError("Fauna Date could not be converted to Javascript Date");return e}toString(){return`DateStub("${this.dateString}")`}};var W=class{coll;id;constructor({coll:e,id:t}){this.id=t,typeof e=="string"?this.coll=new j(e):this.coll=e}},oe=class extends W{ts;constructor(e){let{coll:t,id:s,ts:n,...o}=e;super({coll:t,id:s}),this.ts=n,Object.assign(this,o)}toObject(){return{...this}}},te=class{coll;name;constructor({coll:e,name:t}){this.name=t,typeof e=="string"?this.coll=new j(e):this.coll=e}},ie=class extends te{ts;data;constructor(e){let{coll:t,name:s,ts:n,data:o,...i}=e;super({coll:t,name:s}),this.ts=n,this.data=o||{},Object.assign(this,i)}toObject(){return{...this}}},j=class{name;constructor(e){this.name=e}},ae=class{ref;cause;constructor(e,t){this.ref=e,this.cause=t}};var N=class{data;after;constructor({data:e,after:t}){this.data=e,this.after=t}},$=class{after;constructor(e){this.after=e}},H=class{#e;constructor(e,t,s){if(s=s??{},t instanceof Function)this.#e=Ot(e,t,s);else if(t instanceof N||t instanceof $)this.#e=rt(e,t,s);else throw new TypeError(`Expected 'Page<T> | EmbeddedSet | (() => Promise<T | Page<T> | EmbeddedSet>)', but received ${JSON.stringify(t)}`)}static fromQuery(e,t,s){return new H(e,async()=>(await e.query(t,s)).data,s)}static fromPageable(e,t,s){return new H(e,t,s)}flatten(){return new $e(this)}async next(){return this.#e.next()}async return(){return this.#e.return()}async throw(e){return this.#e.throw(e)}[Symbol.asyncIterator](){return this}},$e=class{#e;constructor(e){this.#e=At(e)}async next(){return this.#e.next()}async return(){return this.#e.return()}async throw(e){return this.#e.throw(e)}[Symbol.asyncIterator](){return this}};async function*rt(r,e,t){let s=e;for(s instanceof N&&(yield s.data);s.after;){let n=Le`Set.paginate(${s.after})`;s=(await r.query(n,t)).data,yield s.data}}async function*Ot(r,e,t){let s=await e();if(s instanceof N||s instanceof $){for await(let n of rt(r,s,t))yield n;return}yield[s]}async function*At(r){for await(let e of r)for(let t of e)yield t}var I=class{token;constructor(e){this.token=e}};var D=class{static encode(e){return xe(e)}static decode(e,t){return JSON.parse(e,(s,n)=>{if(n==null)return null;if(n["@mod"])return new j(n["@mod"]);if(n["@doc"]){if(typeof n["@doc"]=="string"){let[i,c]=n["@doc"].split(":");return new W({coll:i,id:c})}let o=n["@doc"];return o.id?new oe(o):new ie(o)}else if(n["@ref"]){let o=n["@ref"],i;return o.id?i=new W(o):i=new te(o),"exists"in o&&o.exists===!1?new ae(i,o.cause):i}else{if(n["@set"])return typeof n["@set"]=="string"?new $(n["@set"]):new N(n["@set"]);if(n["@int"])return Number(n["@int"]);if(n["@long"]){let o=BigInt(n["@long"]);return t.long_type==="number"?((o>Number.MAX_SAFE_INTEGER||o<Number.MIN_SAFE_INTEGER)&&console.warn("Value is too large to be represented as a number. Returning as Number with loss of precision. Use long_type 'bigint' instead."),Number(o)):o}else{if(n["@double"])return Number(n["@double"]);if(n["@date"])return A.from(n["@date"]);if(n["@time"])return O.from(n["@time"]);if(n["@object"])return n["@object"];if(n["@stream"])return new I(n["@stream"])}}return n})}},ot=BigInt("-9223372036854775808"),it=BigInt("9223372036854775807"),nt=-(2**31),st=2**31-1,h={bigint:r=>{if(r<ot||r>it)throw new RangeError("BigInt value exceeds max magnitude for a 64-bit Fauna long. Use a 'number' to represent doubles beyond that limit.");return r>=nt&&r<=st?{"@int":r.toString()}:{"@long":r.toString()}},number:r=>{if(r===Number.POSITIVE_INFINITY||r===Number.NEGATIVE_INFINITY)throw new RangeError(`Cannot convert ${r} to a Fauna type.`);return Number.isInteger(r)?r>=nt&&r<=st?{"@int":r.toString()}:Number.isSafeInteger(r)?{"@long":r.toString()}:{"@double":r.toString()}:{"@double":r.toString()}},string:r=>r,object:r=>{let e=!1,t={};for(let s in r)s.startsWith("@")&&(e=!0),r[s]!==void 0&&(t[s]=xe(r[s]));return e?{"@object":t}:t},array:r=>{let e=[];for(let t in r)e.push(xe(r[t]));return e},date:r=>({"@time":r.toISOString()}),faunadate:r=>({"@date":r.dateString}),faunatime:r=>({"@time":r.isoString}),module:r=>({"@mod":r.name}),documentReference:r=>({"@ref":{id:r.id,coll:{"@mod":r.coll.name}}}),document:r=>({"@ref":{id:r.id,coll:{"@mod":r.coll.name}}}),namedDocumentReference:r=>({"@ref":{name:r.name,coll:{"@mod":r.coll.name}}}),namedDocument:r=>({"@ref":{name:r.name,coll:{"@mod":r.coll.name}}}),set:r=>{throw new b("Page could not be encoded. Fauna does not accept encoded Set values, yet. Use Page.data and Page.after as arguments, instead.")},streamToken:r=>r.token},xe=r=>{if(r===void 0)throw new TypeError("Passing undefined as a QueryValue is not supported");switch(typeof r){case"bigint":return h.bigint(r);case"string":return h.string(r);case"number":return h.number(r);case"boolean":return r;case"object":return r==null?null:Array.isArray(r)?h.array(r):r instanceof Date?h.date(r):r instanceof A?h.faunadate(r):r instanceof O?h.faunatime(r):r instanceof j?h.module(r):r instanceof oe?h.document(r):r instanceof W?h.documentReference(r):r instanceof ie?h.namedDocument(r):r instanceof te?h.namedDocumentReference(r):r instanceof ae?xe(r.ref):r instanceof N||r instanceof $?h.set(r):r instanceof I?h.streamToken(r):h.object(r)}};function Le(r,...e){return new L(r,...e)}var L=class{#e;#t;constructor(e,...t){if(e.length===0||e.length!==t.length+1)throw new Error("invalid query constructed");this.#e=e,this.#t=t}toQuery(e={}){return{...this.#r(e),...e}}#r(e){if(this.#e.length===1)return{query:{fql:[this.#e[0]]},arguments:{}};let t={};return{query:{fql:this.#e.flatMap((n,o)=>{if(o===this.#e.length-1)return n===""?[]:[n];let i=this.#t[o],c;if(i instanceof L){let d=i.toQuery(e);c=d.query,t={...t,...d.arguments}}else c={value:D.encode(i)};return[n,c].filter(d=>d!=="")})},arguments:t}}};var at="1.4.0-beta.0";var Ee;try{Ee=Ie("node:os")}catch{Ee=void 0}var dt=()=>{let r={driver:["javascript",at].join("-"),env:"unknown",os:"unknown",runtime:"unknown"};try{let e=typeof window>"u"&&typeof process<"u"&&process.versions!=null&&process.versions.node!=null,t=typeof window<"u"&&typeof window.document<"u",s=typeof self=="object"&&self.constructor&&self.constructor.name==="DedicatedWorkerGlobalScope";e?(r.runtime=["nodejs",process.version].join("-"),r.env=Ht(),r.os=[Ee.platform(),Ee.release()].join("-")):s?(r.runtime=ct(navigator),r.env="Service Worker",r.os=ut(navigator)):t?(r.runtime=ct(navigator),r.env="browser",r.os=ut(navigator)):typeof EdgeRuntime!="string"&&(r.runtime="Vercel Edge Runtime",r.env="edge")}catch{}return Object.entries(r).filter(([e,t])=>t!=="unknown").map(e=>e.join("=")).join("; ")},ct=r=>{let e=r.appName,t=""+parseFloat(r.appVersion),s,n,o;return(n=r.userAgent.indexOf("Opera"))!=-1?(e="Opera",t=r.userAgent.substring(n+6),(n=r.userAgent.indexOf("Version"))!=-1&&(t=r.userAgent.substring(n+8))):(n=r.userAgent.indexOf("MSIE"))!=-1?(e="Microsoft Internet Explorer",t=r.userAgent.substring(n+5)):e=="Netscape"&&r.userAgent.indexOf("Trident/")!=-1?(e="Microsoft Internet Explorer",t=r.userAgent.substring(n+5),(n=r.userAgent.indexOf("rv:"))!=-1&&(t=r.userAgent.substring(n+3))):(n=r.userAgent.indexOf("Chrome"))!=-1?(e="Chrome",t=r.userAgent.substring(n+7)):(n=r.userAgent.indexOf("Safari"))!=-1?(e="Safari",t=r.userAgent.substring(n+7),(n=r.userAgent.indexOf("Version"))!=-1&&(t=r.userAgent.substring(n+8)),r.userAgent.indexOf("CriOS")!=-1&&(e="Chrome")):(n=r.userAgent.indexOf("Firefox"))!=-1?(e="Firefox",t=r.userAgent.substring(n+8)):(s=r.userAgent.lastIndexOf(" ")+1)<(n=r.userAgent.lastIndexOf("/"))&&(e=r.userAgent.substring(s,n),t=r.userAgent.substring(n+1),e.toLowerCase()==e.toUpperCase()&&(e=r.appName)),(o=t.indexOf(";"))!=-1&&(t=t.substring(0,o)),(o=t.indexOf(" "))!=-1&&(t=t.substring(0,o)),(o=t.indexOf(")"))!=-1&&(t=t.substring(0,o)),[e,t].join("-")},ut=r=>{let e="unknown",t=[{s:"Windows 10",r:/(Windows 10.0|Windows NT 10.0)/},{s:"Windows 8.1",r:/(Windows 8.1|Windows NT 6.3)/},{s:"Windows 8",r:/(Windows 8|Windows NT 6.2)/},{s:"Windows 7",r:/(Windows 7|Windows NT 6.1)/},{s:"Windows Vista",r:/Windows NT 6.0/},{s:"Windows Server 2003",r:/Windows NT 5.2/},{s:"Windows XP",r:/(Windows NT 5.1|Windows XP)/},{s:"Windows 2000",r:/(Windows NT 5.0|Windows 2000)/},{s:"Windows ME",r:/(Win 9x 4.90|Windows ME)/},{s:"Windows 98",r:/(Windows 98|Win98)/},{s:"Windows 95",r:/(Windows 95|Win95|Windows_95)/},{s:"Windows NT 4.0",r:/(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/},{s:"Windows CE",r:/Windows CE/},{s:"Windows 3.11",r:/Win16/},{s:"Android",r:/Android/},{s:"Open BSD",r:/OpenBSD/},{s:"Sun OS",r:/SunOS/},{s:"Chrome OS",r:/CrOS/},{s:"Linux",r:/(Linux|X11(?!.*CrOS))/},{s:"iOS",r:/(iPhone|iPad|iPod)/},{s:"Mac OS X",r:/Mac OS X/},{s:"Mac OS",r:/(Mac OS|MacPPC|MacIntel|Mac_PowerPC|Macintosh)/},{s:"QNX",r:/QNX/},{s:"UNIX",r:/UNIX/},{s:"BeOS",r:/BeOS/},{s:"OS/2",r:/OS\/2/},{s:"Search Bot",r:/(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/}];for(let n in t){let o=t[n];if(o.r.test(r.userAgent)){e=o.s;break}}let s="unknown";if(/Windows/.test(e)){let n=/Windows (.*)/.exec(e);n&&(s=n[1]),e="Windows"}switch(e){case"Mac OS":case"Mac OS X":case"Android":{let n=/(?:Android|Mac OS|Mac OS X|MacPPC|MacIntel|Mac_PowerPC|Macintosh) ([._\d]+)/.exec(r.userAgent);n&&(s=n[1]);break}case"iOS":{let n=/OS (\d+)_(\d+)_?(\d+)?/.exec(r.appVersion);n&&(s=n[1]+"."+n[2]+"."+(n[3]??0));break}}return[e,s].join("-")},Nt=typeof window<"u"?window:typeof globalThis<"u"?globalThis:typeof global<"u"?global:self,Ht=()=>{if(!(typeof process<"u"&&process&&process.env&&typeof process.env=="object"))return"unknown";let e=[{name:"Netlify",check:function(){return!!process.env.NETLIFY_IMAGES_CDN_DOMAIN}},{name:"Vercel",check:function(){return!!process.env.VERCEL}},{name:"Heroku",check:function(){return!!process.env.PATH&&process.env.PATH.indexOf(".heroku")!==-1}},{name:"AWS Lambda",check:function(){return!!process.env.AWS_LAMBDA_FUNCTION_VERSION}},{name:"GCP Cloud Functions",check:function(){return!!process.env._&&process.env._.indexOf("google")!==-1}},{name:"GCP Compute Instances",check:function(){return!!process.env.GOOGLE_CLOUD_PROJECT}},{name:"Azure Cloud Functions",check:function(){return!!process.env.WEBSITE_FUNCTIONS_AZUREMONITOR_CATEGORIES}},{name:"Azure Compute",check:function(){return!!process.env.ORYX_ENV_TYPE&&!!process.env.WEBSITE_INSTANCE_ID&&process.env.ORYX_ENV_TYPE==="AppService"}},{name:"Mongo Stitch",check:function(){return typeof Nt?.StitchError=="function"}},{name:"Render",check:function(){return!!process.env.RENDER_SERVICE_ID}},{name:"Begin",check:function(){return!!process.env.BEGIN_DATA_SCOPE_ID}}].find(t=>t.check());return e?e.name:"unknown"};var lt=r=>r instanceof Object&&"data"in r,mt=r=>r instanceof Object&&"error"in r&&r.error instanceof Object&&"code"in r.error&&"message"in r.error;var Ue={client_timeout_buffer_ms:5e3,format:"tagged",http2_session_idle_ms:5e3,http2_max_streams:100,long_type:"number",fetch_keepalive:!1,query_timeout_ms:5e3,max_attempts:3,max_backoff:20},Re,x,U,_,re,ge,Ge,he,Be,Qe,pt,Pe,ft,ke,yt,Oe,gt,Ae,ht,Ne,Tt,ze=class{constructor(e,t){u(this,ge);u(this,he);u(this,Qe);u(this,Pe);u(this,ke);u(this,Oe);u(this,Ae);u(this,Ne);u(this,x,void 0);u(this,U,void 0);u(this,_,void 0);u(this,re,!1);T(this,x,{...Ue,...e,secret:p(this,Qe,pt).call(this,e),endpoint:p(this,Pe,ft).call(this,e)}),p(this,Ne,Tt).call(this),t?T(this,U,t):T(this,U,Fe({url:a(this,x).endpoint.toString(),http2_session_idle_ms:a(this,x).http2_session_idle_ms,http2_max_streams:a(this,x).http2_max_streams,fetch_keepalive:a(this,x).fetch_keepalive}))}get lastTxnTs(){return a(this,_)}set lastTxnTs(e){e!==void 0&&T(this,_,a(this,_)?Math.max(e,a(this,_)):e)}get clientConfiguration(){let{...e}=a(this,x);return e}close(){if(a(this,re))throw new P("Your client is closed. You cannot close it again.");a(this,U).close(),T(this,re,!0)}paginate(e,t){return e instanceof L?H.fromQuery(this,e,t):H.fromPageable(this,e,t)}async query(e,t){if(a(this,re))throw new P("Your client is closed. No further requests can be issued.");let s=e.toQuery(t).query;return p(this,ge,Ge).call(this,s,t)}stream(e,t){if(a(this,re))throw new P("Your client is closed. No further requests can be issued.");let s=a(this,U);if(We(s)){let n={...a(this,x),httpStreamClient:s,...t},o=e instanceof L?()=>this.query(e).then(i=>i.data):e;return new Ce(o,n)}else throw new b("Streaming is not supported by this client.")}},_e=ze;Re=new WeakMap,x=new WeakMap,U=new WeakMap,_=new WeakMap,re=new WeakMap,ge=new WeakSet,Ge=async function(e,t,s=0){let n=this.clientConfiguration.max_backoff??Ue.max_backoff,o=this.clientConfiguration.max_attempts??Ue.max_attempts,i=Math.min(Math.random()*2**s,n)*1e3;s+=1;try{return await p(this,Oe,gt).call(this,e,t,s)}catch(c){if(c instanceof F&&s<o)return await bt(i),p(this,ge,Ge).call(this,e,t,s);throw c}},he=new WeakSet,Be=function(e){if(e instanceof b||e instanceof w||e instanceof k||e instanceof l)return e;if(Me(e)){if(mt(e.body)){let t=e.body,s=e.status;return p(this,ke,yt).call(this,t,s)}return new k({message:`Response is in an unkown format: ${e.body}`,httpStatus:e.status})}return new b("A client level error occurred. Fauna was not called.",{cause:e})},Qe=new WeakSet,pt=function(e){let t;typeof process<"u"&&process&&typeof process=="object"&&process.env&&typeof process.env=="object"&&(t=process.env.FAUNA_SECRET);let s=e?.secret??t;if(s===void 0)throw new TypeError("You must provide a secret to the driver. Set it in an environmental variable named FAUNA_SECRET or pass it to the Client constructor.");return s},Pe=new WeakSet,ft=function(e){if(e&&"endpoint"in e&&e.endpoint===void 0)throw new TypeError("ClientConfiguration option endpoint must be defined.");let t;return typeof process<"u"&&process&&typeof process=="object"&&process.env&&typeof process.env=="object"&&(t=process.env.FAUNA_ENDPOINT?new URL(process.env.FAUNA_ENDPOINT):void 0),e?.endpoint??t??qe.default},ke=new WeakSet,yt=function(e,t){switch(t){case 400:return It.includes(e.error.code)?new q(e,t):e.error.code==="invalid_request"?new V(e,t):e.error.code==="abort"&&e.error.abort!==void 0?new ce(e,t):new B(e,t);case 401:return new z(e,t);case 403:return new X(e,t);case 409:return new ue(e,t);case 429:return new F(e,t);case 440:return new Y(e,t);case 500:return new J(e,t);case 503:return new K(e,t);default:return new l(e,t)}},Oe=new WeakSet,gt=async function(e,t,s=0){try{let n={...a(this,x),...t},o={Authorization:`Bearer ${n.secret}`};p(this,Ae,ht).call(this,n,o);let i=n.format==="tagged",c=n.arguments?i?D.encode(n.arguments):n.arguments:void 0,d={query:e,arguments:c},g=n.query_timeout_ms+a(this,x).client_timeout_buffer_ms,f=await a(this,U).request({client_timeout_ms:g,data:d,headers:o,method:"POST"}),m;try{if(m={...f,body:i?D.decode(f.body,{long_type:n.long_type}):JSON.parse(f.body)},m.body.query_tags){let E=m.body.query_tags.split(",").map(v=>v.split("="));m.body.query_tags=Object.fromEntries(E)}}catch(E){throw new k({message:`Error parsing response as JSON: ${E}`,httpStatus:f.status})}if(!lt(m.body))throw p(this,he,Be).call(this,m);let y=m.body.txn_ts;(a(this,_)===void 0&&y!==void 0||y!==void 0&&a(this,_)!==void 0&&a(this,_)<y)&&T(this,_,y);let C=m.body;return C.stats&&(C.stats.attempts=s),C}catch(n){throw p(this,he,Be).call(this,n)}},Ae=new WeakSet,ht=function(e,t){let s=(n,o,i=c=>String(c))=>{o!==void 0&&(t[n]=i(o))};s("x-format",e.format),s("x-typecheck",e.typecheck),s("x-query-timeout-ms",e.query_timeout_ms),s("x-linearized",e.linearized),s("x-max-contention-retries",e.max_contention_retries),s("traceparent",e.traceparent),s("x-query-tags",e.query_tags,n=>Object.entries(n).map(o=>o.join("=")).join(",")),s("x-last-txn-ts",a(this,_),n=>n),s("x-driver-env",a(ze,Re))},Ne=new WeakSet,Tt=function(){let e=a(this,x);if(["client_timeout_buffer_ms","endpoint","format","http2_session_idle_ms","long_type","query_timeout_ms","fetch_keepalive","http2_max_streams","max_backoff","max_attempts"].forEach(s=>{if(e[s]===void 0)throw new TypeError(`ClientConfiguration option '${s}' must be defined.`)}),e.http2_max_streams<=0)throw new RangeError("'http2_max_streams' must be greater than zero.");if(e.client_timeout_buffer_ms<=0)throw new RangeError("'client_timeout_buffer_ms' must be greater than zero.");if(e.query_timeout_ms<=0)throw new RangeError("'query_timeout_ms' must be greater than zero.");if(e.max_backoff<=0)throw new RangeError("'max_backoff' must be greater than zero.");if(e.max_attempts<=0)throw new RangeError("'max_attempts' must be greater than zero.")},u(_e,Re,dt());var Ce=class{closed=!1;#e;#t=0;#r;#s;#n;#o;constructor(e,t){e instanceof I?this.#r=()=>Promise.resolve(e):this.#r=e,this.#e=t,this.#a()}start(e,t){if(typeof e!="function")throw new TypeError(`Expected a function as the 'onEvent' argument, but received ${typeof e}. Please provide a valid function.`);if(t&&typeof t!="function")throw new TypeError(`Expected a function as the 'onError' argument, but received ${typeof t}. Please provide a valid function.`);(async()=>{try{for await(let n of this)e(n)}catch(n){t&&t(n)}})()}async*[Symbol.asyncIterator](){if(this.closed)throw new b("The stream has been closed and cannot be reused.");for(this.#o||(this.#o=await this.#r().then(e=>{if(!(e instanceof I))throw new b(`Error requesting a stream token. Expected a StreamToken as the query result, but received ${typeof e}. Your query must return the result of '<Set>.toStream' or '<Set>.changesOn') | ||
Query result: ${JSON.stringify(e,null)}`);return e})),this.#t=1;!this.closed;){let e=Math.min(Math.random()*2**this.#t,this.#e.max_backoff)*1e3;try{for await(let t of this.#i(this.#s))yield t}catch(t){if(t instanceof R||this.#t>=this.#e.max_attempts)throw this.close(),t;this.#t+=1,await bt(e)}}}close(){this.#n&&(this.#n.close(),this.#n=void 0),this.closed=!0}get last_ts(){return this.#s}async*#i(e){let t=this.#o,s={Authorization:`Bearer ${this.#e.secret}`},n=this.#e.httpStreamClient.stream({data:{token:t.token,start_ts:e},headers:s,method:"POST"});this.#n=n;for await(let o of n.read){let i=D.decode(o,{long_type:this.#e.long_type});if(i.type==="error")throw this.close(),new l(i,400);this.#s=i.txn_ts,i.type==="start"&&(i.type="status"),!(!this.#e.status_events&&i.type==="status")&&(yield i)}}#a(){let e=this.#e;if(["long_type","httpStreamClient","max_backoff","max_attempts","secret"].forEach(s=>{if(e[s]===void 0)throw new TypeError(`ClientConfiguration option '${s}' must be defined.`)}),e.max_backoff<=0)throw new RangeError("'max_backoff' must be greater than zero.");if(e.max_attempts<=0)throw new RangeError("'max_attempts' must be greater than zero.")}},It=["invalid_function_definition","invalid_identifier","invalid_query","invalid_syntax","invalid_type"];function bt(r){return new Promise(e=>setTimeout(e,r))}export{ce as AbortError,z as AuthenticationError,X as AuthorizationError,_e as Client,P as ClientClosedError,b as ClientError,ue as ContendedTransactionError,A as DateStub,oe as Document,W as DocumentReference,$ as EmbeddedSet,R as FaunaError,de as FetchClient,V as InvalidRequestError,it as LONG_MAX,ot as LONG_MIN,j as Module,ie as NamedDocument,te as NamedDocumentReference,w as NetworkError,ee as NodeHTTP2Client,ae as NullDocument,N as Page,k as ProtocolError,q as QueryCheckError,B as QueryRuntimeError,Y as QueryTimeoutError,l as ServiceError,J as ServiceInternalError,K as ServiceTimeoutError,H as SetIterator,Ce as StreamClient,I as StreamToken,D as TaggedTypeFormat,F as ThrottlingError,O as TimeStub,qe as endpoints,Le as fql,Fe as getDefaultHTTPClient,Me as isHTTPResponse,We as isStreamClient}; | ||
//# sourceMappingURL=index.js.map |
@@ -0,1 +1,2 @@ | ||
import { HTTPStreamClient } from "./http-client"; | ||
import type { ValueFormat } from "./wire-protocol"; | ||
@@ -138,2 +139,37 @@ /** | ||
/** | ||
* Configuration for a streaming client. This typically comes from the `Client` | ||
* instance configuration. | ||
*/ | ||
export type StreamClientConfiguration = { | ||
/** | ||
* The underlying {@link HTTPStreamClient} that will execute the actual HTTP calls | ||
*/ | ||
httpStreamClient: HTTPStreamClient; | ||
/** | ||
* Controls what Javascript type to deserialize {@link https://fqlx-beta--fauna-docs.netlify.app/fqlx/beta/reference/language/types#long | Fauna longs} to. | ||
* @see {@link ClientConfiguration.long_type} | ||
*/ | ||
long_type: "number" | "bigint"; | ||
/** | ||
* Max attempts for retryable exceptions. | ||
*/ | ||
max_attempts: number; | ||
/** | ||
* Max backoff between retries. | ||
*/ | ||
max_backoff: number; | ||
/** | ||
* A secret for your Fauna DB, used to authorize your queries. | ||
* @see https://docs.fauna.com/fauna/current/security/keys | ||
*/ | ||
secret: string; | ||
/** | ||
* Indicates if stream should include "status" events, periodic events that | ||
* update the client with the latest valid timestamp (in the event of a | ||
* dropped connection) as well as metrics about about the cost of maintaining | ||
* the stream other than the cost of the received events. | ||
*/ | ||
status_events?: boolean; | ||
}; | ||
/** | ||
* A extensible set of endpoints for calling Fauna. | ||
@@ -140,0 +176,0 @@ * @remarks Most clients will will not need to extend this set. |
@@ -1,6 +0,6 @@ | ||
import { ClientConfiguration } from "./client-configuration"; | ||
import { ClientConfiguration, StreamClientConfiguration } from "./client-configuration"; | ||
import { type HTTPClient } from "./http-client"; | ||
import { Query } from "./query-builder"; | ||
import { EmbeddedSet, Page, SetIterator } from "./values"; | ||
import { type QueryOptions, type QuerySuccess, type QueryValue } from "./wire-protocol"; | ||
import { EmbeddedSet, Page, SetIterator, StreamToken } from "./values"; | ||
import { StreamEventData, StreamEventStatus, type QueryOptions, type QuerySuccess, type QueryValue } from "./wire-protocol"; | ||
/** | ||
@@ -118,2 +118,89 @@ * Client for calling Fauna. | ||
query<T extends QueryValue>(query: Query, options?: QueryOptions): Promise<QuerySuccess<T>>; | ||
/** | ||
* Initialize a streaming request to Fauna | ||
* @param query - A string-encoded streaming token, or a {@link Query} | ||
* @returns A {@link StreamClient} that which can be used to listen to a stream | ||
* of events | ||
* | ||
* @example | ||
* ```javascript | ||
* const stream = client.stream(fql`MyCollection.all().toStream()`) | ||
* | ||
* try { | ||
* for await (const event of stream) { | ||
* switch (event.type) { | ||
* case "update": | ||
* case "add": | ||
* case "remove": | ||
* console.log("Stream update:", event); | ||
* // ... | ||
* break; | ||
* } | ||
* } | ||
* } catch (error) { | ||
* // An error will be handled here if Fauna returns a terminal, "error" event, or | ||
* // if Fauna returns a non-200 response when trying to connect, or | ||
* // if the max number of retries on network errors is reached. | ||
* | ||
* // ... handle fatal error | ||
* }; | ||
* ``` | ||
* | ||
* @example | ||
* ```javascript | ||
* const stream = client.stream(fql`MyCollection.all().toStream()`) | ||
* | ||
* stream.start( | ||
* function onEvent(event) { | ||
* switch (event.type) { | ||
* case "update": | ||
* case "add": | ||
* case "remove": | ||
* console.log("Stream update:", event); | ||
* // ... | ||
* break; | ||
* } | ||
* }, | ||
* function onError(error) { | ||
* // An error will be handled here if Fauna returns a terminal, "error" event, or | ||
* // if Fauna returns a non-200 response when trying to connect, or | ||
* // if the max number of retries on network errors is reached. | ||
* | ||
* // ... handle fatal error | ||
* } | ||
* ); | ||
* ``` | ||
*/ | ||
stream<T extends QueryValue>(tokenOrQuery: StreamToken | Query, options?: Partial<StreamClientConfiguration>): StreamClient<T>; | ||
} | ||
/** | ||
* A class to listen to Fauna streams. | ||
*/ | ||
export declare class StreamClient<T extends QueryValue = any> { | ||
#private; | ||
/** Whether or not this stream has been closed */ | ||
closed: boolean; | ||
/** | ||
* | ||
* @param query - A lambda that returns a promise for a {@link StreamToken} | ||
* @param clientConfiguration - The {@link ClientConfiguration} to apply | ||
* @param httpStreamClient - The underlying {@link HTTPStreamClient} that will | ||
* execute the actual HTTP calls | ||
* @example | ||
* ```typescript | ||
* const streamClient = client.stream(streamToken); | ||
* ``` | ||
*/ | ||
constructor(token: StreamToken | (() => Promise<StreamToken>), clientConfiguration: StreamClientConfiguration); | ||
/** | ||
* A synchronous method to start listening to the stream and handle events | ||
* using callbacks. | ||
* @param onEvent - A callback function to handle each event | ||
* @param onError - An Optional callback function to handle errors. If none is | ||
* provided, error will not be handled, and the stream will simply end. | ||
*/ | ||
start(onEvent: (event: StreamEventData<T> | StreamEventStatus) => void, onError?: (error: Error) => void): void; | ||
[Symbol.asyncIterator](): AsyncGenerator<StreamEventData<T> | StreamEventStatus>; | ||
close(): void; | ||
get last_ts(): number | undefined; | ||
} |
/** following reference needed to include types for experimental fetch API in Node */ | ||
/// <reference lib="dom" /> | ||
import { HTTPClient, HTTPClientOptions, HTTPRequest, HTTPResponse } from "./http-client"; | ||
import { HTTPClient, HTTPClientOptions, HTTPRequest, HTTPResponse, HTTPStreamRequest, HTTPStreamClient, StreamAdapter } from "./http-client"; | ||
/** | ||
* An implementation for {@link HTTPClient} that uses the native fetch API | ||
*/ | ||
export declare class FetchClient implements HTTPClient { | ||
export declare class FetchClient implements HTTPClient, HTTPStreamClient { | ||
#private; | ||
@@ -12,4 +12,6 @@ constructor({ url, fetch_keepalive }: HTTPClientOptions); | ||
request({ data, headers: requestHeaders, method, client_timeout_ms, }: HTTPRequest): Promise<HTTPResponse>; | ||
/** {@inheritDoc HTTPStreamClient.stream} */ | ||
stream({ data, headers: requestHeaders, method, }: HTTPStreamRequest): StreamAdapter; | ||
/** {@inheritDoc HTTPClient.close} */ | ||
close(): void; | ||
} |
@@ -1,2 +0,2 @@ | ||
import { QueryRequest } from "../wire-protocol"; | ||
import { QueryRequest, StreamRequest } from "../wire-protocol"; | ||
/** | ||
@@ -58,1 +58,34 @@ * An object representing an http request. | ||
} | ||
/** | ||
* An object representing an http request. | ||
* The {@link Client} provides this to the {@link HTTPStreamClient} implementation. | ||
*/ | ||
export type HTTPStreamRequest = { | ||
/** The encoded Fauna query to send */ | ||
data: StreamRequest; | ||
/** Headers in object format */ | ||
headers: Record<string, string | undefined>; | ||
/** HTTP method to use */ | ||
method: "POST"; | ||
}; | ||
/** | ||
* A common interface for a StreamClient to operate a stream from any HTTPStreamClient | ||
*/ | ||
export interface StreamAdapter { | ||
read: AsyncGenerator<string>; | ||
close: () => void; | ||
} | ||
/** | ||
* An interface to provide implementation-specific, asyncronous http calls. | ||
* This driver provides default implementations for common environments. Users | ||
* can configure the {@link Client} to use custom implementations if desired. | ||
*/ | ||
export interface HTTPStreamClient { | ||
/** | ||
* Makes an HTTP request and returns the response | ||
* @param req - an {@link HTTPStreamRequest} | ||
* @returns A Promise<{@link HTTPResponse}> | ||
* @throws {@link NetworkError} on request timeout or other network issue. | ||
*/ | ||
stream(req: HTTPStreamRequest): StreamAdapter; | ||
} |
@@ -1,6 +0,8 @@ | ||
import { FetchClient } from "./fetch-client"; | ||
import { NodeHTTP2Client } from "./node-http2-client"; | ||
import { HTTPClient, HTTPClientOptions, HTTPRequest, HTTPResponse } from "./http-client"; | ||
export declare const getDefaultHTTPClient: (options: HTTPClientOptions) => HTTPClient; | ||
import { HTTPClient, HTTPClientOptions, HTTPResponse, HTTPStreamClient } from "./http-client"; | ||
export * from "./fetch-client"; | ||
export * from "./http-client"; | ||
export * from "./node-http2-client"; | ||
export declare const getDefaultHTTPClient: (options: HTTPClientOptions) => HTTPClient & HTTPStreamClient; | ||
export declare const isHTTPResponse: (res: any) => res is HTTPResponse; | ||
export { FetchClient, NodeHTTP2Client, HTTPClient, HTTPRequest, HTTPResponse }; | ||
export declare const isStreamClient: (client: Partial<HTTPStreamClient>) => client is HTTPStreamClient; | ||
export declare const nodeHttp2IsSupported: () => boolean; |
@@ -1,6 +0,6 @@ | ||
import { HTTPClient, HTTPClientOptions, HTTPRequest, HTTPResponse } from "./http-client"; | ||
import { HTTPClient, HTTPClientOptions, HTTPRequest, HTTPResponse, HTTPStreamClient, HTTPStreamRequest, StreamAdapter } from "./http-client"; | ||
/** | ||
* An implementation for {@link HTTPClient} that uses the node http package | ||
*/ | ||
export declare class NodeHTTP2Client implements HTTPClient { | ||
export declare class NodeHTTP2Client implements HTTPClient, HTTPStreamClient { | ||
#private; | ||
@@ -16,2 +16,4 @@ private constructor(); | ||
request(req: HTTPRequest): Promise<HTTPResponse>; | ||
/** {@inheritDoc HTTPStreamClient.stream} */ | ||
stream(req: HTTPStreamRequest): StreamAdapter; | ||
/** {@inheritDoc HTTPClient.close} */ | ||
@@ -18,0 +20,0 @@ close(): void; |
@@ -1,3 +0,3 @@ | ||
export { Client } from "./client"; | ||
export { endpoints, type ClientConfiguration, type Endpoints, } from "./client-configuration"; | ||
export { Client, StreamClient } from "./client"; | ||
export { endpoints, type ClientConfiguration, type Endpoints, type StreamClientConfiguration, } from "./client-configuration"; | ||
export { AbortError, AuthenticationError, AuthorizationError, ClientError, ClientClosedError, ContendedTransactionError, FaunaError, InvalidRequestError, NetworkError, ProtocolError, QueryCheckError, QueryRuntimeError, QueryTimeoutError, ServiceError, ServiceInternalError, ServiceTimeoutError, ThrottlingError, } from "./errors"; | ||
@@ -7,3 +7,3 @@ export { type Query, fql } from "./query-builder"; | ||
export { type QueryValueObject, type QueryValue, type QueryFailure, type QueryInfo, type QueryInterpolation, type QueryOptions, type QueryRequest, type QueryStats, type QuerySuccess, type Span, type ValueFragment, } from "./wire-protocol"; | ||
export { DateStub, Document, DocumentReference, type DocumentT, EmbeddedSet, Module, NamedDocument, NamedDocumentReference, NullDocument, Page, SetIterator, TimeStub, } from "./values"; | ||
export { FetchClient, getDefaultHTTPClient, isHTTPResponse, NodeHTTP2Client, type HTTPClient, type HTTPRequest, type HTTPResponse, } from "./http-client"; | ||
export { DateStub, Document, DocumentReference, EmbeddedSet, Module, NamedDocument, NamedDocumentReference, NullDocument, Page, SetIterator, StreamToken, TimeStub, type DocumentT, } from "./values"; | ||
export { FetchClient, getDefaultHTTPClient, isHTTPResponse, isStreamClient, NodeHTTP2Client, type HTTPClient, type HTTPRequest, type HTTPResponse, type HTTPStreamClient, type StreamAdapter, } from "./http-client"; |
@@ -84,2 +84,4 @@ "use strict"; | ||
SetIterator: () => SetIterator, | ||
StreamClient: () => StreamClient, | ||
StreamToken: () => StreamToken, | ||
TaggedTypeFormat: () => TaggedTypeFormat, | ||
@@ -91,3 +93,4 @@ ThrottlingError: () => ThrottlingError, | ||
getDefaultHTTPClient: () => getDefaultHTTPClient, | ||
isHTTPResponse: () => isHTTPResponse | ||
isHTTPResponse: () => isHTTPResponse, | ||
isStreamClient: () => isStreamClient | ||
}); | ||
@@ -276,6 +279,8 @@ module.exports = __toCommonJS(src_exports); | ||
var FetchClient = class { | ||
#url; | ||
#queryURL; | ||
#streamURL; | ||
#keepalive; | ||
constructor({ url, fetch_keepalive }) { | ||
this.#url = new URL("/query/1", url).toString(); | ||
this.#queryURL = new URL("/query/1", url).toString(); | ||
this.#streamURL = new URL("/stream/1", url).toString(); | ||
this.#keepalive = fetch_keepalive; | ||
@@ -295,3 +300,3 @@ } | ||
})() : AbortSignal.timeout(client_timeout_ms); | ||
const response = await fetch(this.#url, { | ||
const response = await fetch(this.#queryURL, { | ||
method, | ||
@@ -317,5 +322,75 @@ headers: { ...requestHeaders, "Content-Type": "application/json" }, | ||
} | ||
stream({ | ||
data, | ||
headers: requestHeaders, | ||
method | ||
}) { | ||
const request = new Request(this.#streamURL, { | ||
method, | ||
headers: { ...requestHeaders, "Content-Type": "application/json" }, | ||
body: JSON.stringify(data), | ||
keepalive: this.#keepalive | ||
}); | ||
const abortController = new AbortController(); | ||
const options = { | ||
signal: abortController.signal | ||
}; | ||
async function* reader() { | ||
const response = await fetch(request, options).catch((error) => { | ||
throw new NetworkError( | ||
"The network connection encountered a problem.", | ||
{ | ||
cause: error | ||
} | ||
); | ||
}); | ||
const status = response.status; | ||
if (!(status >= 200 && status < 400)) { | ||
const body2 = await response.json(); | ||
throw new ServiceError(body2, status); | ||
} | ||
const body = response.body; | ||
if (!body) { | ||
throw new Error("Response body is undefined."); | ||
} | ||
const reader2 = body.getReader(); | ||
for await (const line of readLines(reader2)) { | ||
yield line; | ||
} | ||
} | ||
return { | ||
read: reader(), | ||
close: () => { | ||
abortController.abort("Stream closed by the client."); | ||
} | ||
}; | ||
} | ||
close() { | ||
} | ||
}; | ||
async function* readLines(reader) { | ||
const textDecoder = new TextDecoder(); | ||
let partOfLine = ""; | ||
for await (const chunk of readChunks(reader)) { | ||
const chunkText = textDecoder.decode(chunk); | ||
const chunkLines = (partOfLine + chunkText).split("\n"); | ||
for (let i = 0; i < chunkLines.length - 1; i++) { | ||
yield chunkLines[i].trim(); | ||
} | ||
partOfLine = chunkLines[chunkLines.length - 1]; | ||
} | ||
if (partOfLine.trim() !== "") { | ||
yield partOfLine; | ||
} | ||
} | ||
async function* readChunks(reader) { | ||
let done = false; | ||
do { | ||
const readResult = await reader.read(); | ||
if (readResult.value !== void 0) { | ||
yield readResult.value; | ||
} | ||
done = readResult.done; | ||
} while (!done); | ||
} | ||
@@ -329,3 +404,3 @@ // src/http-client/node-http2-client.ts | ||
} | ||
var _clients, _http2_session_idle_ms, _http2_max_streams, _url, _numberOfUsers, _session, _getClientKey, getClientKey_fn, _closeForAll, closeForAll_fn, _connect, connect_fn, _doRequest, doRequest_fn; | ||
var _clients, _http2_session_idle_ms, _http2_max_streams, _url, _numberOfUsers, _session, _getClientKey, getClientKey_fn, _closeForAll, closeForAll_fn, _connect, connect_fn, _doRequest, doRequest_fn, _doStream, doStream_fn; | ||
var _NodeHTTP2Client = class { | ||
@@ -340,2 +415,3 @@ constructor({ | ||
__privateAdd(this, _doRequest); | ||
__privateAdd(this, _doStream); | ||
__privateAdd(this, _http2_session_idle_ms, void 0); | ||
@@ -390,2 +466,5 @@ __privateAdd(this, _http2_max_streams, void 0); | ||
} | ||
stream(req) { | ||
return __privateMethod(this, _doStream, doStream_fn).call(this, req); | ||
} | ||
close() { | ||
@@ -480,2 +559,72 @@ if (this.isClosed()) { | ||
}; | ||
_doStream = new WeakSet(); | ||
doStream_fn = function({ | ||
data: requestData, | ||
headers: requestHeaders, | ||
method | ||
}) { | ||
let resolveChunk; | ||
let rejectChunk; | ||
const setChunkPromise = () => new Promise((res, rej) => { | ||
resolveChunk = res; | ||
rejectChunk = rej; | ||
}); | ||
let chunkPromise = setChunkPromise(); | ||
let req; | ||
const onResponse = (http2ResponseHeaders) => { | ||
const status = Number( | ||
http2ResponseHeaders[http2.constants.HTTP2_HEADER_STATUS] | ||
); | ||
if (!(status >= 200 && status < 400)) { | ||
let responseData = ""; | ||
req.on("data", (chunk) => { | ||
responseData += chunk; | ||
}); | ||
req.on("end", () => { | ||
rejectChunk(new ServiceError(JSON.parse(responseData), status)); | ||
}); | ||
} else { | ||
let partOfLine = ""; | ||
req.on("data", (chunk) => { | ||
const chunkLines = (partOfLine + chunk).split("\n"); | ||
resolveChunk(chunkLines.map((s) => s.trim()).slice(0, -1)); | ||
chunkPromise = setChunkPromise(); | ||
partOfLine = chunkLines[chunkLines.length - 1]; | ||
}); | ||
req.on("end", () => { | ||
resolveChunk([partOfLine]); | ||
}); | ||
} | ||
}; | ||
const self2 = this; | ||
async function* reader() { | ||
var _a; | ||
const httpRequestHeaders = { | ||
...requestHeaders, | ||
[http2.constants.HTTP2_HEADER_PATH]: "/stream/1", | ||
[http2.constants.HTTP2_HEADER_METHOD]: method | ||
}; | ||
const session = __privateMethod(_a = self2, _connect, connect_fn).call(_a); | ||
req = session.request(httpRequestHeaders).setEncoding("utf8").on("error", (error) => { | ||
rejectChunk(error); | ||
}).on("response", onResponse); | ||
const body = JSON.stringify(requestData); | ||
req.write(body, "utf8"); | ||
req.end(); | ||
while (true) { | ||
const chunks = await chunkPromise; | ||
for (const chunk of chunks) { | ||
yield chunk; | ||
} | ||
} | ||
} | ||
return { | ||
read: reader(), | ||
close: () => { | ||
if (req) { | ||
req.close(); | ||
} | ||
} | ||
}; | ||
}; | ||
__privateAdd(NodeHTTP2Client, _getClientKey); | ||
@@ -487,2 +636,5 @@ __privateAdd(NodeHTTP2Client, _clients, /* @__PURE__ */ new Map()); | ||
var isHTTPResponse = (res) => res instanceof Object && "body" in res && "headers" in res && "status" in res; | ||
var isStreamClient = (client) => { | ||
return "stream" in client && typeof client.stream === "function"; | ||
}; | ||
var nodeHttp2IsSupported = () => { | ||
@@ -781,2 +933,10 @@ if (typeof process !== "undefined" && process && process.release?.name === "node") { | ||
// src/values/stream.ts | ||
var StreamToken = class { | ||
token; | ||
constructor(token) { | ||
this.token = token; | ||
} | ||
}; | ||
// src/tagged-type.ts | ||
@@ -840,2 +1000,4 @@ var TaggedTypeFormat = class { | ||
return value["@object"]; | ||
} else if (value["@stream"]) { | ||
return new StreamToken(value["@stream"]); | ||
} | ||
@@ -925,3 +1087,4 @@ return value; | ||
); | ||
} | ||
}, | ||
streamToken: (value) => value.token | ||
}; | ||
@@ -968,2 +1131,4 @@ var encode = (input) => { | ||
return encodeMap["set"](input); | ||
} else if (input instanceof StreamToken) { | ||
return encodeMap["streamToken"](input); | ||
} else { | ||
@@ -1020,3 +1185,3 @@ return encodeMap["object"](input); | ||
// src/util/package-version.ts | ||
var packageVersion = "1.3.1"; | ||
var packageVersion = "1.4.0-beta.0"; | ||
@@ -1345,2 +1510,21 @@ // src/util/environment.ts | ||
} | ||
stream(tokenOrQuery, options) { | ||
if (__privateGet(this, _isClosed)) { | ||
throw new ClientClosedError( | ||
"Your client is closed. No further requests can be issued." | ||
); | ||
} | ||
const streamClient = __privateGet(this, _httpClient); | ||
if (isStreamClient(streamClient)) { | ||
const streamClientConfig = { | ||
...__privateGet(this, _clientConfiguration), | ||
httpStreamClient: streamClient, | ||
...options | ||
}; | ||
const tokenOrGetToken = tokenOrQuery instanceof Query2 ? () => this.query(tokenOrQuery).then((res) => res.data) : tokenOrQuery; | ||
return new StreamClient(tokenOrGetToken, streamClientConfig); | ||
} else { | ||
throw new ClientError("Streaming is not supported by this client."); | ||
} | ||
} | ||
}; | ||
@@ -1358,12 +1542,12 @@ var Client = _Client; | ||
const backoffMs = Math.min(Math.random() * 2 ** attempt, maxBackoff) * 1e3; | ||
const wait = (ms) => new Promise((r) => setTimeout(r, ms)); | ||
attempt += 1; | ||
return __privateMethod(this, _query, query_fn).call(this, queryInterpolation, options, attempt).catch((e) => { | ||
if (e instanceof ThrottlingError && attempt < maxAttempts) { | ||
return wait(backoffMs).then( | ||
() => __privateMethod(this, _queryWithRetries, queryWithRetries_fn).call(this, queryInterpolation, options, attempt) | ||
); | ||
try { | ||
return await __privateMethod(this, _query, query_fn).call(this, queryInterpolation, options, attempt); | ||
} catch (error) { | ||
if (error instanceof ThrottlingError && attempt < maxAttempts) { | ||
await wait(backoffMs); | ||
return __privateMethod(this, _queryWithRetries, queryWithRetries_fn).call(this, queryInterpolation, options, attempt); | ||
} | ||
throw e; | ||
}); | ||
throw error; | ||
} | ||
}; | ||
@@ -1515,33 +1699,20 @@ _getError = new WeakSet(); | ||
setHeaders_fn = function(fromObject, headerObject) { | ||
for (const entry of Object.entries(fromObject)) { | ||
if ([ | ||
"format", | ||
"query_timeout_ms", | ||
"linearized", | ||
"max_contention_retries", | ||
"traceparent", | ||
"typecheck", | ||
"query_tags" | ||
].includes(entry[0])) { | ||
let headerValue; | ||
let headerKey = `x-${entry[0].replaceAll("_", "-")}`; | ||
if ("query_tags" === entry[0]) { | ||
headerValue = Object.entries(entry[1]).map((tag) => tag.join("=")).join(","); | ||
} else { | ||
if (typeof entry[1] === "string") { | ||
headerValue = entry[1]; | ||
} else { | ||
headerValue = String(entry[1]); | ||
} | ||
} | ||
if ("traceparent" === entry[0]) { | ||
headerKey = entry[0]; | ||
} | ||
headerObject[headerKey] = headerValue; | ||
const setHeader = (header, value, transform = (v) => String(v)) => { | ||
if (value !== void 0) { | ||
headerObject[header] = transform(value); | ||
} | ||
} | ||
if (headerObject["x-last-txn-ts"] === void 0 && __privateGet(this, _lastTxnTs) !== void 0) { | ||
headerObject["x-last-txn-ts"] = __privateGet(this, _lastTxnTs); | ||
} | ||
headerObject["x-driver-env"] = __privateGet(_Client, _driverEnvHeader); | ||
}; | ||
setHeader("x-format", fromObject.format); | ||
setHeader("x-typecheck", fromObject.typecheck); | ||
setHeader("x-query-timeout-ms", fromObject.query_timeout_ms); | ||
setHeader("x-linearized", fromObject.linearized); | ||
setHeader("x-max-contention-retries", fromObject.max_contention_retries); | ||
setHeader("traceparent", fromObject.traceparent); | ||
setHeader( | ||
"x-query-tags", | ||
fromObject.query_tags, | ||
(tags) => Object.entries(tags).map((tag) => tag.join("=")).join(",") | ||
); | ||
setHeader("x-last-txn-ts", __privateGet(this, _lastTxnTs), (v) => v); | ||
setHeader("x-driver-env", __privateGet(_Client, _driverEnvHeader)); | ||
}; | ||
@@ -1559,3 +1730,5 @@ _validateConfiguration = new WeakSet(); | ||
"fetch_keepalive", | ||
"http2_max_streams" | ||
"http2_max_streams", | ||
"max_backoff", | ||
"max_attempts" | ||
]; | ||
@@ -1580,4 +1753,149 @@ required_options.forEach((option) => { | ||
} | ||
if (config.max_backoff <= 0) { | ||
throw new RangeError(`'max_backoff' must be greater than zero.`); | ||
} | ||
if (config.max_attempts <= 0) { | ||
throw new RangeError(`'max_attempts' must be greater than zero.`); | ||
} | ||
}; | ||
__privateAdd(Client, _driverEnvHeader, getDriverEnv()); | ||
var StreamClient = class { | ||
closed = false; | ||
#clientConfiguration; | ||
#connectionAttempts = 0; | ||
#query; | ||
#last_ts; | ||
#streamAdapter; | ||
#streamToken; | ||
constructor(token, clientConfiguration) { | ||
if (token instanceof StreamToken) { | ||
this.#query = () => Promise.resolve(token); | ||
} else { | ||
this.#query = token; | ||
} | ||
this.#clientConfiguration = clientConfiguration; | ||
this.#validateConfiguration(); | ||
} | ||
start(onEvent, onError) { | ||
if (typeof onEvent !== "function") { | ||
throw new TypeError( | ||
`Expected a function as the 'onEvent' argument, but received ${typeof onEvent}. Please provide a valid function.` | ||
); | ||
} | ||
if (onError && typeof onError !== "function") { | ||
throw new TypeError( | ||
`Expected a function as the 'onError' argument, but received ${typeof onError}. Please provide a valid function.` | ||
); | ||
} | ||
const run = async () => { | ||
try { | ||
for await (const event of this) { | ||
onEvent(event); | ||
} | ||
} catch (error) { | ||
if (onError) { | ||
onError(error); | ||
} | ||
} | ||
}; | ||
run(); | ||
} | ||
async *[Symbol.asyncIterator]() { | ||
if (this.closed) { | ||
throw new ClientError("The stream has been closed and cannot be reused."); | ||
} | ||
if (!this.#streamToken) { | ||
this.#streamToken = await this.#query().then((maybeStreamToken) => { | ||
if (!(maybeStreamToken instanceof StreamToken)) { | ||
throw new ClientError( | ||
`Error requesting a stream token. Expected a StreamToken as the query result, but received ${typeof maybeStreamToken}. Your query must return the result of '<Set>.toStream' or '<Set>.changesOn') | ||
Query result: ${JSON.stringify(maybeStreamToken, null)}` | ||
); | ||
} | ||
return maybeStreamToken; | ||
}); | ||
} | ||
this.#connectionAttempts = 1; | ||
while (!this.closed) { | ||
const backoffMs = Math.min( | ||
Math.random() * 2 ** this.#connectionAttempts, | ||
this.#clientConfiguration.max_backoff | ||
) * 1e3; | ||
try { | ||
for await (const event of this.#startStream(this.#last_ts)) { | ||
yield event; | ||
} | ||
} catch (error) { | ||
if (error instanceof FaunaError || this.#connectionAttempts >= this.#clientConfiguration.max_attempts) { | ||
this.close(); | ||
throw error; | ||
} | ||
this.#connectionAttempts += 1; | ||
await wait(backoffMs); | ||
} | ||
} | ||
} | ||
close() { | ||
if (this.#streamAdapter) { | ||
this.#streamAdapter.close(); | ||
this.#streamAdapter = void 0; | ||
} | ||
this.closed = true; | ||
} | ||
get last_ts() { | ||
return this.#last_ts; | ||
} | ||
async *#startStream(start_ts) { | ||
const streamToken = this.#streamToken; | ||
const headers = { | ||
Authorization: `Bearer ${this.#clientConfiguration.secret}` | ||
}; | ||
const streamAdapter = this.#clientConfiguration.httpStreamClient.stream({ | ||
data: { token: streamToken.token, start_ts }, | ||
headers, | ||
method: "POST" | ||
}); | ||
this.#streamAdapter = streamAdapter; | ||
for await (const event of streamAdapter.read) { | ||
const deserializedEvent = TaggedTypeFormat.decode(event, { | ||
long_type: this.#clientConfiguration.long_type | ||
}); | ||
if (deserializedEvent.type === "error") { | ||
this.close(); | ||
throw new ServiceError(deserializedEvent, 400); | ||
} | ||
this.#last_ts = deserializedEvent.txn_ts; | ||
if (deserializedEvent.type === "start") { | ||
deserializedEvent.type = "status"; | ||
} | ||
if (!this.#clientConfiguration.status_events && deserializedEvent.type === "status") { | ||
continue; | ||
} | ||
yield deserializedEvent; | ||
} | ||
} | ||
#validateConfiguration() { | ||
const config = this.#clientConfiguration; | ||
const required_options = [ | ||
"long_type", | ||
"httpStreamClient", | ||
"max_backoff", | ||
"max_attempts", | ||
"secret" | ||
]; | ||
required_options.forEach((option) => { | ||
if (config[option] === void 0) { | ||
throw new TypeError( | ||
`ClientConfiguration option '${option}' must be defined.` | ||
); | ||
} | ||
}); | ||
if (config.max_backoff <= 0) { | ||
throw new RangeError(`'max_backoff' must be greater than zero.`); | ||
} | ||
if (config.max_attempts <= 0) { | ||
throw new RangeError(`'max_attempts' must be greater than zero.`); | ||
} | ||
} | ||
}; | ||
var QUERY_CHECK_FAILURE_CODES = [ | ||
@@ -1590,2 +1908,5 @@ "invalid_function_definition", | ||
]; | ||
function wait(ms) { | ||
return new Promise((r) => setTimeout(r, ms)); | ||
} | ||
// Annotate the CommonJS export names for ESM import in node: | ||
@@ -1624,2 +1945,4 @@ 0 && (module.exports = { | ||
SetIterator, | ||
StreamClient, | ||
StreamToken, | ||
TaggedTypeFormat, | ||
@@ -1631,4 +1954,5 @@ ThrottlingError, | ||
getDefaultHTTPClient, | ||
isHTTPResponse | ||
isHTTPResponse, | ||
isStreamClient | ||
}); | ||
//# sourceMappingURL=index.js.map |
/** The current package version. */ | ||
export declare const packageVersion = "1.3.1"; | ||
export declare const packageVersion = "1.4.0-beta.0"; |
export * from "./date-time"; | ||
export * from "./doc"; | ||
export * from "./set"; | ||
export * from "./stream"; |
@@ -1,2 +0,2 @@ | ||
import { DateStub, Document, DocumentReference, EmbeddedSet, Module, NamedDocument, NamedDocumentReference, NullDocument, Page, TimeStub } from "./values"; | ||
import { DateStub, Document, DocumentReference, EmbeddedSet, Module, NamedDocument, NamedDocumentReference, NullDocument, Page, StreamToken, TimeStub } from "./values"; | ||
/** | ||
@@ -256,2 +256,22 @@ * A request to make to Fauna. | ||
*/ | ||
export type QueryValue = null | string | number | bigint | boolean | QueryValueObject | Array<QueryValue> | DateStub | TimeStub | Module | Document | DocumentReference | NamedDocument | NamedDocumentReference | NullDocument | Page<QueryValue> | EmbeddedSet; | ||
export type QueryValue = null | string | number | bigint | boolean | QueryValueObject | Array<QueryValue> | DateStub | TimeStub | Module | Document | DocumentReference | NamedDocument | NamedDocumentReference | NullDocument | Page<QueryValue> | EmbeddedSet | StreamToken; | ||
export type StreamRequest = { | ||
token: string; | ||
start_ts?: number; | ||
}; | ||
export type StreamEventType = "status" | "add" | "remove" | "update" | "error"; | ||
export type StreamEventStatus = { | ||
type: "status"; | ||
txn_ts: number; | ||
stats: QueryStats; | ||
}; | ||
export type StreamEventData<T extends QueryValue> = { | ||
type: "add" | "remove" | "update"; | ||
txn_ts: number; | ||
stats: QueryStats; | ||
data: T; | ||
}; | ||
export type StreamEventError = { | ||
type: "error"; | ||
} & QueryFailure; | ||
export type StreamEvent<T extends QueryValue> = StreamEventStatus | StreamEventData<T> | StreamEventError; |
{ | ||
"name": "fauna", | ||
"version": "1.3.1", | ||
"version": "1.4.0-beta.0", | ||
"description": "A driver to query Fauna databases in browsers, Node.js, and other Javascript runtimes", | ||
@@ -45,3 +45,3 @@ "homepage": "https://fauna.com", | ||
"lint": "eslint -f unix \"src/**/*.{ts,tsx}\"", | ||
"fauna-local": "docker start faunadb-local || docker run --rm -d --name faunadb-local -p 8443:8443 -p 8084:8084 fauna/faunadb", | ||
"fauna-local": "docker start faunadb-local || docker run --rm -d --name faunadb-local -p 8443:8443 -p 8084:8084 --mount type=bind,source=\"$(pwd)\"/docker/feature-flags.json,target=/etc/feature-flag-periodic.d/feature-flags.json fauna/faunadb", | ||
"fauna-local-alt-port": "docker start faunadb-local-alt-port || docker run --rm -d --name faunadb-local-alt-port -p 7443:8443 -p 7084:8084 fauna/faunadb", | ||
@@ -48,0 +48,0 @@ "prepare": "husky install", |
@@ -13,3 +13,3 @@ # The Official Javascript Driver for [Fauna](https://fauna.com). | ||
- [A JavaScript driver for Fauna.](#a-javascript-driver-for-fauna) | ||
- [The Official Javascript Driver for Fauna.](#the-official-javascript-driver-for-fauna) | ||
- [Quick-Start](#quick-start) | ||
@@ -27,2 +27,5 @@ - [Supported Runtimes](#supported-runtimes) | ||
- [Client Configuration](#client-configuration) | ||
- [Retry](#retry) | ||
- [Max Attempts](#max-attempts) | ||
- [Max Backoff](#max-backoff) | ||
- [Timeouts](#timeouts) | ||
@@ -34,2 +37,3 @@ - [Query Timeout](#query-timeout) | ||
- [Query Statistics](#query-statistics) | ||
- [Streaming](#streaming) | ||
- [Contributing](#contributing) | ||
@@ -408,2 +412,81 @@ - [Setting up this Repo](#setting-up-this-repo) | ||
## Streaming | ||
Obtain a stream token using a regular query with either the `toStream()` or `changesOn()` FQL methods on a Set. | ||
```javascript | ||
import { Client, fql } from "fauna" | ||
const client = new Client({ secret: FAUNA_SECRET }) | ||
const response = await client.query(fql` | ||
let set = MyCollection.all() | ||
{ | ||
initialPage: set.pageSize(10), | ||
streamToken: set.toStream() | ||
} | ||
`); | ||
const { initialPage, streamToken } = response.data; | ||
const stream = client.stream(streamToken) | ||
``` | ||
The driver will take care of the initial request to convert to a stream if you provide a Query | ||
```javascript | ||
import { Client, fql } from "fauna" | ||
const client = new Client({ secret: FAUNA_SECRET }) | ||
const stream = await client.stream(fql`MyCollection.all().changesOn(.field1, .field2)`) | ||
``` | ||
There are two Two ways to initiate the stream: | ||
1. Async Iterator | ||
2. Callbacks | ||
_Async Iterator example_ | ||
```javascript | ||
try { | ||
for await (const event of stream) { | ||
switch (event.type) { | ||
case "update": | ||
case "add": | ||
case "remove": | ||
console.log("Stream event:", event); | ||
// ... | ||
break; | ||
} | ||
} | ||
} catch (error) { | ||
// An error will be handled here if Fauna returns a terminal, "error" event, or | ||
// if Fauna returns a non-200 response when trying to connect, or | ||
// if the max number of retries on network errors is reached. | ||
// ... handle fatal error | ||
} | ||
``` | ||
_Callbacks example_ | ||
```javascript | ||
stream.start( | ||
function onEvent(event) { | ||
switch (event.type) { | ||
case "update": | ||
case "add": | ||
case "remove": | ||
console.log("Stream event:", event); | ||
// ... | ||
break; | ||
} | ||
}, | ||
function onFatalError(error) { | ||
// An error will be handled here if Fauna returns a terminal, "error" event, or | ||
// if Fauna returns a non-200 response when trying to connect, or | ||
// if the max number of retries on network errors is reached. | ||
// ... handle fatal error | ||
} | ||
); | ||
``` | ||
# Contributing | ||
@@ -410,0 +493,0 @@ |
@@ -0,1 +1,2 @@ | ||
import { HTTPStreamClient } from "./http-client"; | ||
import type { ValueFormat } from "./wire-protocol"; | ||
@@ -159,2 +160,43 @@ | ||
/** | ||
* Configuration for a streaming client. This typically comes from the `Client` | ||
* instance configuration. | ||
*/ | ||
export type StreamClientConfiguration = { | ||
/** | ||
* The underlying {@link HTTPStreamClient} that will execute the actual HTTP calls | ||
*/ | ||
httpStreamClient: HTTPStreamClient; | ||
/** | ||
* Controls what Javascript type to deserialize {@link https://fqlx-beta--fauna-docs.netlify.app/fqlx/beta/reference/language/types#long | Fauna longs} to. | ||
* @see {@link ClientConfiguration.long_type} | ||
*/ | ||
long_type: "number" | "bigint"; | ||
/** | ||
* Max attempts for retryable exceptions. | ||
*/ | ||
max_attempts: number; | ||
/** | ||
* Max backoff between retries. | ||
*/ | ||
max_backoff: number; | ||
/** | ||
* A secret for your Fauna DB, used to authorize your queries. | ||
* @see https://docs.fauna.com/fauna/current/security/keys | ||
*/ | ||
secret: string; | ||
/** | ||
* Indicates if stream should include "status" events, periodic events that | ||
* update the client with the latest valid timestamp (in the event of a | ||
* dropped connection) as well as metrics about about the cost of maintaining | ||
* the stream other than the cost of the received events. | ||
*/ | ||
status_events?: boolean; | ||
}; | ||
/** | ||
* A extensible set of endpoints for calling Fauna. | ||
@@ -161,0 +203,0 @@ * @remarks Most clients will will not need to extend this set. |
@@ -1,3 +0,7 @@ | ||
import { ClientConfiguration, endpoints } from "./client-configuration"; | ||
import { | ||
ClientConfiguration, | ||
StreamClientConfiguration, | ||
endpoints, | ||
} from "./client-configuration"; | ||
import { | ||
AuthenticationError, | ||
@@ -19,5 +23,9 @@ AuthorizationError, | ||
InvalidRequestError, | ||
FaunaError, | ||
} from "./errors"; | ||
import { | ||
HTTPStreamClient, | ||
StreamAdapter, | ||
getDefaultHTTPClient, | ||
isStreamClient, | ||
isHTTPResponse, | ||
@@ -29,3 +37,3 @@ type HTTPClient, | ||
import { getDriverEnv } from "./util/environment"; | ||
import { EmbeddedSet, Page, SetIterator } from "./values"; | ||
import { EmbeddedSet, Page, SetIterator, StreamToken } from "./values"; | ||
import { | ||
@@ -35,2 +43,5 @@ isQueryFailure, | ||
QueryInterpolation, | ||
StreamEvent, | ||
StreamEventData, | ||
StreamEventStatus, | ||
type QueryFailure, | ||
@@ -86,3 +97,3 @@ type QueryOptions, | ||
/** The underlying {@link HTTPClient} client. */ | ||
readonly #httpClient: HTTPClient; | ||
readonly #httpClient: HTTPClient & Partial<HTTPStreamClient>; | ||
/** The last transaction timestamp this client has seen */ | ||
@@ -271,2 +282,88 @@ #lastTxnTs?: number; | ||
/** | ||
* Initialize a streaming request to Fauna | ||
* @param query - A string-encoded streaming token, or a {@link Query} | ||
* @returns A {@link StreamClient} that which can be used to listen to a stream | ||
* of events | ||
* | ||
* @example | ||
* ```javascript | ||
* const stream = client.stream(fql`MyCollection.all().toStream()`) | ||
* | ||
* try { | ||
* for await (const event of stream) { | ||
* switch (event.type) { | ||
* case "update": | ||
* case "add": | ||
* case "remove": | ||
* console.log("Stream update:", event); | ||
* // ... | ||
* break; | ||
* } | ||
* } | ||
* } catch (error) { | ||
* // An error will be handled here if Fauna returns a terminal, "error" event, or | ||
* // if Fauna returns a non-200 response when trying to connect, or | ||
* // if the max number of retries on network errors is reached. | ||
* | ||
* // ... handle fatal error | ||
* }; | ||
* ``` | ||
* | ||
* @example | ||
* ```javascript | ||
* const stream = client.stream(fql`MyCollection.all().toStream()`) | ||
* | ||
* stream.start( | ||
* function onEvent(event) { | ||
* switch (event.type) { | ||
* case "update": | ||
* case "add": | ||
* case "remove": | ||
* console.log("Stream update:", event); | ||
* // ... | ||
* break; | ||
* } | ||
* }, | ||
* function onError(error) { | ||
* // An error will be handled here if Fauna returns a terminal, "error" event, or | ||
* // if Fauna returns a non-200 response when trying to connect, or | ||
* // if the max number of retries on network errors is reached. | ||
* | ||
* // ... handle fatal error | ||
* } | ||
* ); | ||
* ``` | ||
*/ | ||
// TODO: implement options | ||
stream<T extends QueryValue>( | ||
tokenOrQuery: StreamToken | Query, | ||
options?: Partial<StreamClientConfiguration> | ||
): StreamClient<T> { | ||
if (this.#isClosed) { | ||
throw new ClientClosedError( | ||
"Your client is closed. No further requests can be issued." | ||
); | ||
} | ||
const streamClient = this.#httpClient; | ||
if (isStreamClient(streamClient)) { | ||
const streamClientConfig: StreamClientConfiguration = { | ||
...this.#clientConfiguration, | ||
httpStreamClient: streamClient, | ||
...options, | ||
}; | ||
const tokenOrGetToken = | ||
tokenOrQuery instanceof Query | ||
? () => this.query<StreamToken>(tokenOrQuery).then((res) => res.data) | ||
: tokenOrQuery; | ||
return new StreamClient(tokenOrGetToken, streamClientConfig); | ||
} else { | ||
throw new ClientError("Streaming is not supported by this client."); | ||
} | ||
} | ||
async #queryWithRetries<T extends QueryValue>( | ||
@@ -285,12 +382,13 @@ queryInterpolation: string | QueryInterpolation, | ||
const wait = (ms: number) => new Promise((r) => setTimeout(r, ms)); | ||
attempt += 1; | ||
return this.#query<T>(queryInterpolation, options, attempt).catch((e) => { | ||
if (e instanceof ThrottlingError && attempt < maxAttempts) { | ||
return wait(backoffMs).then(() => | ||
this.#queryWithRetries<T>(queryInterpolation, options, attempt) | ||
); | ||
try { | ||
return await this.#query<T>(queryInterpolation, options, attempt); | ||
} catch (error) { | ||
if (error instanceof ThrottlingError && attempt < maxAttempts) { | ||
await wait(backoffMs); | ||
return this.#queryWithRetries<T>(queryInterpolation, options, attempt); | ||
} | ||
throw e; | ||
}); | ||
throw error; | ||
} | ||
} | ||
@@ -516,41 +614,25 @@ | ||
): void { | ||
for (const entry of Object.entries(fromObject)) { | ||
if ( | ||
[ | ||
"format", | ||
"query_timeout_ms", | ||
"linearized", | ||
"max_contention_retries", | ||
"traceparent", | ||
"typecheck", | ||
"query_tags", | ||
].includes(entry[0]) | ||
) { | ||
let headerValue: string; | ||
let headerKey = `x-${entry[0].replaceAll("_", "-")}`; | ||
if ("query_tags" === entry[0]) { | ||
headerValue = Object.entries(entry[1]) | ||
.map((tag) => tag.join("=")) | ||
.join(","); | ||
} else { | ||
if (typeof entry[1] === "string") { | ||
headerValue = entry[1]; | ||
} else { | ||
headerValue = String(entry[1]); | ||
} | ||
} | ||
if ("traceparent" === entry[0]) { | ||
headerKey = entry[0]; | ||
} | ||
headerObject[headerKey] = headerValue; | ||
const setHeader = <V>( | ||
header: string, | ||
value: V | undefined, | ||
transform: (v: V) => string | number = (v) => String(v) | ||
) => { | ||
if (value !== undefined) { | ||
headerObject[header] = transform(value); | ||
} | ||
} | ||
if ( | ||
headerObject["x-last-txn-ts"] === undefined && | ||
this.#lastTxnTs !== undefined | ||
) { | ||
headerObject["x-last-txn-ts"] = this.#lastTxnTs; | ||
} | ||
}; | ||
headerObject["x-driver-env"] = Client.#driverEnvHeader; | ||
setHeader("x-format", fromObject.format); | ||
setHeader("x-typecheck", fromObject.typecheck); | ||
setHeader("x-query-timeout-ms", fromObject.query_timeout_ms); | ||
setHeader("x-linearized", fromObject.linearized); | ||
setHeader("x-max-contention-retries", fromObject.max_contention_retries); | ||
setHeader("traceparent", fromObject.traceparent); | ||
setHeader("x-query-tags", fromObject.query_tags, (tags) => | ||
Object.entries(tags) | ||
.map((tag) => tag.join("=")) | ||
.join(",") | ||
); | ||
setHeader("x-last-txn-ts", this.#lastTxnTs, (v) => v); // x-last-txn-ts doesn't get stringified | ||
setHeader("x-driver-env", Client.#driverEnvHeader); | ||
} | ||
@@ -570,2 +652,4 @@ | ||
"http2_max_streams", | ||
"max_backoff", | ||
"max_attempts", | ||
]; | ||
@@ -593,5 +677,230 @@ required_options.forEach((option) => { | ||
} | ||
if (config.max_backoff <= 0) { | ||
throw new RangeError(`'max_backoff' must be greater than zero.`); | ||
} | ||
if (config.max_attempts <= 0) { | ||
throw new RangeError(`'max_attempts' must be greater than zero.`); | ||
} | ||
} | ||
} | ||
/** | ||
* A class to listen to Fauna streams. | ||
*/ | ||
export class StreamClient<T extends QueryValue = any> { | ||
/** Whether or not this stream has been closed */ | ||
closed = false; | ||
/** The stream client options */ | ||
#clientConfiguration: StreamClientConfiguration; | ||
/** A tracker for the number of connection attempts */ | ||
#connectionAttempts = 0; | ||
/** A lambda that returns a promise for a {@link StreamToken} */ | ||
#query: () => Promise<StreamToken>; | ||
/** The last `txn_ts` value received from events */ | ||
#last_ts?: number; | ||
/** A common interface to operate a stream from any HTTPStreamClient */ | ||
#streamAdapter?: StreamAdapter; | ||
/** A saved copy of the StreamToken once received */ | ||
#streamToken?: StreamToken; | ||
/** | ||
* | ||
* @param query - A lambda that returns a promise for a {@link StreamToken} | ||
* @param clientConfiguration - The {@link ClientConfiguration} to apply | ||
* @param httpStreamClient - The underlying {@link HTTPStreamClient} that will | ||
* execute the actual HTTP calls | ||
* @example | ||
* ```typescript | ||
* const streamClient = client.stream(streamToken); | ||
* ``` | ||
*/ | ||
// TODO: implement stream-specific options | ||
constructor( | ||
token: StreamToken | (() => Promise<StreamToken>), | ||
clientConfiguration: StreamClientConfiguration | ||
) { | ||
if (token instanceof StreamToken) { | ||
this.#query = () => Promise.resolve(token); | ||
} else { | ||
this.#query = token; | ||
} | ||
this.#clientConfiguration = clientConfiguration; | ||
this.#validateConfiguration(); | ||
} | ||
/** | ||
* A synchronous method to start listening to the stream and handle events | ||
* using callbacks. | ||
* @param onEvent - A callback function to handle each event | ||
* @param onError - An Optional callback function to handle errors. If none is | ||
* provided, error will not be handled, and the stream will simply end. | ||
*/ | ||
start( | ||
onEvent: (event: StreamEventData<T> | StreamEventStatus) => void, | ||
onError?: (error: Error) => void | ||
) { | ||
if (typeof onEvent !== "function") { | ||
throw new TypeError( | ||
`Expected a function as the 'onEvent' argument, but received ${typeof onEvent}. Please provide a valid function.` | ||
); | ||
} | ||
if (onError && typeof onError !== "function") { | ||
throw new TypeError( | ||
`Expected a function as the 'onError' argument, but received ${typeof onError}. Please provide a valid function.` | ||
); | ||
} | ||
const run = async () => { | ||
try { | ||
for await (const event of this) { | ||
onEvent(event); | ||
} | ||
} catch (error) { | ||
if (onError) { | ||
onError(error as Error); | ||
} | ||
} | ||
}; | ||
run(); | ||
} | ||
async *[Symbol.asyncIterator](): AsyncGenerator< | ||
StreamEventData<T> | StreamEventStatus | ||
> { | ||
if (this.closed) { | ||
throw new ClientError("The stream has been closed and cannot be reused."); | ||
} | ||
if (!this.#streamToken) { | ||
this.#streamToken = await this.#query().then((maybeStreamToken) => { | ||
if (!(maybeStreamToken instanceof StreamToken)) { | ||
throw new ClientError( | ||
`Error requesting a stream token. Expected a StreamToken as the query result, but received ${typeof maybeStreamToken}. Your query must return the result of '<Set>.toStream' or '<Set>.changesOn')\n` + | ||
`Query result: ${JSON.stringify(maybeStreamToken, null)}` | ||
); | ||
} | ||
return maybeStreamToken; | ||
}); | ||
} | ||
this.#connectionAttempts = 1; | ||
while (!this.closed) { | ||
const backoffMs = | ||
Math.min( | ||
Math.random() * 2 ** this.#connectionAttempts, | ||
this.#clientConfiguration.max_backoff | ||
) * 1_000; | ||
try { | ||
for await (const event of this.#startStream(this.#last_ts)) { | ||
yield event; | ||
} | ||
} catch (error: any) { | ||
if ( | ||
error instanceof FaunaError || | ||
this.#connectionAttempts >= this.#clientConfiguration.max_attempts | ||
) { | ||
// A terminal error from Fauna | ||
this.close(); | ||
throw error; | ||
} | ||
this.#connectionAttempts += 1; | ||
await wait(backoffMs); | ||
} | ||
} | ||
} | ||
close() { | ||
if (this.#streamAdapter) { | ||
this.#streamAdapter.close(); | ||
this.#streamAdapter = undefined; | ||
} | ||
this.closed = true; | ||
} | ||
get last_ts(): number | undefined { | ||
return this.#last_ts; | ||
} | ||
async *#startStream( | ||
start_ts?: number | ||
): AsyncGenerator<StreamEventData<T> | StreamEventStatus> { | ||
// Safety: This method must only be called after a stream token has been acquired | ||
const streamToken = this.#streamToken as StreamToken; | ||
const headers = { | ||
Authorization: `Bearer ${this.#clientConfiguration.secret}`, | ||
}; | ||
const streamAdapter = this.#clientConfiguration.httpStreamClient.stream({ | ||
data: { token: streamToken.token, start_ts }, | ||
headers, | ||
method: "POST", | ||
}); | ||
this.#streamAdapter = streamAdapter; | ||
for await (const event of streamAdapter.read) { | ||
// stream events are always tagged | ||
const deserializedEvent: StreamEvent<T> = TaggedTypeFormat.decode(event, { | ||
long_type: this.#clientConfiguration.long_type, | ||
}); | ||
if (deserializedEvent.type === "error") { | ||
// Errors sent from Fauna are assumed fatal | ||
this.close(); | ||
// TODO: replace with appropriate class from existing error heirarchy | ||
throw new ServiceError(deserializedEvent, 400); | ||
} | ||
this.#last_ts = deserializedEvent.txn_ts; | ||
// TODO: remove this once all environments have updated the events to use "status" instead of "start" | ||
if ((deserializedEvent.type as any) === "start") { | ||
deserializedEvent.type = "status"; | ||
} | ||
if ( | ||
!this.#clientConfiguration.status_events && | ||
deserializedEvent.type === "status" | ||
) { | ||
continue; | ||
} | ||
yield deserializedEvent; | ||
} | ||
} | ||
#validateConfiguration() { | ||
const config = this.#clientConfiguration; | ||
const required_options: (keyof StreamClientConfiguration)[] = [ | ||
"long_type", | ||
"httpStreamClient", | ||
"max_backoff", | ||
"max_attempts", | ||
"secret", | ||
]; | ||
required_options.forEach((option) => { | ||
if (config[option] === undefined) { | ||
throw new TypeError( | ||
`ClientConfiguration option '${option}' must be defined.` | ||
); | ||
} | ||
}); | ||
if (config.max_backoff <= 0) { | ||
throw new RangeError(`'max_backoff' must be greater than zero.`); | ||
} | ||
if (config.max_attempts <= 0) { | ||
throw new RangeError(`'max_attempts' must be greater than zero.`); | ||
} | ||
} | ||
} | ||
// Private types and constants for internal logic. | ||
@@ -606,1 +915,5 @@ | ||
]; | ||
function wait(ms: number) { | ||
return new Promise((r) => setTimeout(r, ms)); | ||
} |
/** following reference needed to include types for experimental fetch API in Node */ | ||
/// <reference lib="dom" /> | ||
import { NetworkError } from "../errors"; | ||
import { NetworkError, ServiceError } from "../errors"; | ||
import { | ||
@@ -10,2 +10,5 @@ HTTPClient, | ||
HTTPResponse, | ||
HTTPStreamRequest, | ||
HTTPStreamClient, | ||
StreamAdapter, | ||
} from "./http-client"; | ||
@@ -16,8 +19,10 @@ | ||
*/ | ||
export class FetchClient implements HTTPClient { | ||
#url: string; | ||
export class FetchClient implements HTTPClient, HTTPStreamClient { | ||
#queryURL: string; | ||
#streamURL: string; | ||
#keepalive: boolean; | ||
constructor({ url, fetch_keepalive }: HTTPClientOptions) { | ||
this.#url = new URL("/query/1", url).toString(); | ||
this.#queryURL = new URL("/query/1", url).toString(); | ||
this.#streamURL = new URL("/stream/1", url).toString(); | ||
this.#keepalive = fetch_keepalive; | ||
@@ -43,3 +48,3 @@ } | ||
const response = await fetch(this.#url, { | ||
const response = await fetch(this.#queryURL, { | ||
method, | ||
@@ -70,2 +75,55 @@ headers: { ...requestHeaders, "Content-Type": "application/json" }, | ||
/** {@inheritDoc HTTPStreamClient.stream} */ | ||
stream({ | ||
data, | ||
headers: requestHeaders, | ||
method, | ||
}: HTTPStreamRequest): StreamAdapter { | ||
const request = new Request(this.#streamURL, { | ||
method, | ||
headers: { ...requestHeaders, "Content-Type": "application/json" }, | ||
body: JSON.stringify(data), | ||
keepalive: this.#keepalive, | ||
}); | ||
const abortController = new AbortController(); | ||
const options = { | ||
signal: abortController.signal, | ||
}; | ||
async function* reader() { | ||
const response = await fetch(request, options).catch((error) => { | ||
throw new NetworkError( | ||
"The network connection encountered a problem.", | ||
{ | ||
cause: error, | ||
} | ||
); | ||
}); | ||
const status = response.status; | ||
if (!(status >= 200 && status < 400)) { | ||
const body = await response.json(); | ||
throw new ServiceError(body, status); | ||
} | ||
const body = response.body; | ||
if (!body) { | ||
throw new Error("Response body is undefined."); | ||
} | ||
const reader = body.getReader(); | ||
for await (const line of readLines(reader)) { | ||
yield line; | ||
} | ||
} | ||
return { | ||
read: reader(), | ||
close: () => { | ||
abortController.abort("Stream closed by the client."); | ||
}, | ||
}; | ||
} | ||
/** {@inheritDoc HTTPClient.close} */ | ||
@@ -76,1 +134,41 @@ close() { | ||
} | ||
/** | ||
* Get individual lines from the stream | ||
* | ||
* The stream may be broken into arbitrary chunks, but the events are delimited by a newline character. | ||
* | ||
* @param reader - The stream reader | ||
*/ | ||
async function* readLines(reader: ReadableStreamDefaultReader<Uint8Array>) { | ||
const textDecoder = new TextDecoder(); | ||
let partOfLine = ""; | ||
for await (const chunk of readChunks(reader)) { | ||
const chunkText = textDecoder.decode(chunk); | ||
const chunkLines = (partOfLine + chunkText).split("\n"); | ||
// Yield all complete lines | ||
for (let i = 0; i < chunkLines.length - 1; i++) { | ||
yield chunkLines[i].trim(); | ||
} | ||
// Store the partial line | ||
partOfLine = chunkLines[chunkLines.length - 1]; | ||
} | ||
// Yield the remaining partial line if any | ||
if (partOfLine.trim() !== "") { | ||
yield partOfLine; | ||
} | ||
} | ||
async function* readChunks(reader: ReadableStreamDefaultReader<Uint8Array>) { | ||
let done = false; | ||
do { | ||
const readResult = await reader.read(); | ||
if (readResult.value !== undefined) { | ||
yield readResult.value; | ||
} | ||
done = readResult.done; | ||
} while (!done); | ||
} |
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
import type { Client } from "../client"; | ||
import { QueryRequest } from "../wire-protocol"; | ||
import { QueryRequest, StreamRequest } from "../wire-protocol"; | ||
@@ -68,1 +68,40 @@ /** | ||
} | ||
/** | ||
* An object representing an http request. | ||
* The {@link Client} provides this to the {@link HTTPStreamClient} implementation. | ||
*/ | ||
export type HTTPStreamRequest = { | ||
/** The encoded Fauna query to send */ | ||
// TODO: Allow type to be a QueryRequest once implemented by the db | ||
data: StreamRequest; | ||
/** Headers in object format */ | ||
headers: Record<string, string | undefined>; | ||
/** HTTP method to use */ | ||
method: "POST"; | ||
}; | ||
/** | ||
* A common interface for a StreamClient to operate a stream from any HTTPStreamClient | ||
*/ | ||
export interface StreamAdapter { | ||
read: AsyncGenerator<string>; | ||
close: () => void; | ||
} | ||
/** | ||
* An interface to provide implementation-specific, asyncronous http calls. | ||
* This driver provides default implementations for common environments. Users | ||
* can configure the {@link Client} to use custom implementations if desired. | ||
*/ | ||
export interface HTTPStreamClient { | ||
/** | ||
* Makes an HTTP request and returns the response | ||
* @param req - an {@link HTTPStreamRequest} | ||
* @returns A Promise<{@link HTTPResponse}> | ||
* @throws {@link NetworkError} on request timeout or other network issue. | ||
*/ | ||
stream(req: HTTPStreamRequest): StreamAdapter; | ||
} |
import { FetchClient } from "./fetch-client"; | ||
import { NodeHTTP2Client } from "./node-http2-client"; | ||
import { | ||
HTTPClient, | ||
HTTPClientOptions, | ||
HTTPRequest, | ||
HTTPResponse, | ||
HTTPStreamClient, | ||
} from "./http-client"; | ||
import { NodeHTTP2Client } from "./node-http2-client"; | ||
export const getDefaultHTTPClient = (options: HTTPClientOptions): HTTPClient => | ||
export * from "./fetch-client"; | ||
export * from "./http-client"; | ||
export * from "./node-http2-client"; | ||
export const getDefaultHTTPClient = ( | ||
options: HTTPClientOptions | ||
): HTTPClient & HTTPStreamClient => | ||
nodeHttp2IsSupported() | ||
@@ -18,3 +24,9 @@ ? NodeHTTP2Client.getClient(options) | ||
const nodeHttp2IsSupported = () => { | ||
export const isStreamClient = ( | ||
client: Partial<HTTPStreamClient> | ||
): client is HTTPStreamClient => { | ||
return "stream" in client && typeof client.stream === "function"; | ||
}; | ||
export const nodeHttp2IsSupported = () => { | ||
if ( | ||
@@ -34,3 +46,1 @@ typeof process !== "undefined" && | ||
}; | ||
export { FetchClient, NodeHTTP2Client, HTTPClient, HTTPRequest, HTTPResponse }; |
@@ -12,4 +12,7 @@ let http2: any; | ||
HTTPResponse, | ||
HTTPStreamClient, | ||
HTTPStreamRequest, | ||
StreamAdapter, | ||
} from "./http-client"; | ||
import { NetworkError } from "../errors"; | ||
import { ServiceError, NetworkError } from "../errors"; | ||
@@ -26,3 +29,3 @@ // alias http2 types | ||
*/ | ||
export class NodeHTTP2Client implements HTTPClient { | ||
export class NodeHTTP2Client implements HTTPClient, HTTPStreamClient { | ||
static #clients: Map<string, NodeHTTP2Client> = new Map(); | ||
@@ -112,2 +115,7 @@ | ||
/** {@inheritDoc HTTPStreamClient.stream} */ | ||
stream(req: HTTPStreamRequest): StreamAdapter { | ||
return this.#doStream(req); | ||
} | ||
/** {@inheritDoc HTTPClient.close} */ | ||
@@ -176,3 +184,3 @@ close() { | ||
// data chunks in the response | ||
req.on("data", (chunk: any) => { | ||
req.on("data", (chunk: string) => { | ||
responseData += chunk; | ||
@@ -220,2 +228,108 @@ }); | ||
} | ||
/** {@inheritDoc HTTPStreamClient.stream} */ | ||
#doStream({ | ||
data: requestData, | ||
headers: requestHeaders, | ||
method, | ||
}: HTTPStreamRequest): StreamAdapter { | ||
let resolveChunk: (chunk: string[]) => void; | ||
let rejectChunk: (reason: any) => void; | ||
const setChunkPromise = () => | ||
new Promise<string[]>((res, rej) => { | ||
resolveChunk = res; | ||
rejectChunk = rej; | ||
}); | ||
let chunkPromise = setChunkPromise(); | ||
let req: ClientHttp2Stream; | ||
const onResponse = ( | ||
http2ResponseHeaders: IncomingHttpHeaders & IncomingHttpStatusHeader | ||
) => { | ||
const status = Number( | ||
http2ResponseHeaders[http2.constants.HTTP2_HEADER_STATUS] | ||
); | ||
if (!(status >= 200 && status < 400)) { | ||
// Get the error body and then throw an error | ||
let responseData = ""; | ||
// append response data to the data string every time we receive new | ||
// data chunks in the response | ||
req.on("data", (chunk: string) => { | ||
responseData += chunk; | ||
}); | ||
// Once the response is finished, resolve the promise | ||
// TODO: The Client contains the information for how to parse an error | ||
// into the appropriate class, so lift this logic out of the HTTPClient. | ||
req.on("end", () => { | ||
rejectChunk(new ServiceError(JSON.parse(responseData), status)); | ||
}); | ||
} else { | ||
let partOfLine = ""; | ||
// append response data to the data string every time we receive new | ||
// data chunks in the response | ||
req.on("data", (chunk: string) => { | ||
const chunkLines = (partOfLine + chunk).split("\n"); | ||
// Yield all complete lines | ||
resolveChunk(chunkLines.map((s) => s.trim()).slice(0, -1)); | ||
chunkPromise = setChunkPromise(); | ||
// Store the partial line | ||
partOfLine = chunkLines[chunkLines.length - 1]; | ||
}); | ||
// Once the response is finished, resolve the promise | ||
req.on("end", () => { | ||
resolveChunk([partOfLine]); | ||
}); | ||
} | ||
}; | ||
// eslint-disable-next-line @typescript-eslint/no-this-alias | ||
const self = this; | ||
async function* reader(): AsyncGenerator<string> { | ||
const httpRequestHeaders: OutgoingHttpHeaders = { | ||
...requestHeaders, | ||
[http2.constants.HTTP2_HEADER_PATH]: "/stream/1", | ||
[http2.constants.HTTP2_HEADER_METHOD]: method, | ||
}; | ||
const session = self.#connect(); | ||
req = session | ||
.request(httpRequestHeaders) | ||
.setEncoding("utf8") | ||
.on("error", (error: any) => { | ||
rejectChunk(error); | ||
}) | ||
.on("response", onResponse); | ||
const body = JSON.stringify(requestData); | ||
req.write(body, "utf8"); | ||
req.end(); | ||
while (true) { | ||
const chunks = await chunkPromise; | ||
for (const chunk of chunks) { | ||
yield chunk; | ||
} | ||
} | ||
} | ||
return { | ||
read: reader(), | ||
close: () => { | ||
if (req) { | ||
req.close(); | ||
} | ||
}, | ||
}; | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
export { Client } from "./client"; | ||
export { Client, StreamClient } from "./client"; | ||
export { | ||
@@ -6,2 +6,3 @@ endpoints, | ||
type Endpoints, | ||
type StreamClientConfiguration, | ||
} from "./client-configuration"; | ||
@@ -46,3 +47,2 @@ export { | ||
DocumentReference, | ||
type DocumentT, | ||
EmbeddedSet, | ||
@@ -55,3 +55,5 @@ Module, | ||
SetIterator, | ||
StreamToken, | ||
TimeStub, | ||
type DocumentT, | ||
} from "./values"; | ||
@@ -62,2 +64,3 @@ export { | ||
isHTTPResponse, | ||
isStreamClient, | ||
NodeHTTP2Client, | ||
@@ -67,2 +70,4 @@ type HTTPClient, | ||
type HTTPResponse, | ||
type HTTPStreamClient, | ||
type StreamAdapter, | ||
} from "./http-client"; |
@@ -13,2 +13,3 @@ import { ClientError } from "./errors"; | ||
EmbeddedSet, | ||
StreamToken, | ||
} from "./values"; | ||
@@ -99,2 +100,4 @@ import { QueryValueObject, QueryValue } from "./wire-protocol"; | ||
return value["@object"]; | ||
} else if (value["@stream"]) { | ||
return new StreamToken(value["@stream"]); | ||
} | ||
@@ -214,2 +217,5 @@ | ||
}, | ||
// TODO: encode as a tagged value if provided as a query arg? | ||
// streamToken: (value: StreamToken): TaggedStreamToken => ({ "@stream": value.token }), | ||
streamToken: (value: StreamToken): string => value.token, | ||
}; | ||
@@ -259,2 +265,4 @@ | ||
return encodeMap["set"](input); | ||
} else if (input instanceof StreamToken) { | ||
return encodeMap["streamToken"](input); | ||
} else { | ||
@@ -261,0 +269,0 @@ return encodeMap["object"](input); |
//THIS FILE IS AUTOGENERATED. DO NOT EDIT. SEE .husky/pre-commit | ||
/** The current package version. */ | ||
export const packageVersion = "1.3.1"; | ||
export const packageVersion = "1.4.0-beta.0"; |
export * from "./date-time"; | ||
export * from "./doc"; | ||
export * from "./set"; | ||
export * from "./stream"; |
@@ -13,2 +13,3 @@ // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
Page, | ||
StreamToken, | ||
TimeStub, | ||
@@ -320,2 +321,26 @@ } from "./values"; | ||
| Page<QueryValue> | ||
| EmbeddedSet; | ||
| EmbeddedSet | ||
| StreamToken; | ||
export type StreamRequest = { | ||
token: string; | ||
start_ts?: number; | ||
}; | ||
export type StreamEventType = "status" | "add" | "remove" | "update" | "error"; | ||
export type StreamEventStatus = { | ||
type: "status"; | ||
txn_ts: number; | ||
stats: QueryStats; | ||
}; | ||
export type StreamEventData<T extends QueryValue> = { | ||
type: "add" | "remove" | "update"; | ||
txn_ts: number; | ||
stats: QueryStats; | ||
data: T; | ||
}; | ||
export type StreamEventError = { type: "error" } & QueryFailure; | ||
export type StreamEvent<T extends QueryValue> = | ||
| StreamEventStatus | ||
| StreamEventData<T> | ||
| StreamEventError; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
607518
45
7228
515
1
1
4