@ai-matrx/alchemy 0.22.40 → 0.23.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.
- package/CHANGELOG.md +13 -0
- package/dist/surface.cjs +1 -1
- package/dist/surface.cjs.map +1 -1
- package/dist/surface.js +1 -1
- package/dist/surface.js.map +1 -1
- package/package.json +6 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
## 0.23.0 — surface reads go through @ai-matrx/data's doors
|
|
5
|
+
|
|
6
|
+
- `readSurfaceChain` reads `ui.ui_surface` and `ui.ui_surface_action` through `@ai-matrx/data`'s doors
|
|
7
|
+
(descriptors generated into `src/db/ui.ts` by `pnpm db:generate`): same columns, live rows only, the
|
|
8
|
+
chain's actions by `sort_order` (then `id`, stable), and now EVERY row (paged past the 1,000-row cap).
|
|
9
|
+
Answers are unchanged: a database error keeps its own sentence and code (`read_failed:<code>`), a
|
|
10
|
+
transport failure is `unreachable` (retryable). Alchemy now has no direct database call (the
|
|
11
|
+
`check-db-doors` guard blocks one at zero).
|
|
12
|
+
- New dependency: `@ai-matrx/data` (>= 0.25.0).
|
|
13
|
+
|
|
14
|
+
Consumer action: none — the signature is unchanged (pass the viewer's supabase-js client as before).
|
|
15
|
+
|
|
3
16
|
## 0.22.40
|
|
4
17
|
|
|
5
18
|
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
package/dist/surface.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var surface_exports={};__export(surface_exports,{APPLET_SURFACE_PREFIX:()=>APPLET_SURFACE_PREFIX,PAGE_ACTION_STREAM:()=>PAGE_ACTION_STREAM,createItemSources:()=>createItemSources,createLiveValues:()=>createLiveValues,createSurfacePort:()=>createSurfacePort,itemSources:()=>itemSources,liveSlotsOf:()=>liveSlotsOf,liveValueOf:()=>liveValueOf,liveValues:()=>liveValues,mergeLiveCapture:()=>mergeLiveCapture,readSurfaceChain:()=>readSurfaceChain,surfaceActionVisible:()=>surfaceActionVisible,valueStream:()=>valueStream});module.exports=__toCommonJS(surface_exports);var DEFAULT_MAX_INHERITANCE_DEPTH=3;var APPLET_CATEGORY="applet";function compact(value){return Object.fromEntries(Object.entries(value).filter(([,v])=>v!==void 0))}function surfaceActionRowToAction(row){return compact({name:row.name,category:row.category,label:row.label,description:row.description,input:row.input??void 0,output:row.output??void 0,runs:row.runs,target:row.target??void 0,triggeredBy:[...row.triggered_by],policy:row.policy,updatesValue:row.updates_value??void 0,itemType:row.item_type||void 0,sortOrder:row.sort_order,origin:compact({source:"surface-action",surfaceName:row.surface_name,surfaceId:row.surface_id,id:row.id,declaredBy:row.declared_by,codeRef:row.code_ref??void 0,organizationId:row.organization_id??void 0})})}function toActionInfo(declaration,declaredOn){return compact({name:declaration.name,category:declaration.category??APPLET_CATEGORY,label:declaration.label,description:declaration.description,runs:declaration.runs,policy:declaration.policy,triggeredBy:[...declaration.triggeredBy],input:declaration.input,output:declaration.output,declaredOn,target:declaration.target,updatesValue:declaration.updatesValue,itemType:declaration.itemType,source:declaration.origin?.source})}function resolveActionChain(chain,options={}){let maxDepth=options.maxDepth??DEFAULT_MAX_INHERITANCE_DEPTH,names=chain.map(link=>link.surfaceName),seen=new Set;for(let name of names){if(seen.has(name))throw new Error(`[actions] inheritance CYCLE detected at "${name}". Fix the surface chain: ${names.join(" \u2192 ")}`);seen.add(name)}if(chain.length-1>maxDepth)throw new Error(`[actions] inheritance chain for "${names[names.length-1]}" exceeds depth ${maxDepth}: ${names.join(" \u2192 ")}`);let byName=new Map;for(let link of chain){let own=new Set;for(let action of link.actions){if(own.has(action.name))throw new Error(`[actions] surface "${link.surfaceName}" declares Action "${action.name}" twice. Names are unique; nothing was overwritten.`);own.add(action.name),byName.set(action.name,toActionInfo(action,link.surfaceName))}}return[...byName.values()]}function asSurfaceClient(client){let c=client;if(typeof c.schema!="function")throw new Error("[alchemy/surface] the surface runtime needs a supabase-js v2 client carrying the viewer's session (it has no schema()).");return c}function readError(error,code="read_failed"){let out={code:error.code?`${code}:${error.code}`:code,message:error.message,retryable:!1};return error.details&&(out.diagnostic=error.details),out}async function read(query,shape){try{let answer=await query();return answer.error?{ok:!1,error:readError(answer.error)}:{ok:!0,data:shape(answer.data)}}catch(err){return{ok:!1,error:{code:"unreachable",message:err instanceof Error?err.message:String(err),retryable:!0}}}}function isRecord(value){return typeof value=="object"&&value!==null&&!Array.isArray(value)}var APPLET_SURFACE_PREFIX="applets/";function fail(code,message,retryable=!1){return{ok:!1,error:{code,message,retryable}}}function surfaceActionVisible(row,organizationIds){return row.declared_by==="code"||row.surface_name.startsWith(APPLET_SURFACE_PREFIX)?!0:typeof row.organization_id=="string"&&(organizationIds??[]).includes(row.organization_id)}async function readSurfaceChain(client,surfaceName,options={}){let db;try{db=asSurfaceClient(client)}catch(err){return fail("client_invalid",err instanceof Error?err.message:String(err))}let maxDepth=options.maxDepth??DEFAULT_MAX_INHERITANCE_DEPTH,names=[],at=surfaceName;for(;at;){if(names.includes(at))return fail("surface_cycle",`The surface chain loops at "${at}": ${[...names,at].join(" \u2192 ")}.`);if(names.length>maxDepth)return fail("surface_too_deep",`The surface chain for "${surfaceName}" is deeper than ${maxDepth}: ${[...names,at].join(" \u2192 ")}.`);let name=at,row=await read(()=>db.schema("ui").from("ui_surface").select("name, parent_surface_name").eq("name",name).is("deleted_at",null).maybeSingle(),d=>d);if(!row.ok)return row;if(!isRecord(row.data))return fail("surface_not_found",names.length===0?`The surface "${at}" does not exist yet; saving the Applet writes it (ui.save_applet_surface).`:`The surface "${names[names.length-1]}" names a parent "${at}" that does not exist.`);names.push(at);let parent=row.data.parent_surface_name;at=typeof parent=="string"&&parent?parent:null}let rows=await read(()=>db.schema("ui").from("ui_surface_action").select("*").in("surface_name",names).is("deleted_at",null).order("sort_order",{ascending:!0}),d=>Array.isArray(d)?d:[]);if(!rows.ok)return rows;let visible=rows.data.filter(r=>surfaceActionVisible(r,options.organizationIds)),chain=[...names].reverse().map(name=>({surfaceName:name,actions:visible.filter(r=>r.surface_name===name).map(surfaceActionRowToAction)}));try{return{ok:!0,data:resolveActionChain(chain,{maxDepth})}}catch(err){return fail("surface_chain",err instanceof Error?err.message:String(err))}}function fail2(code,message,retryable=!1,diagnostic){return{ok:!1,error:diagnostic?{code,message,retryable,diagnostic}:{code,message,retryable}}}function messageOf(error){return error instanceof Error&&error.message.trim()?error.message:typeof error=="string"&&error.trim()?error:"The run failed without saying why."}function newRunId(){let c=globalThis.crypto;return c?.randomUUID?c.randomUUID():`run-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,10)}`}function fillActionTarget(template,input){let source=input&&typeof input=="object"&&!Array.isArray(input)?input:{},missing=[];return{value:template.replace(/\{([A-Za-z_][A-Za-z0-9_]*)\}/g,(_,key)=>{let v=source[key];return v==null||v===""||typeof v=="object"?(missing.push(key),""):encodeURIComponent(String(v))}),missing}}function fillName(template,input){let filled=fillActionTarget(template,input);return{value:decodeURIComponent(filled.value),missing:filled.missing}}function toJson(value){return value===void 0?null:JSON.parse(JSON.stringify(value))}function refuseCaller(info,by){return info.triggeredBy.includes(by)?info.policy==="manual"&&by!=="person"?{code:"manual_only",message:`"${info.label}" (${info.name}) runs only when a person starts it.`,retryable:!1}:null:{code:"caller_not_allowed",message:`"${info.label}" (${info.name}) can't be started by ${CALLER_WORDS[by]}; it allows ${info.triggeredBy.join(", ")||"no one"}.`,retryable:!1}}var CALLER_WORDS={person:"a person",agent:"an agent",applet:"an applet",automation:"an automation"};function absent(info,capability){return fail2("capability_absent",`"${info.label}" can't run here: ${capability} isn't available on this page.`)}async function runSurfaceAction(info,input,by,ports,options={}){let refusal=refuseCaller(info,by);if(refusal)return{ok:!1,error:refusal};let context={runId:options.runId??newRunId(),by,viewerId:options.viewerId??null,surfaceName:info.declaredOn,policy:info.policy},capture=(error,stage)=>{try{ports.diagnostics.capture(error,{area:"actions",detail:{action:info.name,runs:info.runs,stage,runId:context.runId}})}catch{}};try{if(info.source==="write-target"&&ports.door){let receipt=await ports.door.write({surfaceName:info.declaredOn,target:info.name,...info.itemType?{itemType:info.itemType}:{},...options.itemIdentity?{itemIdentity:options.itemIdentity}:{},...options.ops?{ops:options.ops}:{value:input},by,runId:context.runId},options.signal);return receipt.status==="refused"?{ok:!1,error:{code:receipt.reason,message:receipt.sentence,retryable:receipt.reason==="failed",remedy:receipt.remedy}}:{ok:!0,data:receipt}}if(info.policy==="ask"&&by!=="person"&&info.runs!=="server"){if(!ports.approvals)return fail2("needs_approval",`"${info.label}" needs a person's approval, and there is no one to ask here.`);if(!await ports.approvals.ask({action:info.name,label:info.label,description:info.description,input:toJson(input),by,surfaceName:info.declaredOn,runId:context.runId}))return fail2("declined",`"${info.label}" was not approved, so it did not run.`)}switch(info.runs){case"server":{if(!ports.serverActions)return absent(info,"server actions");let filled=fillName(info.target??info.name,input);if(filled.missing.length)return fail2("input_missing",`"${info.label}" needs ${filled.missing.join(", ")}.`);let response=await ports.serverActions.run({name:filled.value,input:toJson(input),by,surface_name:info.declaredOn,policy:info.policy});return response.status==="refused"?fail2("refused",response.message):{ok:!0,data:response}}case"mandate":{if(!ports.mandates)return absent(info,"running a job");if(!info.target)return fail2("target_missing",`"${info.label}" names no job to run.`);let filled=fillName(info.target,input);if(filled.missing.length)return fail2("input_missing",`"${info.label}" needs ${filled.missing.join(", ")}.`);let record=input&&typeof input=="object"&&!Array.isArray(input)?input:{},variables=/\{[A-Za-z_]/.test(info.target)?record.variables&&typeof record.variables=="object"?toJson(record.variables):{}:toJson(record),userInput=typeof record.userInput=="string"?record.userInput:void 0;return{ok:!0,data:await ports.mandates.run({alias:filled.value,variables,...userInput!==void 0?{userInput}:{},by,policy:info.policy})}}case"navigate":{if(!ports.overlay)return absent(info,"navigation");let filled=fillActionTarget(info.target??"",input);if(filled.missing.length)return fail2("input_missing",`"${info.label}" needs ${filled.missing.join(", ")}.`);let to=info.target&&/^\{[A-Za-z_][A-Za-z0-9_]*\}$/.test(info.target)?decodeURIComponent(filled.value):filled.value;return to?(ports.overlay.navigate(to),{ok:!0,data:{to}}):fail2("target_missing",`"${info.label}" names no page to open.`)}case"page":return ports.pageActions?await ports.pageActions.dispatch(info.name,input,context):absent(info,"this page's actions");default:{let runs=info.runs;return fail2("runs_unknown",`"${info.label}" runs as "${String(runs)}", which nothing here knows how to run.`)}}}catch(error){return capture(error,"run"),fail2("run_failed",messageOf(error),!0)}}function isNamed(v){return typeof v=="object"&&v!==null&&"id"in v&&"name"in v}function mergeLiveCapture(base,contributions){let owners=new Map(base.sections.map(s=>[s.id,"page"])),sections=[...base.sections],identity={...base.identity};for(let c of contributions){for(let[k,v]of Object.entries(c.identity??{})){let prior=identity[k];(prior==null||isNamed(prior)&&!prior.name)&&(identity[k]=v)}for(let s of c.sections){let prior=owners.get(s.id);if(prior)throw new Error(`[live values] section "${s.id}" is claimed by both "${prior}" and "${c.owner}"`);owners.set(s.id,c.owner),sections.push(s)}}return{...base,identity,sections}}function liveSlotsOf(capture){let onDemand=capture.sections.filter(s=>typeof s.load=="function").map(s=>({id:s.id,title:s.title,...s.description?{description:s.description}:{},role:s.role,...s.brief!==void 0?{brief:s.brief}:{},load:()=>s.load()}));return{selection:{choices:capture.selection,text:null},"on-demand":onDemand,errors:capture.errors,requests:capture.requests}}function liveValueOf(capture,name){switch(name){case"selection":return capture.selection;case"errors":return capture.errors;case"requests":return capture.requests;case"on-demand":return capture.sections.filter(s=>typeof s.load=="function").map(s=>({id:s.id,title:s.title,role:s.role}));case"title":case"route":case"url":case"kind":case"identity":return capture[name];default:break}return Object.prototype.hasOwnProperty.call(capture.identity,name)?capture.identity[name]:Object.prototype.hasOwnProperty.call(capture.selection,name)?capture.selection[name]:capture.sections.find(s=>s.id===name)?.value}function createLiveValues(){let nextId=0,version=0,pages=[],parts=[],listeners=new Set,emit=()=>{version+=1;for(let listener of[...listeners])listener()},contributions=()=>parts.map(p=>{let got=p.read();return Array.isArray(got)?{owner:p.owner,sections:got}:{owner:p.owner,...got}}),read2=()=>{let top=pages[pages.length-1];return top?mergeLiveCapture(top.read(),contributions()):null};return{publish(get){let id=++nextId;return pages=[...pages,{id,read:get}],emit(),()=>{pages=pages.filter(p=>p.id!==id),emit()}},contribute(owner,get){let id=++nextId;return parts=[...parts,{id,owner,read:get}],emit(),()=>{parts=parts.filter(p=>p.id!==id),emit()}},touch:emit,has:()=>pages.length>0,read:read2,outline(){let top=pages[pages.length-1];if(!top)return null;let base=top.read(),sections=[...base.sections,...contributions().flatMap(c=>c.sections)];return{surfaceName:base.surfaceName??null,kind:base.kind,title:base.title,loadable:sections.filter(s=>typeof s.load=="function").map(s=>({id:s.id,title:s.title}))}},readFor(surfaceName){let capture=read2();return capture&&capture.surfaceName===surfaceName?capture:null},subscribe(listener){return listeners.add(listener),()=>{listeners.delete(listener)}},version:()=>version}}var liveValues=createLiveValues();var PAGE_ACTION_STREAM="actions",valueStream=name=>`value:${name}`;function clone(value){if(value===void 0)return value;let sc=globalThis.structuredClone;return sc?sc(value):JSON.parse(JSON.stringify(value))}function errorOf(err,code="surface_failed"){return{ok:!1,error:{code,message:err instanceof Error&&err.message?err.message:String(err),retryable:!1}}}var sequence=0;function defaultMint(prefix){let c=globalThis.crypto;return c?.randomUUID?`${prefix}-${c.randomUUID()}`:`${prefix}-${Date.now().toString(36)}-${(sequence++).toString(36)}`}function createSurfacePort(opts){let mint=opts.mintId??defaultMint,pending=opts.pending??new Map,parked=new Set,values=new Map,mounted=null,disposed=!1,report=(error,stage,detail={})=>{try{opts.ports.diagnostics.capture(error,{area:"actions",detail:{stage:`surface.${stage}`,...detail}})}catch{}},memberships=async()=>{if(opts.organizationIds)try{return await opts.organizationIds()}catch(err){report(err,"organizationIds");return}},read2=async name=>{let organizationIds=await memberships();return readSurfaceChain(opts.client,name,{...opts.maxDepth!==void 0?{maxDepth:opts.maxDepth}:{},...organizationIds?{organizationIds}:{}})},currentChain=async fresh=>{if(mounted&&!fresh)return{ok:!0,data:mounted.chain};let name=mounted?.name??(opts.surfaceName?await opts.surfaceName():null);if(!name)return{ok:!1,error:{code:"not_mounted",message:"No surface is mounted here yet.",retryable:!1}};let chain=await read2(name);return chain.ok&&mounted?.name===name&&(mounted.chain=chain.data),chain},awaitPageAction=runId=>disposed?Promise.resolve({ok:!1,error:{code:"host_disposed",message:"This page has closed.",retryable:!1}}):new Promise(resolve=>{parked.add(runId),pending.set(runId,resolve)}),selfRouted={dispatch(name,input,context){let waiting=awaitPageAction(context.runId),event={action:name,input,runId:context.runId,by:context.by},forget=()=>{pending.delete(context.runId),parked.delete(context.runId)};if(disposed)return forget(),Promise.resolve({ok:!1,error:{code:"host_disposed",message:"This page closed, so the action did not run.",retryable:!1}});let heard;try{heard=opts.emit(PAGE_ACTION_STREAM,event)}catch(err){throw forget(),err}return heard!==!0?(forget(),Promise.resolve({ok:!1,error:{code:"action_unhandled",message:`No page is open to run "${name}".`,retryable:!1}})):waiting}},port={async mount(name){try{let chain=await read2(name);if(!chain.ok)return chain;if(opts.seedValues)try{let seeds=await opts.seedValues();for(let[key,v]of Object.entries(seeds))values.has(key)||values.set(key,clone(v))}catch(err){report(err,"seedValues",{surface:name})}return mounted={mountId:mint("mount"),name,chain:chain.data},{ok:!0,data:{mountId:mounted.mountId}}}catch(err){return errorOf(err)}},async unmount(mountId){mounted?.mountId===mountId&&(mounted=null)},async getValue(name){if(values.has(name)||!opts.live||!mounted)return clone(values.get(name));try{let capture=opts.live.readFor(mounted.name);return capture?clone(liveValueOf(capture,name)):void 0}catch(err){report(err,"live",{name});return}},async setValue(name,v){let copy;try{copy=clone(v)}catch(err){report(err,"setValue",{name});return}values.set(name,copy);try{opts.emit(valueStream(name),clone(v))}catch(err){report(err,"emit",{stream:valueStream(name)})}},async listActions(){try{let chain=await currentChain(!1);return chain.ok?clone(chain.data):(report(chain.error,"listActions"),[])}catch(err){return report(err,"listActions"),[]}},async runAction(name,input,by){let info;try{let known=await currentChain(!1);if(info=known.ok?known.data.find(a=>a.name===name):void 0,!info){let fresh=await currentChain(!0);if(!fresh.ok)return fresh;info=fresh.data.find(a=>a.name===name)}}catch(err){return errorOf(err)}if(!info)return{ok:!1,error:{code:"action_not_found",message:`This page has no action named "${name}".`,retryable:!1}};let viewerId=null;try{viewerId=opts.viewerId?await opts.viewerId():null}catch(err){report(err,"viewerId")}let ports={...opts.ports,pageActions:opts.pageActions??selfRouted},result=await runSurfaceAction(info,input,by,ports,{runId:mint("act"),viewerId});return result.ok&&info.updatesValue&&await port.setValue(info.updatesValue,result.data),result},async completeAction(runId,result){let resolve=pending.get(runId);if(!resolve){report(new Error(`completeAction for run "${runId}", which is not waiting (already answered, never routed here, or the page closed).`),"completeAction",{runId});return}pending.delete(runId),parked.delete(runId),resolve?.(result)},awaitPageAction,dispose(){disposed=!0;for(let runId of parked){let resolve=pending.get(runId);pending.delete(runId),resolve?.({ok:!1,error:{code:"host_disposed",message:"This page closed before the action finished.",retryable:!1}})}parked.clear(),mounted=null,values.clear()}};return port}function createItemSources(onError){let sources=new Map;return{register(containerId,source){return sources.set(containerId,source),()=>{sources.get(containerId)===source&&sources.delete(containerId)}},resolve(target){let source=sources.get(target.containerId);if(!source)return null;try{return source(target)??null}catch(error){return onError?.(error,target),null}},has:containerId=>sources.has(containerId)}}var itemSources=createItemSources((error,target)=>{console.error(`[alchemy items] the source for "${target.containerId}" threw resolving ${target.level}`,error)});
|
|
1
|
+
"use strict";var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var surface_exports={};__export(surface_exports,{APPLET_SURFACE_PREFIX:()=>APPLET_SURFACE_PREFIX,PAGE_ACTION_STREAM:()=>PAGE_ACTION_STREAM,createItemSources:()=>createItemSources,createLiveValues:()=>createLiveValues,createSurfacePort:()=>createSurfacePort,itemSources:()=>itemSources,liveSlotsOf:()=>liveSlotsOf,liveValueOf:()=>liveValueOf,liveValues:()=>liveValues,mergeLiveCapture:()=>mergeLiveCapture,readSurfaceChain:()=>readSurfaceChain,surfaceActionVisible:()=>surfaceActionVisible,valueStream:()=>valueStream});module.exports=__toCommonJS(surface_exports);var import_db=require("@ai-matrx/data/db");var uiSurface={schema:"ui",name:"ui_surface",kind:"table",type:"entity",pk:["name"],org:{rule:"binding"},versioned:!1,softDelete:!0,archive:null,readOnly:!1,deletesRefused:!1,readOnlyColumns:[],listScope:"organization",realtime:!1,createdBy:!0},uiSurfaceAction={schema:"ui",name:"ui_surface_action",kind:"table",type:null,pk:["id"],org:{rule:"binding"},versioned:!1,softDelete:!0,archive:null,readOnly:!1,deletesRefused:!1,readOnlyColumns:[],listScope:null,realtime:!1,createdBy:!0};var DEFAULT_MAX_INHERITANCE_DEPTH=3;var APPLET_CATEGORY="applet";function compact(value){return Object.fromEntries(Object.entries(value).filter(([,v])=>v!==void 0))}function surfaceActionRowToAction(row){return compact({name:row.name,category:row.category,label:row.label,description:row.description,input:row.input??void 0,output:row.output??void 0,runs:row.runs,target:row.target??void 0,triggeredBy:[...row.triggered_by],policy:row.policy,updatesValue:row.updates_value??void 0,itemType:row.item_type||void 0,sortOrder:row.sort_order,origin:compact({source:"surface-action",surfaceName:row.surface_name,surfaceId:row.surface_id,id:row.id,declaredBy:row.declared_by,codeRef:row.code_ref??void 0,organizationId:row.organization_id??void 0})})}function toActionInfo(declaration,declaredOn){return compact({name:declaration.name,category:declaration.category??APPLET_CATEGORY,label:declaration.label,description:declaration.description,runs:declaration.runs,policy:declaration.policy,triggeredBy:[...declaration.triggeredBy],input:declaration.input,output:declaration.output,declaredOn,target:declaration.target,updatesValue:declaration.updatesValue,itemType:declaration.itemType,source:declaration.origin?.source})}function resolveActionChain(chain,options={}){let maxDepth=options.maxDepth??DEFAULT_MAX_INHERITANCE_DEPTH,names=chain.map(link=>link.surfaceName),seen=new Set;for(let name of names){if(seen.has(name))throw new Error(`[actions] inheritance CYCLE detected at "${name}". Fix the surface chain: ${names.join(" \u2192 ")}`);seen.add(name)}if(chain.length-1>maxDepth)throw new Error(`[actions] inheritance chain for "${names[names.length-1]}" exceeds depth ${maxDepth}: ${names.join(" \u2192 ")}`);let byName=new Map;for(let link of chain){let own=new Set;for(let action of link.actions){if(own.has(action.name))throw new Error(`[actions] surface "${link.surfaceName}" declares Action "${action.name}" twice. Names are unique; nothing was overwritten.`);own.add(action.name),byName.set(action.name,toActionInfo(action,link.surfaceName))}}return[...byName.values()]}function asSurfaceClient(client){let c=client;if(typeof c.schema!="function")throw new Error("[alchemy/surface] the surface runtime needs a supabase-js v2 client carrying the viewer's session (it has no schema()).");return c}function readError(error,code="read_failed"){let out={code:error.code?`${code}:${error.code}`:code,message:error.message,retryable:!1};return error.details&&(out.diagnostic=error.details),out}async function door(run){try{return{ok:!0,data:await run()}}catch(err){let cause=err??{};return cause.name==="DatabaseError"&&cause.cause?{ok:!1,error:readError(cause.cause)}:{ok:!1,error:{code:"unreachable",message:err instanceof Error?err.message:String(err),retryable:!0}}}}function isRecord(value){return typeof value=="object"&&value!==null&&!Array.isArray(value)}var APPLET_SURFACE_PREFIX="applets/";function fail(code,message,retryable=!1){return{ok:!1,error:{code,message,retryable}}}function surfaceActionVisible(row,organizationIds){return row.declared_by==="code"||row.surface_name.startsWith(APPLET_SURFACE_PREFIX)?!0:typeof row.organization_id=="string"&&(organizationIds??[]).includes(row.organization_id)}async function readSurfaceChain(client,surfaceName,options={}){let db;try{db=(0,import_db.createDb)({client:asSurfaceClient(client)})}catch(err){return fail("client_invalid",err instanceof Error?err.message:String(err))}let maxDepth=options.maxDepth??DEFAULT_MAX_INHERITANCE_DEPTH,names=[],at=surfaceName;for(;at;){if(names.includes(at))return fail("surface_cycle",`The surface chain loops at "${at}": ${[...names,at].join(" \u2192 ")}.`);if(names.length>maxDepth)return fail("surface_too_deep",`The surface chain for "${surfaceName}" is deeper than ${maxDepth}: ${[...names,at].join(" \u2192 ")}.`);let name=at,row=await door(()=>(0,import_db.read)(db,uiSurface,name,{columns:["name","parent_surface_name"]}));if(!row.ok)return row;if(!isRecord(row.data))return fail("surface_not_found",names.length===0?`The surface "${at}" does not exist yet; saving the Applet writes it (ui.save_applet_surface).`:`The surface "${names[names.length-1]}" names a parent "${at}" that does not exist.`);names.push(at);let parent=row.data.parent_surface_name;at=typeof parent=="string"&&parent?parent:null}let rows=await door(async()=>await(0,import_db.listAll)(db,uiSurfaceAction,q=>q.in("surface_name",names),{orderBy:"sort_order"}));if(!rows.ok)return rows;let visible=rows.data.filter(r=>surfaceActionVisible(r,options.organizationIds)),chain=[...names].reverse().map(name=>({surfaceName:name,actions:visible.filter(r=>r.surface_name===name).map(surfaceActionRowToAction)}));try{return{ok:!0,data:resolveActionChain(chain,{maxDepth})}}catch(err){return fail("surface_chain",err instanceof Error?err.message:String(err))}}function fail2(code,message,retryable=!1,diagnostic){return{ok:!1,error:diagnostic?{code,message,retryable,diagnostic}:{code,message,retryable}}}function messageOf(error){return error instanceof Error&&error.message.trim()?error.message:typeof error=="string"&&error.trim()?error:"The run failed without saying why."}function newRunId(){let c=globalThis.crypto;return c?.randomUUID?c.randomUUID():`run-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,10)}`}function fillActionTarget(template,input){let source=input&&typeof input=="object"&&!Array.isArray(input)?input:{},missing=[];return{value:template.replace(/\{([A-Za-z_][A-Za-z0-9_]*)\}/g,(_,key)=>{let v=source[key];return v==null||v===""||typeof v=="object"?(missing.push(key),""):encodeURIComponent(String(v))}),missing}}function fillName(template,input){let filled=fillActionTarget(template,input);return{value:decodeURIComponent(filled.value),missing:filled.missing}}function toJson(value){return value===void 0?null:JSON.parse(JSON.stringify(value))}function refuseCaller(info,by){return info.triggeredBy.includes(by)?info.policy==="manual"&&by!=="person"?{code:"manual_only",message:`"${info.label}" (${info.name}) runs only when a person starts it.`,retryable:!1}:null:{code:"caller_not_allowed",message:`"${info.label}" (${info.name}) can't be started by ${CALLER_WORDS[by]}; it allows ${info.triggeredBy.join(", ")||"no one"}.`,retryable:!1}}var CALLER_WORDS={person:"a person",agent:"an agent",applet:"an applet",automation:"an automation"};function absent(info,capability){return fail2("capability_absent",`"${info.label}" can't run here: ${capability} isn't available on this page.`)}async function runSurfaceAction(info,input,by,ports,options={}){let refusal=refuseCaller(info,by);if(refusal)return{ok:!1,error:refusal};let context={runId:options.runId??newRunId(),by,viewerId:options.viewerId??null,surfaceName:info.declaredOn,policy:info.policy},capture=(error,stage)=>{try{ports.diagnostics.capture(error,{area:"actions",detail:{action:info.name,runs:info.runs,stage,runId:context.runId}})}catch{}};try{if(info.source==="write-target"&&ports.door){let receipt=await ports.door.write({surfaceName:info.declaredOn,target:info.name,...info.itemType?{itemType:info.itemType}:{},...options.itemIdentity?{itemIdentity:options.itemIdentity}:{},...options.ops?{ops:options.ops}:{value:input},by,runId:context.runId},options.signal);return receipt.status==="refused"?{ok:!1,error:{code:receipt.reason,message:receipt.sentence,retryable:receipt.reason==="failed",remedy:receipt.remedy}}:{ok:!0,data:receipt}}if(info.policy==="ask"&&by!=="person"&&info.runs!=="server"){if(!ports.approvals)return fail2("needs_approval",`"${info.label}" needs a person's approval, and there is no one to ask here.`);if(!await ports.approvals.ask({action:info.name,label:info.label,description:info.description,input:toJson(input),by,surfaceName:info.declaredOn,runId:context.runId}))return fail2("declined",`"${info.label}" was not approved, so it did not run.`)}switch(info.runs){case"server":{if(!ports.serverActions)return absent(info,"server actions");let filled=fillName(info.target??info.name,input);if(filled.missing.length)return fail2("input_missing",`"${info.label}" needs ${filled.missing.join(", ")}.`);let response=await ports.serverActions.run({name:filled.value,input:toJson(input),by,surface_name:info.declaredOn,policy:info.policy});return response.status==="refused"?fail2("refused",response.message):{ok:!0,data:response}}case"mandate":{if(!ports.mandates)return absent(info,"running a job");if(!info.target)return fail2("target_missing",`"${info.label}" names no job to run.`);let filled=fillName(info.target,input);if(filled.missing.length)return fail2("input_missing",`"${info.label}" needs ${filled.missing.join(", ")}.`);let record=input&&typeof input=="object"&&!Array.isArray(input)?input:{},variables=/\{[A-Za-z_]/.test(info.target)?record.variables&&typeof record.variables=="object"?toJson(record.variables):{}:toJson(record),userInput=typeof record.userInput=="string"?record.userInput:void 0;return{ok:!0,data:await ports.mandates.run({alias:filled.value,variables,...userInput!==void 0?{userInput}:{},by,policy:info.policy})}}case"navigate":{if(!ports.overlay)return absent(info,"navigation");let filled=fillActionTarget(info.target??"",input);if(filled.missing.length)return fail2("input_missing",`"${info.label}" needs ${filled.missing.join(", ")}.`);let to=info.target&&/^\{[A-Za-z_][A-Za-z0-9_]*\}$/.test(info.target)?decodeURIComponent(filled.value):filled.value;return to?(ports.overlay.navigate(to),{ok:!0,data:{to}}):fail2("target_missing",`"${info.label}" names no page to open.`)}case"page":return ports.pageActions?await ports.pageActions.dispatch(info.name,input,context):absent(info,"this page's actions");default:{let runs=info.runs;return fail2("runs_unknown",`"${info.label}" runs as "${String(runs)}", which nothing here knows how to run.`)}}}catch(error){return capture(error,"run"),fail2("run_failed",messageOf(error),!0)}}function isNamed(v){return typeof v=="object"&&v!==null&&"id"in v&&"name"in v}function mergeLiveCapture(base,contributions){let owners=new Map(base.sections.map(s=>[s.id,"page"])),sections=[...base.sections],identity={...base.identity};for(let c of contributions){for(let[k,v]of Object.entries(c.identity??{})){let prior=identity[k];(prior==null||isNamed(prior)&&!prior.name)&&(identity[k]=v)}for(let s of c.sections){let prior=owners.get(s.id);if(prior)throw new Error(`[live values] section "${s.id}" is claimed by both "${prior}" and "${c.owner}"`);owners.set(s.id,c.owner),sections.push(s)}}return{...base,identity,sections}}function liveSlotsOf(capture){let onDemand=capture.sections.filter(s=>typeof s.load=="function").map(s=>({id:s.id,title:s.title,...s.description?{description:s.description}:{},role:s.role,...s.brief!==void 0?{brief:s.brief}:{},load:()=>s.load()}));return{selection:{choices:capture.selection,text:null},"on-demand":onDemand,errors:capture.errors,requests:capture.requests}}function liveValueOf(capture,name){switch(name){case"selection":return capture.selection;case"errors":return capture.errors;case"requests":return capture.requests;case"on-demand":return capture.sections.filter(s=>typeof s.load=="function").map(s=>({id:s.id,title:s.title,role:s.role}));case"title":case"route":case"url":case"kind":case"identity":return capture[name];default:break}return Object.prototype.hasOwnProperty.call(capture.identity,name)?capture.identity[name]:Object.prototype.hasOwnProperty.call(capture.selection,name)?capture.selection[name]:capture.sections.find(s=>s.id===name)?.value}function createLiveValues(){let nextId=0,version=0,pages=[],parts=[],listeners=new Set,emit=()=>{version+=1;for(let listener of[...listeners])listener()},contributions=()=>parts.map(p=>{let got=p.read();return Array.isArray(got)?{owner:p.owner,sections:got}:{owner:p.owner,...got}}),read=()=>{let top=pages[pages.length-1];return top?mergeLiveCapture(top.read(),contributions()):null};return{publish(get){let id=++nextId;return pages=[...pages,{id,read:get}],emit(),()=>{pages=pages.filter(p=>p.id!==id),emit()}},contribute(owner,get){let id=++nextId;return parts=[...parts,{id,owner,read:get}],emit(),()=>{parts=parts.filter(p=>p.id!==id),emit()}},touch:emit,has:()=>pages.length>0,read,outline(){let top=pages[pages.length-1];if(!top)return null;let base=top.read(),sections=[...base.sections,...contributions().flatMap(c=>c.sections)];return{surfaceName:base.surfaceName??null,kind:base.kind,title:base.title,loadable:sections.filter(s=>typeof s.load=="function").map(s=>({id:s.id,title:s.title}))}},readFor(surfaceName){let capture=read();return capture&&capture.surfaceName===surfaceName?capture:null},subscribe(listener){return listeners.add(listener),()=>{listeners.delete(listener)}},version:()=>version}}var liveValues=createLiveValues();var PAGE_ACTION_STREAM="actions",valueStream=name=>`value:${name}`;function clone(value){if(value===void 0)return value;let sc=globalThis.structuredClone;return sc?sc(value):JSON.parse(JSON.stringify(value))}function errorOf(err,code="surface_failed"){return{ok:!1,error:{code,message:err instanceof Error&&err.message?err.message:String(err),retryable:!1}}}var sequence=0;function defaultMint(prefix){let c=globalThis.crypto;return c?.randomUUID?`${prefix}-${c.randomUUID()}`:`${prefix}-${Date.now().toString(36)}-${(sequence++).toString(36)}`}function createSurfacePort(opts){let mint=opts.mintId??defaultMint,pending=opts.pending??new Map,parked=new Set,values=new Map,mounted=null,disposed=!1,report=(error,stage,detail={})=>{try{opts.ports.diagnostics.capture(error,{area:"actions",detail:{stage:`surface.${stage}`,...detail}})}catch{}},memberships=async()=>{if(opts.organizationIds)try{return await opts.organizationIds()}catch(err){report(err,"organizationIds");return}},read=async name=>{let organizationIds=await memberships();return readSurfaceChain(opts.client,name,{...opts.maxDepth!==void 0?{maxDepth:opts.maxDepth}:{},...organizationIds?{organizationIds}:{}})},currentChain=async fresh=>{if(mounted&&!fresh)return{ok:!0,data:mounted.chain};let name=mounted?.name??(opts.surfaceName?await opts.surfaceName():null);if(!name)return{ok:!1,error:{code:"not_mounted",message:"No surface is mounted here yet.",retryable:!1}};let chain=await read(name);return chain.ok&&mounted?.name===name&&(mounted.chain=chain.data),chain},awaitPageAction=runId=>disposed?Promise.resolve({ok:!1,error:{code:"host_disposed",message:"This page has closed.",retryable:!1}}):new Promise(resolve=>{parked.add(runId),pending.set(runId,resolve)}),selfRouted={dispatch(name,input,context){let waiting=awaitPageAction(context.runId),event={action:name,input,runId:context.runId,by:context.by},forget=()=>{pending.delete(context.runId),parked.delete(context.runId)};if(disposed)return forget(),Promise.resolve({ok:!1,error:{code:"host_disposed",message:"This page closed, so the action did not run.",retryable:!1}});let heard;try{heard=opts.emit(PAGE_ACTION_STREAM,event)}catch(err){throw forget(),err}return heard!==!0?(forget(),Promise.resolve({ok:!1,error:{code:"action_unhandled",message:`No page is open to run "${name}".`,retryable:!1}})):waiting}},port={async mount(name){try{let chain=await read(name);if(!chain.ok)return chain;if(opts.seedValues)try{let seeds=await opts.seedValues();for(let[key,v]of Object.entries(seeds))values.has(key)||values.set(key,clone(v))}catch(err){report(err,"seedValues",{surface:name})}return mounted={mountId:mint("mount"),name,chain:chain.data},{ok:!0,data:{mountId:mounted.mountId}}}catch(err){return errorOf(err)}},async unmount(mountId){mounted?.mountId===mountId&&(mounted=null)},async getValue(name){if(values.has(name)||!opts.live||!mounted)return clone(values.get(name));try{let capture=opts.live.readFor(mounted.name);return capture?clone(liveValueOf(capture,name)):void 0}catch(err){report(err,"live",{name});return}},async setValue(name,v){let copy;try{copy=clone(v)}catch(err){report(err,"setValue",{name});return}values.set(name,copy);try{opts.emit(valueStream(name),clone(v))}catch(err){report(err,"emit",{stream:valueStream(name)})}},async listActions(){try{let chain=await currentChain(!1);return chain.ok?clone(chain.data):(report(chain.error,"listActions"),[])}catch(err){return report(err,"listActions"),[]}},async runAction(name,input,by){let info;try{let known=await currentChain(!1);if(info=known.ok?known.data.find(a=>a.name===name):void 0,!info){let fresh=await currentChain(!0);if(!fresh.ok)return fresh;info=fresh.data.find(a=>a.name===name)}}catch(err){return errorOf(err)}if(!info)return{ok:!1,error:{code:"action_not_found",message:`This page has no action named "${name}".`,retryable:!1}};let viewerId=null;try{viewerId=opts.viewerId?await opts.viewerId():null}catch(err){report(err,"viewerId")}let ports={...opts.ports,pageActions:opts.pageActions??selfRouted},result=await runSurfaceAction(info,input,by,ports,{runId:mint("act"),viewerId});return result.ok&&info.updatesValue&&await port.setValue(info.updatesValue,result.data),result},async completeAction(runId,result){let resolve=pending.get(runId);if(!resolve){report(new Error(`completeAction for run "${runId}", which is not waiting (already answered, never routed here, or the page closed).`),"completeAction",{runId});return}pending.delete(runId),parked.delete(runId),resolve?.(result)},awaitPageAction,dispose(){disposed=!0;for(let runId of parked){let resolve=pending.get(runId);pending.delete(runId),resolve?.({ok:!1,error:{code:"host_disposed",message:"This page closed before the action finished.",retryable:!1}})}parked.clear(),mounted=null,values.clear()}};return port}function createItemSources(onError){let sources=new Map;return{register(containerId,source){return sources.set(containerId,source),()=>{sources.get(containerId)===source&&sources.delete(containerId)}},resolve(target){let source=sources.get(target.containerId);if(!source)return null;try{return source(target)??null}catch(error){return onError?.(error,target),null}},has:containerId=>sources.has(containerId)}}var itemSources=createItemSources((error,target)=>{console.error(`[alchemy items] the source for "${target.containerId}" threw resolving ${target.level}`,error)});
|
|
2
2
|
//# sourceMappingURL=surface.cjs.map
|