@@ -5,17 +5,22 @@ import type { Simplify } from "effect/Types";
55import { PlanReviewer , type PlanRejected } from "./approve.ts" ;
66import type { AnyBinding , BindingService } from "./binding.ts" ;
77import type { ApplyEvent , ApplyStatus } from "./event.ts" ;
8+ import * as Output from "./output.ts" ;
89import {
910 plan ,
1011 type BindNode ,
1112 type Create ,
1213 type CRUD ,
1314 type Delete ,
15+ type IPlan ,
1416 type Plan ,
1517 type Update ,
18+ type DerivePlan ,
19+ type BindingTags ,
1620} from "./plan.ts" ;
17- import type { Resource } from "./resource.ts" ;
18- import type { Service } from "./service.ts" ;
21+ import type { Instance } from "./policy.ts" ;
22+ import type { AnyResource , Resource } from "./resource.ts" ;
23+ import type { AnyService , Service } from "./service.ts" ;
1924import { State } from "./state.ts" ;
2025
2126export interface PlanStatusSession {
@@ -30,40 +35,45 @@ export interface ScopedPlanStatusSession extends PlanStatusSession {
3035export class PlanStatusReporter extends Context . Tag ( "PlanStatusReporter" ) <
3136 PlanStatusReporter ,
3237 {
33- start ( plan : Plan ) : Effect . Effect < PlanStatusSession , never > ;
38+ start ( plan : IPlan ) : Effect . Effect < PlanStatusSession , never > ;
3439 }
3540> ( ) { }
3641
37- export const apply : typeof applyPlan &
38- typeof applyResources &
39- typeof applyResourcesPhase = ( ...args : any [ ] ) : any =>
40- Effect . isEffect ( args [ 0 ] )
41- ? applyPlan ( args [ 0 ] as any )
42- : args . length === 1 && "phase" in args [ 0 ]
43- ? applyResourcesPhase ( args [ 0 ] )
44- : applyResources ( ...args ) ;
45-
46- export const applyResourcesPhase = <
47- const Phase extends "update" | "destroy" ,
48- const Resources extends ( Service | Resource ) [ ] ,
49- > ( props : {
50- resources : Resources ;
51- phase : Phase ;
52- } ) => applyPlan ( plan ( props ) ) ;
53-
54- export const applyResources = < const Resources extends ( Service | Resource ) [ ] > (
42+ export type ApplyEffect <
43+ P extends IPlan ,
44+ Err = never ,
45+ Req = never ,
46+ > = Effect . Effect <
47+ {
48+ [ k in keyof AppliedPlan < P > ] : AppliedPlan < P > [ k ] ;
49+ } ,
50+ Err | PlanRejected ,
51+ Req
52+ > ;
53+
54+ export type AppliedPlan < P extends IPlan > = {
55+ [ id in keyof P [ "resources" ] ] : P [ "resources" ] [ id ] extends
56+ | Delete < Resource >
57+ | undefined
58+ | never
59+ ? never
60+ : Simplify < P [ "resources" ] [ id ] [ "resource" ] [ "attr" ] > ;
61+ } ;
62+
63+ export const apply = <
64+ const Resources extends ( AnyService | AnyResource ) [ ] = never ,
65+ > (
5566 ...resources : Resources
56- ) =>
57- applyPlan (
58- plan ( {
59- phase : "update" ,
60- resources,
61- } ) ,
62- ) ;
63-
64- export const applyPlan = < P extends Plan , Err , Req > (
67+ ) : ApplyEffect <
68+ DerivePlan < Instance < Resources [ number ] > > ,
69+ never ,
70+ State | BindingTags < Instance < Resources [ number ] > >
71+ // TODO(sam): don't cast to any
72+ > => applyPlan ( plan ( ...resources ) ) as any ;
73+
74+ export const applyPlan = < P extends IPlan , Err = never , Req = never > (
6575 plan : Effect . Effect < P , Err , Req > ,
66- ) =>
76+ ) : ApplyEffect < P , Err , Req > =>
6777 plan . pipe (
6878 Effect . flatMap ( ( plan ) =>
6979 Effect . gen ( function * ( ) {
@@ -90,6 +100,18 @@ export const applyPlan = <P extends Plan, Err, Req>(
90100 ) : Effect . Effect < T , Err , Req > =>
91101 Effect . isEffect ( effect ) ? effect : Effect . succeed ( effect ) ;
92102
103+ const resolveUpstream = Effect . fn ( function * ( resourceId : string ) {
104+ const upstreamNode = plan . resources [ resourceId ] ;
105+ const upstreamAttr = upstreamNode
106+ ? yield * apply ( upstreamNode )
107+ : yield * Effect . dieMessage ( `Resource ${ resourceId } not found` ) ;
108+ return {
109+ resourceId,
110+ upstreamAttr,
111+ upstreamNode,
112+ } ;
113+ } ) ;
114+
93115 const resolveBindingUpstream = Effect . fn ( function * ( {
94116 node,
95117 resource,
@@ -104,10 +126,8 @@ export const applyPlan = <P extends Plan, Err, Req>(
104126 const provider = yield * binding . Tag ;
105127
106128 const resourceId : string = node . binding . capability . resource . id ;
107- const upstreamNode = plan . resources [ resourceId ] ;
108- const upstreamAttr = resource
109- ? yield * apply ( upstreamNode )
110- : yield * Effect . dieMessage ( `Resource ${ resourceId } not found` ) ;
129+ const { upstreamAttr, upstreamNode } =
130+ yield * resolveUpstream ( resourceId ) ;
111131
112132 return {
113133 resourceId,
@@ -221,23 +241,21 @@ export const applyPlan = <P extends Plan, Err, Req>(
221241 node ,
222242 ) =>
223243 Effect . gen ( function * ( ) {
224- const checkpoint = < Out , Err > (
225- effect : Effect . Effect < Out , Err , never > ,
226- ) => effect . pipe ( Effect . flatMap ( ( output ) => saveState ( { output } ) ) ) ;
227-
228244 const saveState = < Output > ( {
229245 output,
230246 bindings = node . bindings ,
247+ news,
231248 } : {
232249 output : Output ;
233250 bindings ?: BindNode [ ] ;
251+ news : any ;
234252 } ) =>
235253 state
236254 . set ( node . resource . id , {
237255 id : node . resource . id ,
238256 type : node . resource . type ,
239257 status : node . action === "create" ? "created" : "updated" ,
240- props : node . resource . props ,
258+ props : news ,
241259 output,
242260 bindings,
243261 } )
@@ -271,18 +289,33 @@ export const applyPlan = <P extends Plan, Err, Req>(
271289 attr,
272290 phase,
273291 } : {
274- node : Create < Resource > | Update < Resource > ;
292+ node : Create | Update ;
275293 attr : any ;
276294 phase : "create" | "update" ;
277295 } ) {
296+ const upstream = Object . fromEntries (
297+ yield * Effect . all (
298+ Object . entries ( Output . resolveUpstream ( node . news ) ) . map (
299+ ( [ id , resource ] ) =>
300+ resolveUpstream ( id ) . pipe (
301+ Effect . map ( ( { upstreamAttr } ) => [
302+ id ,
303+ upstreamAttr ,
304+ ] ) ,
305+ ) ,
306+ ) ,
307+ ) ,
308+ ) ;
309+ const news = yield * Output . evaluate ( node . news , upstream ) ;
310+
278311 yield * report ( phase === "create" ? "creating" : "updating" ) ;
279312
280313 let bindingOutputs = yield * attachBindings ( {
281314 resource,
282315 bindings : node . bindings ,
283316 target : {
284317 id,
285- props : node . news ,
318+ props : news ,
286319 attr,
287320 } ,
288321 } ) ;
@@ -293,7 +326,7 @@ export const applyPlan = <P extends Plan, Err, Req>(
293326 : node . provider . update
294327 ) ( {
295328 id,
296- news : node . news ,
329+ news,
297330 bindings : bindingOutputs ,
298331 session : scopedSession ,
299332 ...( node . action === "update"
@@ -316,12 +349,13 @@ export const applyPlan = <P extends Plan, Err, Req>(
316349 bindingOutputs,
317350 target : {
318351 id,
319- props : node . news ,
352+ props : news ,
320353 attr,
321354 } ,
322355 } ) ;
323356
324357 yield * saveState ( {
358+ news,
325359 output,
326360 bindings : node . bindings . map ( ( binding , i ) => ( {
327361 ...binding ,
@@ -364,11 +398,7 @@ export const applyPlan = <P extends Plan, Err, Req>(
364398 yield * Effect . all (
365399 node . downstream . map ( ( dep ) =>
366400 dep in plan . resources
367- ? apply (
368- plan . resources [
369- dep
370- ] as P [ "resources" ] [ keyof P [ "resources" ] ] ,
371- )
401+ ? apply ( plan . resources [ dep ] as any )
372402 : Effect . void ,
373403 ) ,
374404 ) ;
@@ -399,29 +429,30 @@ export const applyPlan = <P extends Plan, Err, Req>(
399429 } ) ;
400430 const create = Effect . gen ( function * ( ) {
401431 yield * report ( "creating" ) ;
402- return yield * (
403- node . provider
404- . create ( {
405- id,
406- news : node . news ,
407- // TODO(sam): these need to only include attach actions
408- bindings : yield * attachBindings ( {
409- resource,
410- bindings : node . bindings ,
411- target : {
412- id,
413- props : node . news ,
414- attr : node . attributes ,
415- } ,
416- } ) ,
417- session : scopedSession ,
418- } )
419- // TODO(sam): delete and create will conflict here, we need to extend the state store for replace
420- . pipe (
421- checkpoint ,
422- Effect . tap ( ( ) => report ( "created" ) ) ,
423- )
424- ) ;
432+
433+ // TODO(sam): delete and create will conflict here, we need to extend the state store for replace
434+ return yield * node . provider
435+ . create ( {
436+ id,
437+ news : node . news ,
438+ // TODO(sam): these need to only include attach actions
439+ bindings : yield * attachBindings ( {
440+ resource,
441+ bindings : node . bindings ,
442+ target : {
443+ id,
444+ // TODO(sam): resolve the news
445+ props : node . news ,
446+ attr : node . attributes ,
447+ } ,
448+ } ) ,
449+ session : scopedSession ,
450+ } )
451+ . pipe (
452+ Effect . tap ( ( output ) =>
453+ saveState ( { news : node . news , output } ) ,
454+ ) ,
455+ ) ;
425456 } ) ;
426457 if ( ! node . deleteFirst ) {
427458 yield * destroy ;
@@ -457,20 +488,4 @@ export const applyPlan = <P extends Plan, Err, Req>(
457488 return resources ;
458489 } ) ,
459490 ) ,
460- ) as Effect . Effect <
461- "update" extends P [ "phase" ]
462- ?
463- | {
464- [ id in keyof P [ "resources" ] ] : P [ "resources" ] [ id ] extends
465- | Delete < Resource >
466- | undefined
467- | never
468- ? never
469- : Simplify < P [ "resources" ] [ id ] [ "resource" ] [ "attr" ] > ;
470- }
471- // union distribution isn't happening, so we gotta add this additional void here just in case
472- | ( "destroy" extends P [ "phase" ] ? void : never )
473- : void ,
474- Err | PlanRejected ,
475- Req
476- > ;
491+ ) as ApplyEffect < P > ;
0 commit comments