@ziprack/sdk 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- var ZipRackSdk=(function(u){"use strict";class R{constructor(t){this.link=t}activities(){return this.link.launch()?.activity?.activities??[]}activity(t){return this.activities().find(e=>e.id===t)??null}statement(t){return this.link.send("activity:statement",{...t,occurred_at:t.occurred_at??new Date().toISOString()})}answer(t,e,s={}){return this.statement({verb:"answered",activity:{id:t,type:s.type??this.activity(t)?.type},result:{response:e},context:s.context})}progress(t,e){return this.statement({verb:"progressed",activity:{id:t,type:"step"},context:e})}}class E{constructor(t,e){this.link=t,this.promptId=e}async send(t,e){let s="";const r=await this.link.send("ai:chat",{prompt:this.promptId,message:t},{timeoutMs:6e4,onPartial:i=>{const a=typeof i.delta=="string"?i.delta:"";s+=a,e?.(a,s)}});return r.ok?{ok:!0,data:{message_id:String(r.data?.message_id??""),text:r.data?.text??s,usage:r.data?.usage??{input_tokens:0,output_tokens:0}}}:r}reset(){return this.link.send("ai:chat.reset",{prompt:this.promptId})}}class I{constructor(t){this.link=t}prompts(){return this.link.launch()?.ai?.prompts??[]}ask(t,e={}){return this.link.send("ai:ask",{prompt:t,vars:e},{timeoutMs:6e4})}chat(t){return new E(this.link,t)}}class L{constructor(t){this.link=t,this.local={}}current(){const t=this.link.launch()?.attempt;return t?{...t,...this.local}:null}get status(){return this.current()?.status??null}get id(){return this.current()?.id??null}state(){return this.current()?.state??null}segment(){return this.current()?.segment??null}setState(t){return this.local.state=t,this.link.send("attempt:state",{state:t})}setSegment(t){return this.local.segment=t,this.link.send("attempt:segment",{segment:t})}async complete(t){const e=await this.link.send("attempt:complete",{summary:t??null});return e.ok&&(this.local.status="completed"),e}}class O{constructor(t){this.link=t}user(){return this.link.launch()?.auth?.user??null}attribute(t,e=null){const s=this.user();return!s||!(t in s)?e:s[t]}}const j="$room";class T{constructor(t,e){this.link=t,this.name=e,this.listeners=new Map,this.unsubscribe=null}attach(){this.unsubscribe??=this.link.onEvent(t=>{t.channel===this.name&&(this.listeners.get(t.event)?.forEach(e=>e(t.payload,t.from)),this.listeners.get("*")?.forEach(e=>e({event:t.event,payload:t.payload},t.from)))})}listen(t,e){const s=this.listeners.get(t)??new Set;return s.add(e),this.listeners.set(t,s),()=>{s.delete(e)}}publish(t,e={}){return this.link.send("broadcast:publish",{channel:this.name,event:t,payload:e})}async leave(){return this.unsubscribe?.(),this.unsubscribe=null,this.listeners.clear(),this.link.send("broadcast:leave",{channel:this.name})}}class C{constructor(t){this.link=t,this.channels=new Map,this.roomListeners=new Map,this.localRole=null,this.localMembers=null,this.localState=null,this.detach=null,this.state={get:async e=>{const s=await this.link.send("broadcast:state.get",e?{keys:e}:{});return s.ok&&s.data&&(this.localState={...this.localState??{},...s.data.state}),s},set:(e,s)=>(this.localState={...this.localState??this.launch()?.state??{},[e]:s},this.link.send("broadcast:state.set",{key:e,value:s})),snapshot:()=>this.localState??this.launch()?.state??{}}}get room(){return this.launch()?.room??null}get role(){return this.localRole??this.launch()?.role??null}get isFacilitator(){return this.role==="facilitator"}members(){return this.localMembers??this.launch()?.members??[]}async join(t="main"){this.ensureRoomListener();let e=this.channels.get(t);return e||(e=new T(this.link,t),this.channels.set(t,e)),e.attach(),await this.link.send("broadcast:join",{channel:t}),e}async presence(){const t=await this.link.send("broadcast:presence",{});return t.ok&&t.data&&(this.localMembers=t.data.members),t}invite(){return this.link.send("broadcast:invite",{})}claim(){return this.link.send("broadcast:claim",{})}transfer(t){return this.link.send("broadcast:transfer",{to:t})}on(t,e){this.ensureRoomListener();const s=this.roomListeners.get(t)??new Set;return s.add(e),this.roomListeners.set(t,s),()=>{s.delete(e)}}launch(){return this.link.launch()?.broadcast??null}ensureRoomListener(){this.detach??=this.link.onEvent(t=>{if(t.channel!==j)return;const e=t.payload;t.event==="presence"&&Array.isArray(e.members)&&(this.localMembers=e.members),t.event==="state"&&typeof e.key=="string"&&(this.localState={...this.localState??this.launch()?.state??{},[e.key]:e.value}),t.event==="role"&&e.participant_id===this.link.launch()?.actor.id&&(this.localRole=e.role),this.roomListeners.get(t.event)?.forEach(s=>s(e))})}}function w(n){return{ok:!1,error:"undeclared_capability",message:`The manifest does not declare the [${n}] capability.`}}class q{constructor(t,e){this.link=t,this.name=e}create(t){return this.send("db:create",{data:t})}get(t){return this.send("db:get",{id:t})}update(t,e,s={}){return this.send("db:update",{id:t,data:e,merge:s.merge??!0})}delete(t){return this.send("db:delete",{id:t})}list(t={}){return this.send("db:list",t)}count(t={}){return this.send("db:count",t)}send(t,e){return this.link.launch()&&!this.link.declares("db")?Promise.resolve(w("db")):this.link.send(t,{collection:this.name,...e})}}class U{constructor(t){this.link=t}collection(t){return new q(this.link,t)}collections(){return this.link.launch()?.db?.collections??[]}}class P{constructor(t){this.link=t}get(t){return this.link.launch()?.media?.[t]??null}embedUrl(t){return this.get(t)?.embedUrl??null}all(){return this.link.launch()?.media??{}}}class N{constructor(t){this.link=t}uploads(){return this.link.launch()?.storage?.uploads??[]}put(t,e,s={}){const r=e instanceof File?e.name:"blob";return this.link.send("storage:put",{upload:t,file:e,name:r,mime:e.type,bytes:e.size,meta:s},{timeoutMs:12e4})}url(t){return this.link.send("storage:url",{id:t})}list(t){return this.link.send("storage:list",t?{upload:t}:{})}delete(t){return this.link.send("storage:delete",{id:t})}}const y="ziprack",S=1;let _=0;function x(){const n=typeof globalThis.crypto<"u"?globalThis.crypto:void 0;return n&&typeof n.randomUUID=="function"?n.randomUUID():(_+=1,`req-${Date.now().toString(36)}-${_.toString(36)}`)}function M(n,t,e){const s={ns:y,v:S,type:n,payload:t};return e!==void 0&&(s.requestId=e),s}function z(n){if(typeof n!="object"||n===null)return!1;const t=n;return t.ns===y&&t.v===S&&typeof t.type=="string"}function p(n){const t=n.indexOf(":");return t===-1?n:n.slice(0,t)}class H{constructor(t){this.options=t,this.kind="host",this.pending=new Map,this.eventListeners=new Set,this.launchResolvers=[],this.launchData=null,this.listener=e=>this.onMessage(e),this.target=t.target??window.parent,this.isHost=t.isHost??(e=>e===window.parent),this.listenOn=t.listenOn??window,this.listenOn.addEventListener("message",this.listener)}announceReady(){this.fire("rack:ready",{})}waitForLaunch(t){return this.launchData?Promise.resolve(this.launchData):new Promise(e=>{const s=setTimeout(()=>{this.launchResolvers=this.launchResolvers.filter(i=>i!==r),e(null)},t),r=i=>{clearTimeout(s),e(i)};this.launchResolvers.push(r)})}send(t,e,s={}){const r=x(),i=s.timeoutMs??this.options.ackTimeoutMs;return new Promise(a=>{const o={resolve:a,timer:setTimeout(()=>this.expire(r),i),timeoutMs:i,onPartial:s.onPartial};this.pending.set(r,o),this.post(M(t,e,r))})}fire(t,e={}){this.post(M(t,e))}onEvent(t){return this.eventListeners.add(t),()=>{this.eventListeners.delete(t)}}dispose(){this.listenOn.removeEventListener("message",this.listener);for(const[t,e]of this.pending)clearTimeout(e.timer),e.resolve({ok:!1,error:"disposed"}),this.pending.delete(t)}expire(t){const e=this.pending.get(t);e&&(this.pending.delete(t),e.resolve({ok:!1,error:"timeout"}))}post(t){this.target.postMessage(t,"*")}onMessage(t){if(!this.isHost(t.source)||!z(t.data))return;const e=t.data;if(e.type==="rack:launch"){this.launchData=e.payload;const s=this.launchResolvers;this.launchResolvers=[],s.forEach(r=>r(this.launchData));return}if(e.type==="rack:event"){const s=e.payload;this.eventListeners.forEach(r=>r(s));return}e.type==="rack:ack"&&this.onAck(e.payload)}onAck(t){const e=this.pending.get(t.requestId);if(!e)return;if(!t.done&&t.data?.stream===!0){clearTimeout(e.timer),e.timer=setTimeout(()=>this.expire(t.requestId),e.timeoutMs);return}if(t.partial&&!t.done){const{requestId:b,ok:v,partial:m,...h}=t;e.onPartial?.(h),clearTimeout(e.timer),e.timer=setTimeout(()=>this.expire(t.requestId),e.timeoutMs);return}clearTimeout(e.timer),this.pending.delete(t.requestId);const{requestId:s,ok:r,data:i,error:a,message:o,details:c,partial:l,done:D,...d}=t;e.resolve({ok:r,data:i??(Object.keys(d).length?d:void 0),error:a,message:o,details:c})}}class F{constructor(t,e,s={}){this.manifest=t,this.store=e,this.options=s,this.statements=[],this.events=[],this.aiHandlers=new Map,this.chatHistory=new Map,this.blobs=new Map,this.eventListeners=new Set,this.roomMembers=new Map,this.roomState={},this.role="participant",this.joinedAt=Date.now()+Math.random(),this.channel=null,this.heartbeat=null,this.participantId=s.participantId??Math.floor(Math.random()*1e6)+1,this.participantName=s.participantName??"Dev User"}get slug(){return this.manifest?.slug??"applet"}key(...t){return`ziprack-mock:${this.slug}:${t.join(":")}`}declares(t){return this.manifest?(this.manifest.requires?.capabilities??[]).some(e=>e.split("@")[0]===t):!0}onEvent(t){return this.eventListeners.add(t),()=>{this.eventListeners.delete(t)}}pushEvent(t){this.events.push(t),this.eventListeners.forEach(e=>e(t))}launchData(t){const e=this.manifest,s={schema:"ziprack/1",actor:{id:this.participantId,name:this.participantName},applet:{slug:this.slug,name:`${e?.name??t??"Applet"} (mock)`,version:e?.version??"dev"},capabilities:e?.requires?.capabilities??[],media:Object.fromEntries((e?.media??[]).map(r=>[r.id,{provider:r.provider,embedUrl:r.provider==="vimeo"?`https://player.vimeo.com/video/${r.key}?dnt=1`:`https://www.youtube-nocookie.com/embed/${r.key}?rel=0`,meta:null}]))};if(this.declares("auth")){const r={user:{id:this.participantId,display_name:this.participantName}};for(const i of e?.requires?.profile??[])r.user[i]=r.user[i]??`mock-${i}`;s.auth=r}return this.declares("attempt")&&(s.attempt=this.attempt()),this.declares("activity")&&(s.activity={activities:[...e?.activities??[]].sort((r,i)=>(r.sequence??0)-(i.sequence??0))}),this.declares("db")&&(s.db={collections:(e?.collections??[]).map(r=>({name:r.name,scope:r.scope}))}),this.declares("storage")&&(s.storage={uploads:(e?.uploads??[]).map(r=>({name:r.name,scope:r.scope,mime:r.mime??[],max_bytes:r.max_bytes??10485760}))}),this.declares("ai")&&(s.ai={prompts:(e?.prompts??[]).map(r=>({id:r.id,mode:r.mode}))}),this.declares("broadcast")&&(this.joinRoom(),s.broadcast={room:{id:1,code:"MOCK01",name:`${this.slug} room`,status:"open",mode:e?.broadcast?.mock_mode??"first_in"},role:this.role,members:this.members(),state:this.stateSnapshot(),channels:e?.broadcast?.channels??["main"]}),s}mockAi(t,e){this.aiHandlers.set(t,e)}dispose(){this.heartbeat&&clearInterval(this.heartbeat),this.channel&&(this.send({t:"bye",from:this.member()}),this.channel.close())}async handle(t,e){if(this.options.log?.(t,e),t==="rack:eject")return{ok:!0};const s=p(t);if((this.options.strict??this.manifest!==null)&&!this.declares(s))return{ok:!1,error:"undeclared_capability",message:`The manifest does not declare the [${s}] capability.`};switch(t){case"attempt:state":return this.attemptState(e);case"attempt:segment":return this.attemptSegment(e);case"attempt:complete":return this.attemptComplete(e);case"activity:statement":return this.statement(e);case"db:create":return this.dbCreate(e);case"db:get":return this.dbGet(e);case"db:update":return this.dbUpdate(e);case"db:delete":return this.dbDelete(e);case"db:list":return this.dbList(e);case"db:count":return this.dbCount(e);case"storage:put":return this.storagePut(e);case"storage:url":return this.storageUrl(e);case"storage:list":return this.storageList(e);case"storage:delete":return this.storageDelete(e);case"ai:ask":return this.aiAsk(e);case"ai:chat":return this.aiChat(e);case"ai:chat.reset":return this.chatHistory.delete(String(e.prompt)),{ok:!0};case"broadcast:join":return{ok:!0,data:{members:this.members(),state:this.stateSnapshot()}};case"broadcast:leave":return{ok:!0};case"broadcast:publish":return this.publish(e);case"broadcast:state.get":return{ok:!0,data:{state:this.stateSnapshot(Array.isArray(e.keys)?e.keys:void 0)}};case"broadcast:state.set":return this.stateSet(e);case"broadcast:presence":return{ok:!0,data:{members:this.members()}};case"broadcast:invite":return this.invite();case"broadcast:claim":return this.claim();case"broadcast:transfer":return this.transfer(e);default:return{ok:!1,error:"unknown_type",message:`Unknown message type [${t}].`}}}attempt(){return this.store.get(this.key("attempt"))??{id:1,status:"in_progress",state:null,segment:null}}saveAttempt(t){this.store.set(this.key("attempt"),t)}attemptState(t){const e=this.attempt();if(e.status==="completed")return{ok:!1,error:"completed",message:"This attempt is already completed."};const s=JSON.stringify(t.state??{}).length;return s>65536?{ok:!1,error:"validation",message:`State is ${s} bytes; the limit is 65536.`}:(this.saveAttempt({...e,state:t.state??null}),{ok:!0})}attemptSegment(t){const e=this.attempt();return e.status==="completed"?{ok:!1,error:"completed"}:(this.saveAttempt({...e,segment:t.segment??null}),{ok:!0})}attemptComplete(t){const e=this.attempt(),s=new Date().toISOString();return e.status!=="completed"&&(this.saveAttempt({...e,status:"completed"}),this.declares("activity")&&this.statement({verb:"completed",activity:{id:"attempt",type:"attempt"},result:t.summary??null,occurred_at:s})),{ok:!0,data:{status:"completed",completed_at:s}}}resetAttempt(){this.store.remove(this.key("attempt"))}statement(t){const e=String(t.verb??"");if(!/^[a-z][a-z0-9_.:-]*$/.test(e)||e.length>100)return{ok:!1,error:"validation",message:`Invalid verb [${e}].`};if(!t.activity?.id)return{ok:!1,error:"validation",message:"activity.id is required."};const r=`mock-${Date.now().toString(36)}-${this.statements.length+1}`;return this.statements.push({id:r,...t}),{ok:!0,data:{id:r}}}collectionKey(t){return this.key("db",t)}records(t){return this.store.get(this.collectionKey(t))??[]}saveRecords(t,e){this.store.set(this.collectionKey(t),e)}collectionDeclared(t){return this.manifest&&!(this.manifest.collections??[]).some(s=>s.name===t)?{ok:!1,error:"not_found",message:`Collection [${t}] is not declared in the manifest.`}:(this.manifest?.collections??[]).find(s=>s.name===t)?.scope==="attempt"&&this.attempt().status==="completed"?{ok:!1,error:"completed",message:"Attempt-scoped records are frozen after completion."}:null}dbCreate(t){const e=String(t.collection),s=this.collectionDeclared(e);if(s)return s;const r=new Date().toISOString(),i={id:`rec-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,7)}`,data:t.data??{},owner_id:this.participantId,created_at:r,updated_at:r};return this.saveRecords(e,[...this.records(e),i]),{ok:!0,data:{record:i}}}dbGet(t){const e=this.records(String(t.collection)).find(s=>s.id===t.id);return e?{ok:!0,data:{record:e}}:{ok:!1,error:"not_found"}}dbUpdate(t){const e=String(t.collection),s=this.collectionDeclared(e);if(s&&s.error==="completed")return s;const r=this.records(e),i=r.findIndex(c=>c.id===t.id);if(i===-1)return{ok:!1,error:"not_found"};const a=t.merge!==!1,o=t.data??{};return r[i]={...r[i],data:a?{...r[i].data,...o}:o,updated_at:new Date().toISOString()},this.saveRecords(e,r),{ok:!0,data:{record:r[i]}}}dbDelete(t){const e=String(t.collection),s=this.records(e);return s.some(r=>r.id===t.id)?(this.saveRecords(e,s.filter(r=>r.id!==t.id)),{ok:!0,data:{id:t.id}}):{ok:!1,error:"not_found"}}filter(t,e){return e?t.filter(s=>e.every(([r,i,a])=>{const o=r.split(".").reduce((c,l)=>c?.[l],s.data);switch(i){case"=":return o===a;case"!=":return o!==a;case"<":return o<a;case"<=":return o<=a;case">":return o>a;case">=":return o>=a;case"in":return Array.isArray(a)&&a.includes(o);default:return!1}})):t}dbList(t){const e=String(t.collection);if(this.manifest&&!(this.manifest.collections??[]).some(c=>c.name===e))return{ok:!1,error:"not_found",message:`Collection [${e}] is not declared in the manifest.`};let s=this.filter(this.records(e),t.where);if(typeof t.orderBy=="string"){const c=t.orderBy,l=t.direction==="desc"?-1:1;s=[...s].sort((D,d)=>{const b=c.split(".").reduce((m,h)=>m?.[h],D.data),v=c.split(".").reduce((m,h)=>m?.[h],d.data);return b<v?-l:b>v?l:0})}const r=Math.min(Number(t.limit??50),100),i=t.cursor?Number(t.cursor):0,a=s.slice(i,i+r),o=i+r<s.length?String(i+r):null;return{ok:!0,data:{records:a,nextCursor:o}}}dbCount(t){return{ok:!0,data:{count:this.filter(this.records(String(t.collection)),t.where).length}}}files(){return this.store.get(this.key("files"))??[]}storagePut(t){const e=String(t.upload),s=(this.manifest?.uploads??[]).find(c=>c.name===e);if(this.manifest&&!s)return{ok:!1,error:"not_found",message:`Upload [${e}] is not declared in the manifest.`};const r=String(t.mime??"");if(s?.mime&&s.mime.length>0&&!s.mime.some(c=>c===r||c.endsWith("/*")&&r.startsWith(c.slice(0,-1))))return{ok:!1,error:"validation",message:`MIME type [${r}] is not allowed for upload [${e}].`};const i=Number(t.bytes??0);if(s?.max_bytes&&i>s.max_bytes)return{ok:!1,error:"quota_exceeded",message:`File is ${i} bytes; the limit is ${s.max_bytes}.`};const a={id:`file-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,7)}`,upload:e,name:String(t.name??"blob"),mime:r,bytes:i,status:"clean",created_at:new Date().toISOString()},o=t.file;return typeof URL<"u"&&typeof URL.createObjectURL=="function"&&o instanceof Blob&&this.blobs.set(a.id,URL.createObjectURL(o)),this.store.set(this.key("files"),[...this.files(),a]),{ok:!0,data:{file:a}}}storageUrl(t){const e=this.files().find(s=>s.id===t.id);return e?{ok:!0,data:{url:this.blobs.get(e.id)??`mock://files/${e.id}`,expires_at:new Date(Date.now()+6e5).toISOString()}}:{ok:!1,error:"not_found"}}storageList(t){return{ok:!0,data:{files:this.files().filter(s=>!t.upload||s.upload===t.upload)}}}storageDelete(t){return this.store.set(this.key("files"),this.files().filter(e=>e.id!==t.id)),{ok:!0,data:{id:t.id}}}prompt(t){return(this.manifest?.prompts??[]).find(e=>e.id===t)??(this.manifest?null:{id:t,mode:"analysis"})}async aiAsk(t){const e=this.prompt(String(t.prompt));if(!e)return{ok:!1,error:"not_found",message:`Prompt [${t.prompt}] is not declared in the manifest.`};const s=this.aiHandlers.get(e.id),r=t.vars??{};return{ok:!0,data:{output:s?await s(r,"analysis"):f(e.output_schema,r),usage:{input_tokens:42,output_tokens:17}}}}async aiChat(t){const e=this.prompt(String(t.prompt));if(!e)return{ok:!1,error:"not_found",message:`Prompt [${t.prompt}] is not declared in the manifest.`};const s=String(t.message??""),r=this.chatHistory.get(e.id)??[];r.push({role:"user",content:s});const i=this.aiHandlers.get(e.id),a=i?String(await i({history:r},"chat",s)):`[mock ${e.id}] You said: ${s}`;r.push({role:"assistant",content:a}),this.chatHistory.set(e.id,r);const o=a.split(" "),c=`msg-${Date.now().toString(36)}`;return{stream:(async function*(){for(let l=0;l<o.length;l++)yield{delta:(l===0?"":" ")+o[l]}})(),final:()=>({message_id:c,text:a,usage:{input_tokens:s.length,output_tokens:a.length}})}}member(){return{id:this.participantId,name:this.participantName,role:this.role,present:!0,joinedAt:this.joinedAt}}members(){const t=Date.now(),e=[...this.roomMembers.values()].filter(i=>t-i.lastSeen<15e3).map(({lastSeen:i,joinedAt:a,...o})=>o),{joinedAt:s,...r}=this.member();return[r,...e].sort((i,a)=>i.id-a.id)}stateSnapshot(t){const e={};for(const[s,r]of Object.entries(this.roomState))(!t||t.includes(s))&&(e[s]=r.value);return e}send(t){this.channel?.postMessage({...t,sender:this.participantId})}joinRoom(){if(this.channel)return;const t=this.manifest?.broadcast?.mock_mode??"first_in";this.role=this.options.role??(t==="first_in"?"facilitator":"participant"),!(typeof BroadcastChannel>"u")&&(this.channel=new BroadcastChannel(this.key("room")),this.channel.onmessage=e=>this.onRoomMessage(e.data),this.send({t:"hello",from:this.member()}),this.heartbeat=setInterval(()=>this.send({t:"here",from:this.member()}),5e3))}reconcileFacilitator(t){t.role==="facilitator"&&this.role==="facilitator"&&(t.joinedAt??0)<this.joinedAt&&(this.role="participant",this.pushEvent({channel:"$room",event:"role",payload:{role:"participant",participant_id:this.participantId}}))}onRoomMessage(t){const e=t.from;if(t.sender!==this.participantId)switch(t.t){case"hello":e&&(this.roomMembers.set(e.id,{...e,lastSeen:Date.now()}),this.reconcileFacilitator(e),this.send({t:"here",from:this.member()}),this.send({t:"state-sync",state:this.roomState}),this.pushEvent({channel:"$room",event:"presence",payload:{members:this.members()}}));break;case"here":if(e){const s=this.roomMembers.has(e.id);this.roomMembers.set(e.id,{...e,lastSeen:Date.now()}),this.reconcileFacilitator(e),s||this.pushEvent({channel:"$room",event:"presence",payload:{members:this.members()}})}break;case"bye":e&&(this.roomMembers.delete(e.id),this.pushEvent({channel:"$room",event:"presence",payload:{members:this.members()}}));break;case"event":this.pushEvent({channel:String(t.channel),event:String(t.event),payload:t.payload,from:{participant_id:Number(t.sender),role:e?.role??"participant"}});break;case"state":{const s=String(t.key),r=Number(t.version);(this.roomState[s]?.version??0)<=r&&(this.roomState[s]={value:t.value,version:r},this.pushEvent({channel:"$room",event:"state",payload:{key:s,value:t.value,version:r}}));break}case"state-sync":for(const[s,r]of Object.entries(t.state))(this.roomState[s]?.version??0)<r.version&&(this.roomState[s]=r);break;case"role":e&&(this.roomMembers.set(e.id,{...e,lastSeen:Date.now()}),e.role==="facilitator"&&this.role==="facilitator"&&(this.role="participant",this.pushEvent({channel:"$room",event:"role",payload:{role:"participant",participant_id:this.participantId}})),this.pushEvent({channel:"$room",event:"role",payload:{role:e.role,participant_id:e.id}}),this.pushEvent({channel:"$room",event:"presence",payload:{members:this.members()}}));break}}invite(){return{ok:!0,data:{code:"MOCK01",url:typeof location>"u"?"https://rack.test/r/MOCK01":location.origin+location.pathname}}}setRole(t){this.role=t,this.send({t:"role",from:this.member()}),this.pushEvent({channel:"$room",event:"role",payload:{role:t,participant_id:this.participantId}})}publish(t){const e=String(t.event);if((this.manifest?.broadcast?.facilitator_events??[]).includes(e)&&this.role!=="facilitator")return{ok:!1,error:"forbidden",message:`Event [${e}] may only be sent by the facilitator.`};const r={participant_id:this.participantId,role:this.role};return this.send({t:"event",channel:t.channel,event:e,payload:t.payload,from:this.member()}),this.pushEvent({channel:String(t.channel??"main"),event:e,payload:t.payload,from:r}),{ok:!0}}stateSet(t){const e=String(t.key),s=(this.roomState[e]?.version??0)+1;return this.roomState[e]={value:t.value,version:s},this.send({t:"state",key:e,value:t.value,version:s}),this.pushEvent({channel:"$room",event:"state",payload:{key:e,value:t.value,version:s}}),{ok:!0,data:{version:s}}}claim(){return this.setRole("facilitator"),{ok:!0,data:{role:"facilitator"}}}transfer(t){if(this.role!=="facilitator")return{ok:!1,error:"forbidden",message:"Only the facilitator can transfer the role."};const e=this.roomMembers.get(Number(t.to));return e?(this.role="participant",e.role="facilitator",this.send({t:"role",from:{...e}}),this.pushEvent({channel:"$room",event:"role",payload:{role:"participant",participant_id:this.participantId}}),this.pushEvent({channel:"$room",event:"role",payload:{role:"facilitator",participant_id:e.id}}),{ok:!0}):{ok:!1,error:"not_found"}}}function f(n,t){if(!n)return{summary:`Mock analysis of ${JSON.stringify(t).slice(0,60)}`};const e=n.type;if(e==="object"){const s={};for(const[r,i]of Object.entries(n.properties??{}))s[r]=f(i,t);return s}return e==="array"?[f(n.items,t)]:e==="number"||e==="integer"?typeof n.minimum=="number"?n.minimum:3:e==="boolean"?!0:Array.isArray(n.enum)?n.enum[0]:"mock"}class k{constructor(t){this.rack=t,this.kind="mock"}async send(t,e,s={}){const r=await this.rack.handle(t,e??{});if("stream"in r){for await(const i of r.stream)s.onPartial?.(i);return{ok:!0,data:r.final()}}return r}fire(t,e={}){this.rack.handle(t,e??{})}onEvent(t){return this.rack.onEvent(t)}dispose(){this.rack.dispose()}}class B{get(t){try{const e=window.localStorage.getItem(t);return e===null?null:JSON.parse(e)}catch{return null}}set(t,e){try{window.localStorage.setItem(t,JSON.stringify(e))}catch{}}remove(t){try{window.localStorage.removeItem(t)}catch{}}keys(t){try{const e=[];for(let s=0;s<window.localStorage.length;s++){const r=window.localStorage.key(s);r&&r.startsWith(t)&&e.push(r)}return e}catch{return[]}}}const K=3e3,J=5e3;class A{constructor(){this.transport=null,this.initPromise=null,this.launchData=null,this.queue=[],this.launchListeners=new Set,this.eventListeners=new Set,this.pendingAiMocks=[];const t={send:(e,s,r)=>this.send(e,s,r),fire:(e,s)=>this.fire(e,s),launch:()=>this.launchData,declares:e=>this.declares(e),onEvent:e=>this.on("event",e),mode:()=>this.mode};this.auth=new O(t),this.attempt=new L(t),this.activity=new R(t),this.db=new U(t),this.storage=new N(t),this.ai=new I(t),this.broadcast=new C(t),this.media=new P(t),this.mock={ai:(e,s)=>{const r=this.mockRack();r?r.mockAi(e,s):this.pendingAiMocks.push([e,s])},resetAttempt:()=>this.mockRack()?.resetAttempt(),rack:()=>this.mockRack()}}init(t={}){return this.initPromise??=this.performInit(t),this.initPromise}get launched(){return this.launchData!==null}get mode(){return this.transport?.kind??"pending"}getLaunchData(){return this.launchData}declares(t){return(this.launchData?.capabilities??[]).some(e=>e.split("@")[0]===t)}on(t,e){if(t==="launch"){const r=e;return this.launchListeners.add(r),this.launchData&&r(this.launchData),()=>{this.launchListeners.delete(r)}}const s=e;return this.eventListeners.add(s),()=>{this.eventListeners.delete(s)}}eject(){this.fire("rack:eject",{}),this.transport?.kind==="mock"&&console.info("[ziprack mock] eject: the Rack would navigate away now.")}send(t,e,s){return this.launchData&&!t.startsWith("rack:")&&!this.declares(p(t))?Promise.resolve(w(p(t))):this.transport?this.transport.send(t,e,s):new Promise(r=>{this.queue.push({type:t,payload:e,options:s,resolve:r})})}fire(t,e={}){if(this.transport){this.transport.fire(t,e);return}this.send(t,e)}dispose(){this.transport?.dispose(),this.transport=null,this.initPromise=null,this.launchData=null}async performInit(t){const e=t.ackTimeoutMs??J,s=t.launchTimeoutMs??K;if(t.transport){const o=t.launch??(t.transport instanceof k?t.transport.rack.launchData():null);if(!o)throw new Error("init({ transport }) needs `launch` data unless the transport is a MockTransport.");return this.activate(t.transport,o)}if(!(typeof window<"u"&&window.parent!==window))return this.activateMock(t,"not embedded in a Rack");const i=new H({ackTimeoutMs:e});i.announceReady();const a=await i.waitForLaunch(s);return a?this.activate(i,a):(i.dispose(),this.activateMock(t,`no rack:launch within ${s}ms`))}async activateMock(t,e){console.info(`[ziprack] running in mock mode (${e})`);const s=await W(t.manifestUrl??"./manifest.json",t.slug),r=new F(s,new B,{log:(i,a)=>console.info(`[ziprack mock] ${i}`,a)});for(const[i,a]of this.pendingAiMocks)r.mockAi(i,a);return this.pendingAiMocks=[],this.activate(new k(r),r.launchData(t.slug))}activate(t,e){return this.transport=t,this.launchData=e,t.onEvent(s=>this.eventListeners.forEach(r=>r(s))),this.launchListeners.forEach(s=>s(e)),this.flushQueue(),e}flushQueue(){if(this.transport)for(;this.queue.length>0;){const e=this.queue.shift();this.send(e.type,e.payload,e.options).then(e.resolve)}}mockRack(){return this.transport instanceof k?this.transport.rack:null}}async function W(n,t){if(typeof fetch!="function")return null;try{const e=await fetch(n,{cache:"no-store"});if(!e.ok)throw new Error(String(e.status));const s=await e.json();return t&&s.slug!==t&&console.warn(`[ziprack mock] manifest slug [${s.slug}] does not match init({ slug: '${t}' }).`),s}catch(e){return console.warn(`[ziprack mock] could not load ${n}; mock mode will accept every capability.`,e),null}}const g=new A;function $(){return new A}return window.Rack=g,window.ZipRack={Rack:g,createRack:$},u.Rack=g,u.createRack=$,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"}),u})({});
1
+ var ZipRackSdk=(function(u){"use strict";class D{constructor(t){this.link=t}activities(){return this.link.launch()?.activity?.activities??[]}activity(t){return this.activities().find(e=>e.id===t)??null}statement(t){return this.link.send("activity:statement",{...t,occurred_at:t.occurred_at??new Date().toISOString()})}answer(t,e,s={}){return this.statement({verb:"answered",activity:{id:t,type:s.type??this.activity(t)?.type},result:{response:e},context:s.context})}progress(t,e){return this.statement({verb:"progressed",activity:{id:t,type:"step"},context:e})}}class E{constructor(t,e){this.link=t,this.promptId=e}async send(t,e){let s="";const r=await this.link.send("ai:chat",{prompt:this.promptId,message:t},{timeoutMs:6e4,onPartial:i=>{const a=typeof i.delta=="string"?i.delta:"";s+=a,e?.(a,s)}});return r.ok?{ok:!0,data:{message_id:String(r.data?.message_id??""),text:r.data?.text??s,usage:r.data?.usage??{input_tokens:0,output_tokens:0}}}:r}reset(){return this.link.send("ai:chat.reset",{prompt:this.promptId})}}class I{constructor(t){this.link=t}prompts(){return this.link.launch()?.ai?.prompts??[]}ask(t,e={}){return this.link.send("ai:ask",{prompt:t,vars:e},{timeoutMs:6e4})}chat(t){return new E(this.link,t)}}class L{constructor(t){this.link=t,this.local={}}current(){const t=this.link.launch()?.attempt;return t?{...t,...this.local}:null}get status(){return this.current()?.status??null}get id(){return this.current()?.id??null}state(){return this.current()?.state??null}segment(){return this.current()?.segment??null}setState(t){return this.local.state=t,this.link.send("attempt:state",{state:t})}setSegment(t){return this.local.segment=t,this.link.send("attempt:segment",{segment:t})}async complete(t){const e=await this.link.send("attempt:complete",{summary:t??null});return e.ok&&(this.local.status="completed"),e}}class O{constructor(t){this.link=t}user(){return this.link.launch()?.auth?.user??null}attribute(t,e=null){const s=this.user();return!s||!(t in s)?e:s[t]}roles(){return this.link.launch()?.roles??[]}hasRole(t){return this.roles().includes(t)}isGuest(){return this.hasRole("guest")}}const j="$room";class T{constructor(t,e){this.link=t,this.name=e,this.listeners=new Map,this.unsubscribe=null}attach(){this.unsubscribe??=this.link.onEvent(t=>{t.channel===this.name&&(this.listeners.get(t.event)?.forEach(e=>e(t.payload,t.from)),this.listeners.get("*")?.forEach(e=>e({event:t.event,payload:t.payload},t.from)))})}listen(t,e){const s=this.listeners.get(t)??new Set;return s.add(e),this.listeners.set(t,s),()=>{s.delete(e)}}publish(t,e={}){return this.link.send("broadcast:publish",{channel:this.name,event:t,payload:e})}async leave(){return this.unsubscribe?.(),this.unsubscribe=null,this.listeners.clear(),this.link.send("broadcast:leave",{channel:this.name})}}class C{constructor(t){this.link=t,this.channels=new Map,this.roomListeners=new Map,this.localRole=null,this.localMembers=null,this.localState=null,this.detach=null,this.state={get:async e=>{const s=await this.link.send("broadcast:state.get",e?{keys:e}:{});return s.ok&&s.data&&(this.localState={...this.localState??{},...s.data.state}),s},set:(e,s)=>(this.localState={...this.localState??this.launch()?.state??{},[e]:s},this.link.send("broadcast:state.set",{key:e,value:s})),snapshot:()=>this.localState??this.launch()?.state??{}}}get room(){return this.launch()?.room??null}get role(){return this.localRole??this.launch()?.role??null}get isFacilitator(){return this.role==="facilitator"}members(){return this.localMembers??this.launch()?.members??[]}async join(t="main"){this.ensureRoomListener();let e=this.channels.get(t);return e||(e=new T(this.link,t),this.channels.set(t,e)),e.attach(),await this.link.send("broadcast:join",{channel:t}),e}async presence(){const t=await this.link.send("broadcast:presence",{});return t.ok&&t.data&&(this.localMembers=t.data.members),t}invite(){return this.link.send("broadcast:invite",{})}claim(){return this.link.send("broadcast:claim",{})}transfer(t){return this.link.send("broadcast:transfer",{to:t})}on(t,e){this.ensureRoomListener();const s=this.roomListeners.get(t)??new Set;return s.add(e),this.roomListeners.set(t,s),()=>{s.delete(e)}}launch(){return this.link.launch()?.broadcast??null}ensureRoomListener(){this.detach??=this.link.onEvent(t=>{if(t.channel!==j)return;const e=t.payload;t.event==="presence"&&Array.isArray(e.members)&&(this.localMembers=e.members),t.event==="state"&&typeof e.key=="string"&&(this.localState={...this.localState??this.launch()?.state??{},[e.key]:e.value}),t.event==="role"&&e.participant_id===this.link.launch()?.actor.id&&(this.localRole=e.role),this.roomListeners.get(t.event)?.forEach(s=>s(e))})}}function w(n){return{ok:!1,error:"undeclared_capability",message:`The manifest does not declare the [${n}] capability.`}}class U{constructor(t,e){this.link=t,this.name=e}create(t){return this.send("db:create",{data:t})}get(t){return this.send("db:get",{id:t})}update(t,e,s={}){return this.send("db:update",{id:t,data:e,merge:s.merge??!0})}delete(t){return this.send("db:delete",{id:t})}list(t={}){return this.send("db:list",t)}count(t={}){return this.send("db:count",t)}send(t,e){return this.link.launch()&&!this.link.declares("db")?Promise.resolve(w("db")):this.link.send(t,{collection:this.name,...e})}}class q{constructor(t){this.link=t}collection(t){return new U(this.link,t)}collections(){return this.link.launch()?.db?.collections??[]}}class P{constructor(t){this.link=t}get(t){return this.link.launch()?.media?.[t]??null}embedUrl(t){return this.get(t)?.embedUrl??null}all(){return this.link.launch()?.media??{}}}class N{constructor(t){this.link=t}uploads(){return this.link.launch()?.storage?.uploads??[]}put(t,e,s={}){const r=e instanceof File?e.name:"blob";return this.link.send("storage:put",{upload:t,file:e,name:r,mime:e.type,bytes:e.size,meta:s},{timeoutMs:12e4})}url(t){return this.link.send("storage:url",{id:t})}list(t){return this.link.send("storage:list",t?{upload:t}:{})}delete(t){return this.link.send("storage:delete",{id:t})}}const y="ziprack",S=1;let _=0;function x(){const n=typeof globalThis.crypto<"u"?globalThis.crypto:void 0;return n&&typeof n.randomUUID=="function"?n.randomUUID():(_+=1,`req-${Date.now().toString(36)}-${_.toString(36)}`)}function M(n,t,e){const s={ns:y,v:S,type:n,payload:t};return e!==void 0&&(s.requestId=e),s}function z(n){if(typeof n!="object"||n===null)return!1;const t=n;return t.ns===y&&t.v===S&&typeof t.type=="string"}function p(n){const t=n.indexOf(":");return t===-1?n:n.slice(0,t)}class H{constructor(t){this.options=t,this.kind="host",this.pending=new Map,this.eventListeners=new Set,this.launchResolvers=[],this.launchData=null,this.listener=e=>this.onMessage(e),this.target=t.target??window.parent,this.isHost=t.isHost??(e=>e===window.parent),this.listenOn=t.listenOn??window,this.listenOn.addEventListener("message",this.listener)}announceReady(){this.fire("rack:ready",{})}waitForLaunch(t){return this.launchData?Promise.resolve(this.launchData):new Promise(e=>{const s=setTimeout(()=>{this.launchResolvers=this.launchResolvers.filter(i=>i!==r),e(null)},t),r=i=>{clearTimeout(s),e(i)};this.launchResolvers.push(r)})}send(t,e,s={}){const r=x(),i=s.timeoutMs??this.options.ackTimeoutMs;return new Promise(a=>{const o={resolve:a,timer:setTimeout(()=>this.expire(r),i),timeoutMs:i,onPartial:s.onPartial};this.pending.set(r,o),this.post(M(t,e,r))})}fire(t,e={}){this.post(M(t,e))}onEvent(t){return this.eventListeners.add(t),()=>{this.eventListeners.delete(t)}}dispose(){this.listenOn.removeEventListener("message",this.listener);for(const[t,e]of this.pending)clearTimeout(e.timer),e.resolve({ok:!1,error:"disposed"}),this.pending.delete(t)}expire(t){const e=this.pending.get(t);e&&(this.pending.delete(t),e.resolve({ok:!1,error:"timeout"}))}post(t){this.target.postMessage(t,"*")}onMessage(t){if(!this.isHost(t.source)||!z(t.data))return;const e=t.data;if(e.type==="rack:launch"){this.launchData=e.payload;const s=this.launchResolvers;this.launchResolvers=[],s.forEach(r=>r(this.launchData));return}if(e.type==="rack:event"){const s=e.payload;this.eventListeners.forEach(r=>r(s));return}e.type==="rack:ack"&&this.onAck(e.payload)}onAck(t){const e=this.pending.get(t.requestId);if(!e)return;if(!t.done&&t.data?.stream===!0){clearTimeout(e.timer),e.timer=setTimeout(()=>this.expire(t.requestId),e.timeoutMs);return}if(t.partial&&!t.done){const{requestId:b,ok:v,partial:m,...h}=t;e.onPartial?.(h),clearTimeout(e.timer),e.timer=setTimeout(()=>this.expire(t.requestId),e.timeoutMs);return}clearTimeout(e.timer),this.pending.delete(t.requestId);const{requestId:s,ok:r,data:i,error:a,message:o,details:c,partial:l,done:R,...d}=t;e.resolve({ok:r,data:i??(Object.keys(d).length?d:void 0),error:a,message:o,details:c})}}class F{constructor(t,e,s={}){this.manifest=t,this.store=e,this.options=s,this.statements=[],this.events=[],this.aiHandlers=new Map,this.chatHistory=new Map,this.blobs=new Map,this.eventListeners=new Set,this.roomMembers=new Map,this.roomState={},this.role="participant",this.joinedAt=Date.now()+Math.random(),this.channel=null,this.heartbeat=null,this.participantId=s.participantId??Math.floor(Math.random()*1e6)+1,this.participantName=s.participantName??"Dev User"}get slug(){return this.manifest?.slug??"applet"}key(...t){return`ziprack-mock:${this.slug}:${t.join(":")}`}declares(t){return this.manifest?(this.manifest.requires?.capabilities??[]).some(e=>e.split("@")[0]===t):!0}onEvent(t){return this.eventListeners.add(t),()=>{this.eventListeners.delete(t)}}pushEvent(t){this.events.push(t),this.eventListeners.forEach(e=>e(t))}launchData(t){const e=this.manifest,s={schema:"ziprack/1",actor:{id:this.participantId,name:this.participantName},applet:{slug:this.slug,name:`${e?.name??t??"Applet"} (mock)`,version:e?.version??"dev"},capabilities:e?.requires?.capabilities??[],media:Object.fromEntries((e?.media??[]).map(r=>[r.id,{provider:r.provider,embedUrl:r.provider==="vimeo"?`https://player.vimeo.com/video/${r.key}?dnt=1`:`https://www.youtube-nocookie.com/embed/${r.key}?rel=0`,meta:null}])),roles:this.roles()};if(this.declares("auth")){const r={user:{id:this.participantId,display_name:this.participantName}};for(const i of e?.requires?.profile??[])r.user[i]=r.user[i]??`mock-${i}`;s.auth=r}return this.declares("attempt")&&(s.attempt=this.attempt()),this.declares("activity")&&(s.activity={activities:[...e?.activities??[]].sort((r,i)=>(r.sequence??0)-(i.sequence??0))}),this.declares("db")&&(s.db={collections:(e?.collections??[]).map(r=>({name:r.name,scope:r.scope}))}),this.declares("storage")&&(s.storage={uploads:(e?.uploads??[]).map(r=>({name:r.name,scope:r.scope,mime:r.mime??[],max_bytes:r.max_bytes??10485760}))}),this.declares("ai")&&(s.ai={prompts:(e?.prompts??[]).map(r=>({id:r.id,mode:r.mode}))}),this.declares("broadcast")&&(this.joinRoom(),s.broadcast={room:{id:1,code:"MOCK01",name:`${this.slug} room`,status:"open",mode:e?.broadcast?.mock_mode??"first_in"},role:this.role,members:this.members(),state:this.stateSnapshot(),channels:e?.broadcast?.channels??["main"]}),s}roles(){const t=this.options.guest??!1,e=(this.manifest?.roles??[]).filter(s=>s.grant==="everyone"||s.grant==="members"&&!t||s.grant==="guests"&&t).map(s=>s.name);return[...new Set([t?"guest":"member",...e,...this.options.roles??[]])]}mockAi(t,e){this.aiHandlers.set(t,e)}dispose(){this.heartbeat&&clearInterval(this.heartbeat),this.channel&&(this.send({t:"bye",from:this.member()}),this.channel.close())}async handle(t,e){if(this.options.log?.(t,e),t==="rack:eject")return{ok:!0};const s=p(t);if((this.options.strict??this.manifest!==null)&&!this.declares(s))return{ok:!1,error:"undeclared_capability",message:`The manifest does not declare the [${s}] capability.`};switch(t){case"attempt:state":return this.attemptState(e);case"attempt:segment":return this.attemptSegment(e);case"attempt:complete":return this.attemptComplete(e);case"activity:statement":return this.statement(e);case"db:create":return this.dbCreate(e);case"db:get":return this.dbGet(e);case"db:update":return this.dbUpdate(e);case"db:delete":return this.dbDelete(e);case"db:list":return this.dbList(e);case"db:count":return this.dbCount(e);case"storage:put":return this.storagePut(e);case"storage:url":return this.storageUrl(e);case"storage:list":return this.storageList(e);case"storage:delete":return this.storageDelete(e);case"ai:ask":return this.aiAsk(e);case"ai:chat":return this.aiChat(e);case"ai:chat.reset":return this.chatHistory.delete(String(e.prompt)),{ok:!0};case"broadcast:join":return{ok:!0,data:{members:this.members(),state:this.stateSnapshot()}};case"broadcast:leave":return{ok:!0};case"broadcast:publish":return this.publish(e);case"broadcast:state.get":return{ok:!0,data:{state:this.stateSnapshot(Array.isArray(e.keys)?e.keys:void 0)}};case"broadcast:state.set":return this.stateSet(e);case"broadcast:presence":return{ok:!0,data:{members:this.members()}};case"broadcast:invite":return this.invite();case"broadcast:claim":return this.claim();case"broadcast:transfer":return this.transfer(e);default:return{ok:!1,error:"unknown_type",message:`Unknown message type [${t}].`}}}attempt(){return this.store.get(this.key("attempt"))??{id:1,status:"in_progress",state:null,segment:null}}saveAttempt(t){this.store.set(this.key("attempt"),t)}attemptState(t){const e=this.attempt();if(e.status==="completed")return{ok:!1,error:"completed",message:"This attempt is already completed."};const s=JSON.stringify(t.state??{}).length;return s>65536?{ok:!1,error:"validation",message:`State is ${s} bytes; the limit is 65536.`}:(this.saveAttempt({...e,state:t.state??null}),{ok:!0})}attemptSegment(t){const e=this.attempt();return e.status==="completed"?{ok:!1,error:"completed"}:(this.saveAttempt({...e,segment:t.segment??null}),{ok:!0})}attemptComplete(t){const e=this.attempt(),s=new Date().toISOString();return e.status!=="completed"&&(this.saveAttempt({...e,status:"completed"}),this.declares("activity")&&this.statement({verb:"completed",activity:{id:"attempt",type:"attempt"},result:t.summary??null,occurred_at:s})),{ok:!0,data:{status:"completed",completed_at:s}}}resetAttempt(){this.store.remove(this.key("attempt"))}statement(t){const e=String(t.verb??"");if(!/^[a-z][a-z0-9_.:-]*$/.test(e)||e.length>100)return{ok:!1,error:"validation",message:`Invalid verb [${e}].`};if(!t.activity?.id)return{ok:!1,error:"validation",message:"activity.id is required."};const r=`mock-${Date.now().toString(36)}-${this.statements.length+1}`;return this.statements.push({id:r,...t}),{ok:!0,data:{id:r}}}collectionKey(t){return this.key("db",t)}records(t){return this.store.get(this.collectionKey(t))??[]}saveRecords(t,e){this.store.set(this.collectionKey(t),e)}collectionDeclared(t){return this.manifest&&!(this.manifest.collections??[]).some(s=>s.name===t)?{ok:!1,error:"not_found",message:`Collection [${t}] is not declared in the manifest.`}:(this.manifest?.collections??[]).find(s=>s.name===t)?.scope==="attempt"&&this.attempt().status==="completed"?{ok:!1,error:"completed",message:"Attempt-scoped records are frozen after completion."}:null}dbCreate(t){const e=String(t.collection),s=this.collectionDeclared(e);if(s)return s;const r=new Date().toISOString(),i={id:`rec-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,7)}`,data:t.data??{},owner_id:this.participantId,created_at:r,updated_at:r};return this.saveRecords(e,[...this.records(e),i]),{ok:!0,data:{record:i}}}dbGet(t){const e=this.records(String(t.collection)).find(s=>s.id===t.id);return e?{ok:!0,data:{record:e}}:{ok:!1,error:"not_found"}}dbUpdate(t){const e=String(t.collection),s=this.collectionDeclared(e);if(s&&s.error==="completed")return s;const r=this.records(e),i=r.findIndex(c=>c.id===t.id);if(i===-1)return{ok:!1,error:"not_found"};const a=t.merge!==!1,o=t.data??{};return r[i]={...r[i],data:a?{...r[i].data,...o}:o,updated_at:new Date().toISOString()},this.saveRecords(e,r),{ok:!0,data:{record:r[i]}}}dbDelete(t){const e=String(t.collection),s=this.records(e);return s.some(r=>r.id===t.id)?(this.saveRecords(e,s.filter(r=>r.id!==t.id)),{ok:!0,data:{id:t.id}}):{ok:!1,error:"not_found"}}filter(t,e){return e?t.filter(s=>e.every(([r,i,a])=>{const o=r.split(".").reduce((c,l)=>c?.[l],s.data);switch(i){case"=":return o===a;case"!=":return o!==a;case"<":return o<a;case"<=":return o<=a;case">":return o>a;case">=":return o>=a;case"in":return Array.isArray(a)&&a.includes(o);default:return!1}})):t}dbList(t){const e=String(t.collection);if(this.manifest&&!(this.manifest.collections??[]).some(c=>c.name===e))return{ok:!1,error:"not_found",message:`Collection [${e}] is not declared in the manifest.`};let s=this.filter(this.records(e),t.where);if(typeof t.orderBy=="string"){const c=t.orderBy,l=t.direction==="desc"?-1:1;s=[...s].sort((R,d)=>{const b=c.split(".").reduce((m,h)=>m?.[h],R.data),v=c.split(".").reduce((m,h)=>m?.[h],d.data);return b<v?-l:b>v?l:0})}const r=Math.min(Number(t.limit??50),100),i=t.cursor?Number(t.cursor):0,a=s.slice(i,i+r),o=i+r<s.length?String(i+r):null;return{ok:!0,data:{records:a,nextCursor:o}}}dbCount(t){return{ok:!0,data:{count:this.filter(this.records(String(t.collection)),t.where).length}}}files(){return this.store.get(this.key("files"))??[]}storagePut(t){const e=String(t.upload),s=(this.manifest?.uploads??[]).find(c=>c.name===e);if(this.manifest&&!s)return{ok:!1,error:"not_found",message:`Upload [${e}] is not declared in the manifest.`};const r=String(t.mime??"");if(s?.mime&&s.mime.length>0&&!s.mime.some(c=>c===r||c.endsWith("/*")&&r.startsWith(c.slice(0,-1))))return{ok:!1,error:"validation",message:`MIME type [${r}] is not allowed for upload [${e}].`};const i=Number(t.bytes??0);if(s?.max_bytes&&i>s.max_bytes)return{ok:!1,error:"quota_exceeded",message:`File is ${i} bytes; the limit is ${s.max_bytes}.`};const a={id:`file-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,7)}`,upload:e,name:String(t.name??"blob"),mime:r,bytes:i,status:"clean",created_at:new Date().toISOString()},o=t.file;return typeof URL<"u"&&typeof URL.createObjectURL=="function"&&o instanceof Blob&&this.blobs.set(a.id,URL.createObjectURL(o)),this.store.set(this.key("files"),[...this.files(),a]),{ok:!0,data:{file:a}}}storageUrl(t){const e=this.files().find(s=>s.id===t.id);return e?{ok:!0,data:{url:this.blobs.get(e.id)??`mock://files/${e.id}`,expires_at:new Date(Date.now()+6e5).toISOString()}}:{ok:!1,error:"not_found"}}storageList(t){return{ok:!0,data:{files:this.files().filter(s=>!t.upload||s.upload===t.upload)}}}storageDelete(t){return this.store.set(this.key("files"),this.files().filter(e=>e.id!==t.id)),{ok:!0,data:{id:t.id}}}prompt(t){return(this.manifest?.prompts??[]).find(e=>e.id===t)??(this.manifest?null:{id:t,mode:"analysis"})}async aiAsk(t){const e=this.prompt(String(t.prompt));if(!e)return{ok:!1,error:"not_found",message:`Prompt [${t.prompt}] is not declared in the manifest.`};const s=this.aiHandlers.get(e.id),r=t.vars??{};return{ok:!0,data:{output:s?await s(r,"analysis"):f(e.output_schema,r),usage:{input_tokens:42,output_tokens:17}}}}async aiChat(t){const e=this.prompt(String(t.prompt));if(!e)return{ok:!1,error:"not_found",message:`Prompt [${t.prompt}] is not declared in the manifest.`};const s=String(t.message??""),r=this.chatHistory.get(e.id)??[];r.push({role:"user",content:s});const i=this.aiHandlers.get(e.id),a=i?String(await i({history:r},"chat",s)):`[mock ${e.id}] You said: ${s}`;r.push({role:"assistant",content:a}),this.chatHistory.set(e.id,r);const o=a.split(" "),c=`msg-${Date.now().toString(36)}`;return{stream:(async function*(){for(let l=0;l<o.length;l++)yield{delta:(l===0?"":" ")+o[l]}})(),final:()=>({message_id:c,text:a,usage:{input_tokens:s.length,output_tokens:a.length}})}}member(){return{id:this.participantId,name:this.participantName,role:this.role,present:!0,joinedAt:this.joinedAt}}members(){const t=Date.now(),e=[...this.roomMembers.values()].filter(i=>t-i.lastSeen<15e3).map(({lastSeen:i,joinedAt:a,...o})=>o),{joinedAt:s,...r}=this.member();return[r,...e].sort((i,a)=>i.id-a.id)}stateSnapshot(t){const e={};for(const[s,r]of Object.entries(this.roomState))(!t||t.includes(s))&&(e[s]=r.value);return e}send(t){this.channel?.postMessage({...t,sender:this.participantId})}joinRoom(){if(this.channel)return;const t=this.manifest?.broadcast?.mock_mode??"first_in";this.role=this.options.role??(t==="first_in"?"facilitator":"participant"),!(typeof BroadcastChannel>"u")&&(this.channel=new BroadcastChannel(this.key("room")),this.channel.onmessage=e=>this.onRoomMessage(e.data),this.send({t:"hello",from:this.member()}),this.heartbeat=setInterval(()=>this.send({t:"here",from:this.member()}),5e3))}reconcileFacilitator(t){t.role==="facilitator"&&this.role==="facilitator"&&(t.joinedAt??0)<this.joinedAt&&(this.role="participant",this.pushEvent({channel:"$room",event:"role",payload:{role:"participant",participant_id:this.participantId}}))}onRoomMessage(t){const e=t.from;if(t.sender!==this.participantId)switch(t.t){case"hello":e&&(this.roomMembers.set(e.id,{...e,lastSeen:Date.now()}),this.reconcileFacilitator(e),this.send({t:"here",from:this.member()}),this.send({t:"state-sync",state:this.roomState}),this.pushEvent({channel:"$room",event:"presence",payload:{members:this.members()}}));break;case"here":if(e){const s=this.roomMembers.has(e.id);this.roomMembers.set(e.id,{...e,lastSeen:Date.now()}),this.reconcileFacilitator(e),s||this.pushEvent({channel:"$room",event:"presence",payload:{members:this.members()}})}break;case"bye":e&&(this.roomMembers.delete(e.id),this.pushEvent({channel:"$room",event:"presence",payload:{members:this.members()}}));break;case"event":this.pushEvent({channel:String(t.channel),event:String(t.event),payload:t.payload,from:{participant_id:Number(t.sender),role:e?.role??"participant"}});break;case"state":{const s=String(t.key),r=Number(t.version);(this.roomState[s]?.version??0)<=r&&(this.roomState[s]={value:t.value,version:r},this.pushEvent({channel:"$room",event:"state",payload:{key:s,value:t.value,version:r}}));break}case"state-sync":for(const[s,r]of Object.entries(t.state))(this.roomState[s]?.version??0)<r.version&&(this.roomState[s]=r);break;case"role":e&&(this.roomMembers.set(e.id,{...e,lastSeen:Date.now()}),e.role==="facilitator"&&this.role==="facilitator"&&(this.role="participant",this.pushEvent({channel:"$room",event:"role",payload:{role:"participant",participant_id:this.participantId}})),this.pushEvent({channel:"$room",event:"role",payload:{role:e.role,participant_id:e.id}}),this.pushEvent({channel:"$room",event:"presence",payload:{members:this.members()}}));break}}invite(){return{ok:!0,data:{code:"MOCK01",url:typeof location>"u"?"https://rack.test/r/MOCK01":location.origin+location.pathname}}}setRole(t){this.role=t,this.send({t:"role",from:this.member()}),this.pushEvent({channel:"$room",event:"role",payload:{role:t,participant_id:this.participantId}})}publish(t){const e=String(t.event);if((this.manifest?.broadcast?.facilitator_events??[]).includes(e)&&this.role!=="facilitator")return{ok:!1,error:"forbidden",message:`Event [${e}] may only be sent by the facilitator.`};const r={participant_id:this.participantId,role:this.role};return this.send({t:"event",channel:t.channel,event:e,payload:t.payload,from:this.member()}),this.pushEvent({channel:String(t.channel??"main"),event:e,payload:t.payload,from:r}),{ok:!0}}stateSet(t){const e=String(t.key),s=(this.roomState[e]?.version??0)+1;return this.roomState[e]={value:t.value,version:s},this.send({t:"state",key:e,value:t.value,version:s}),this.pushEvent({channel:"$room",event:"state",payload:{key:e,value:t.value,version:s}}),{ok:!0,data:{version:s}}}claim(){return this.setRole("facilitator"),{ok:!0,data:{role:"facilitator"}}}transfer(t){if(this.role!=="facilitator")return{ok:!1,error:"forbidden",message:"Only the facilitator can transfer the role."};const e=this.roomMembers.get(Number(t.to));return e?(this.role="participant",e.role="facilitator",this.send({t:"role",from:{...e}}),this.pushEvent({channel:"$room",event:"role",payload:{role:"participant",participant_id:this.participantId}}),this.pushEvent({channel:"$room",event:"role",payload:{role:"facilitator",participant_id:e.id}}),{ok:!0}):{ok:!1,error:"not_found"}}}function f(n,t){if(!n)return{summary:`Mock analysis of ${JSON.stringify(t).slice(0,60)}`};const e=n.type;if(e==="object"){const s={};for(const[r,i]of Object.entries(n.properties??{}))s[r]=f(i,t);return s}return e==="array"?[f(n.items,t)]:e==="number"||e==="integer"?typeof n.minimum=="number"?n.minimum:3:e==="boolean"?!0:Array.isArray(n.enum)?n.enum[0]:"mock"}class k{constructor(t){this.rack=t,this.kind="mock"}async send(t,e,s={}){const r=await this.rack.handle(t,e??{});if("stream"in r){for await(const i of r.stream)s.onPartial?.(i);return{ok:!0,data:r.final()}}return r}fire(t,e={}){this.rack.handle(t,e??{})}onEvent(t){return this.rack.onEvent(t)}dispose(){this.rack.dispose()}}class B{get(t){try{const e=window.localStorage.getItem(t);return e===null?null:JSON.parse(e)}catch{return null}}set(t,e){try{window.localStorage.setItem(t,JSON.stringify(e))}catch{}}remove(t){try{window.localStorage.removeItem(t)}catch{}}keys(t){try{const e=[];for(let s=0;s<window.localStorage.length;s++){const r=window.localStorage.key(s);r&&r.startsWith(t)&&e.push(r)}return e}catch{return[]}}}const K=3e3,J=5e3;class A{constructor(){this.transport=null,this.initPromise=null,this.launchData=null,this.queue=[],this.launchListeners=new Set,this.eventListeners=new Set,this.pendingAiMocks=[];const t={send:(e,s,r)=>this.send(e,s,r),fire:(e,s)=>this.fire(e,s),launch:()=>this.launchData,declares:e=>this.declares(e),onEvent:e=>this.on("event",e),mode:()=>this.mode};this.auth=new O(t),this.attempt=new L(t),this.activity=new D(t),this.db=new q(t),this.storage=new N(t),this.ai=new I(t),this.broadcast=new C(t),this.media=new P(t),this.mock={ai:(e,s)=>{const r=this.mockRack();r?r.mockAi(e,s):this.pendingAiMocks.push([e,s])},resetAttempt:()=>this.mockRack()?.resetAttempt(),rack:()=>this.mockRack()}}init(t={}){return this.initPromise??=this.performInit(t),this.initPromise}get launched(){return this.launchData!==null}get mode(){return this.transport?.kind??"pending"}getLaunchData(){return this.launchData}declares(t){return(this.launchData?.capabilities??[]).some(e=>e.split("@")[0]===t)}on(t,e){if(t==="launch"){const r=e;return this.launchListeners.add(r),this.launchData&&r(this.launchData),()=>{this.launchListeners.delete(r)}}const s=e;return this.eventListeners.add(s),()=>{this.eventListeners.delete(s)}}eject(){this.fire("rack:eject",{}),this.transport?.kind==="mock"&&console.info("[ziprack mock] eject: the Rack would navigate away now.")}send(t,e,s){return this.launchData&&!t.startsWith("rack:")&&!this.declares(p(t))?Promise.resolve(w(p(t))):this.transport?this.transport.send(t,e,s):new Promise(r=>{this.queue.push({type:t,payload:e,options:s,resolve:r})})}fire(t,e={}){if(this.transport){this.transport.fire(t,e);return}this.send(t,e)}dispose(){this.transport?.dispose(),this.transport=null,this.initPromise=null,this.launchData=null}async performInit(t){const e=t.ackTimeoutMs??J,s=t.launchTimeoutMs??K;if(t.transport){const o=t.launch??(t.transport instanceof k?t.transport.rack.launchData():null);if(!o)throw new Error("init({ transport }) needs `launch` data unless the transport is a MockTransport.");return this.activate(t.transport,o)}if(!(typeof window<"u"&&window.parent!==window))return this.activateMock(t,"not embedded in a Rack");const i=new H({ackTimeoutMs:e});i.announceReady();const a=await i.waitForLaunch(s);return a?this.activate(i,a):(i.dispose(),this.activateMock(t,`no rack:launch within ${s}ms`))}async activateMock(t,e){console.info(`[ziprack] running in mock mode (${e})`);const s=await W(t.manifestUrl??"./manifest.json",t.slug),r=new F(s,new B,{log:(i,a)=>console.info(`[ziprack mock] ${i}`,a),...G()});for(const[i,a]of this.pendingAiMocks)r.mockAi(i,a);return this.pendingAiMocks=[],this.activate(new k(r),r.launchData(t.slug))}activate(t,e){return this.transport=t,this.launchData=e,t.onEvent(s=>this.eventListeners.forEach(r=>r(s))),this.launchListeners.forEach(s=>s(e)),this.flushQueue(),e}flushQueue(){if(this.transport)for(;this.queue.length>0;){const e=this.queue.shift();this.send(e.type,e.payload,e.options).then(e.resolve)}}mockRack(){return this.transport instanceof k?this.transport.rack:null}}async function W(n,t){if(typeof fetch!="function")return null;try{const e=await fetch(n,{cache:"no-store"});if(!e.ok)throw new Error(String(e.status));const s=await e.json();return t&&s.slug!==t&&console.warn(`[ziprack mock] manifest slug [${s.slug}] does not match init({ slug: '${t}' }).`),s}catch(e){return console.warn(`[ziprack mock] could not load ${n}; mock mode will accept every capability.`,e),null}}function G(n=typeof location>"u"?"":location.search){const t=new URLSearchParams(n),e=(t.get("ziprack-roles")??"").split(",").map(s=>s.trim()).filter(s=>s!=="");return{...e.length>0?{roles:e}:{},...t.has("ziprack-guest")?{guest:t.get("ziprack-guest")!=="0"}:{}}}const g=new A;function $(){return new A}return window.Rack=g,window.ZipRack={Rack:g,createRack:$},u.Rack=g,u.createRack=$,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"}),u})({});
2
2
  //# sourceMappingURL=ziprack-sdk.iife.js.map