1- /** @typedef {{ type: 'head' | 'body', content: string } } TNode */
2- /** @typedef {{ [key in TNode['type'] ]: string } } AccumulatedContent */
1+ /** @typedef {'head' | 'body' } PayloadType */
2+ /** @typedef {{ [key in PayloadType ]: string } } AccumulatedContent */
33/** @typedef {{ start: number, end: number, fn: (content: AccumulatedContent) => AccumulatedContent | Promise<AccumulatedContent> } } Compaction */
44
5- // TODO we test for `instanceof AsyncContentTree` in some tight loops -- we might optimize
6- // by giving the tree a symbol property and checking that instead if we can actually notice any impact
7-
85/**
9- * /**
10- * A base class for payloads. Payloads are basically a tree of `string | Payload`s, where each
11- * `Payload` in the tree represents work that may or may not have completed. A payload can be
12- * {@link collect}ed to aggregate the content from itself and all of its children, but this will
13- * throw if any of the children are performing asynchronous work. A payload can also be collected
14- * asynchronously with {@link collect_async}, which will wait for all children to complete before
15- * collecting their contents.
6+ * Payloads are basically a tree of `string | Payload`s, where each `Payload` in the tree represents
7+ * work that may or may not have completed. A payload can be {@link collect}ed to aggregate the
8+ * content from itself and all of its children, but this will throw if any of the children are
9+ * performing asynchronous work. A payload can also be collected asynchronously with
10+ * {@link collect_async}, which will wait for all children to complete before collecting their
11+ * contents.
12+ *
13+ * The `string` values within a payload are always associated with the {@link type} of that payload. To switch types,
14+ * call {@link child} with a different `type` argument.
1615 */
1716export class Payload {
1817 /**
19- * @type {TNode['type'] }
18+ * @type {PayloadType }
2019 */
2120 type ;
2221
@@ -25,7 +24,7 @@ export class Payload {
2524
2625 /**
2726 * The contents of the payload.
28- * @type {(TNode | Payload)[] }
27+ * @type {(string | Payload)[] }
2928 */
3029 out = [ ] ;
3130
@@ -55,7 +54,7 @@ export class Payload {
5554 * @param {TreeState } [global]
5655 * @param {{ select_value: string | undefined } } [local]
5756 * @param {Payload | undefined } [parent]
58- * @param {TNode['type'] } [type]
57+ * @param {PayloadType } [type]
5958 */
6059 constructor ( global = new TreeState ( ) , local = { select_value : undefined } , parent , type ) {
6160 this . global = global ;
@@ -69,7 +68,7 @@ export class Payload {
6968 * but has its own `out` array and `promise` property. The child payload is automatically
7069 * inserted into the parent payload's `out` array.
7170 * @param {(tree: Payload) => void | Promise<void> } render
72- * @param {TNode['type'] } [type]
71+ * @param {PayloadType } [type]
7372 * @returns {void }
7473 */
7574 child ( render , type ) {
@@ -81,13 +80,9 @@ export class Payload {
8180 }
8281 }
8382
84- /**
85- * This is a convenience function that allows pushing strings, and will automatically use the configured `type` of this
86- * payload. It's fine to push content of a different type to the `out` array; it's just more annoying to write.
87- * @param {string } content
88- */
83+ /** @param {string } content */
8984 push ( content ) {
90- this . out . push ( { type : this . type , content } ) ;
85+ this . out . push ( content ) ;
9186 }
9287
9388 /**
@@ -100,25 +95,24 @@ export class Payload {
10095 const to_compact = this . out . splice ( start , end - start , child ) ;
10196 const promises = Payload . #collect_promises( to_compact , [ ] ) ;
10297
103- /** @param { AccumulatedContent | Promise<AccumulatedContent> } res */
104- const push_result = ( res ) => {
98+ const push_result = ( ) => {
99+ const res = fn ( Payload . #collect_content ( to_compact , this . type ) ) ;
105100 if ( res instanceof Promise ) {
106- child . promise = res . then ( ( resolved ) => {
101+ const promise = res . then ( ( resolved ) => {
107102 Payload . #push_accumulated_content( child , resolved ) ;
108103 } ) ;
104+ return promise ;
109105 } else {
110106 Payload . #push_accumulated_content( child , res ) ;
111107 }
112108 } ;
113109
114110 if ( promises . length > 0 ) {
115- // we have to wait for the accumulated work associated with all branches to complete,
111+ // we have to wait for the accumulated work associated with all pruned branches to complete,
116112 // then we can accumulate their content to compact it.
117- child . promise = Promise . all ( promises )
118- . then ( ( ) => fn ( Payload . #collect_content( to_compact ) ) )
119- . then ( push_result ) ;
113+ child . promise = Promise . all ( promises ) . then ( push_result ) ;
120114 } else {
121- push_result ( fn ( Payload . #collect_content ( to_compact ) ) ) ;
115+ push_result ( ) ;
122116 }
123117 }
124118
@@ -136,26 +130,27 @@ export class Payload {
136130 async collect_async ( ) {
137131 // TODO: Should probably use `Promise.allSettled` here just so we can report detailed errors
138132 await Promise . all ( Payload . #collect_promises( this . out , this . promise ? [ this . promise ] : [ ] ) ) ;
139- return Payload . #collect_content( this . out ) ;
133+ return Payload . #collect_content( this . out , this . type ) ;
140134 }
141135
142136 /**
143- * Collect all of the code from the `out` array and return it as a string.
137+ * Collect all of the code from the `out` array and return it as a string. Throws if any of the children are
138+ * performing asynchronous work.
144139 * @returns {AccumulatedContent }
145140 */
146141 collect ( ) {
147142 const promises = Payload . #collect_promises( this . out , this . promise ? [ this . promise ] : [ ] ) ;
148143 if ( promises . length > 0 ) {
149- // TODO is there a good way to report where this is? Probably by using some sort of loc or stack trace in `child` creation
144+ // TODO is there a good way to report where this is? Probably by using some sort of loc or stack trace in `child` creation.
150145 throw new Error ( 'Encountered an asynchronous component while rendering synchronously' ) ;
151146 }
152147
153- return Payload . #collect_content( this . out ) ;
148+ return Payload . #collect_content( this . out , this . type ) ;
154149 }
155150
156151 copy ( ) {
157152 const copy = new Payload ( this . global , this . local , this . parent , this . type ) ;
158- copy . out = this . out . map ( ( item ) => ( item instanceof Payload ? item . copy ( ) : item ) ) ;
153+ copy . out = this . out . map ( ( item ) => ( typeof item === 'string' ? item : item . copy ( ) ) ) ;
159154 copy . promise = this . promise ;
160155 return copy ;
161156 }
@@ -167,7 +162,7 @@ export class Payload {
167162 this . global . subsume ( other . global ) ;
168163 this . local = other . local ;
169164 this . out = other . out . map ( ( item ) => {
170- if ( item instanceof Payload ) {
165+ if ( typeof item !== 'string' ) {
171166 item . subsume ( item ) ;
172167 }
173168 return item ;
@@ -177,34 +172,34 @@ export class Payload {
177172 }
178173
179174 /**
180- * @param {(TNode | Payload)[] } items
175+ * @param {(string | Payload)[] } items
181176 * @param {Promise<void>[] } promises
182177 * @returns {Promise<void>[] }
183178 */
184179 static #collect_promises( items , promises ) {
185180 for ( const item of items ) {
186- if ( item instanceof Payload ) {
187- if ( item . promise ) {
188- promises . push ( item . promise ) ;
189- }
190- Payload . #collect_promises( item . out , promises ) ;
181+ if ( typeof item === 'string' ) continue ;
182+ if ( item . promise ) {
183+ promises . push ( item . promise ) ;
191184 }
185+ Payload . #collect_promises( item . out , promises ) ;
192186 }
193187 return promises ;
194188 }
195189
196190 /**
197191 * Collect all of the code from the `out` array and return it as a string.
198- * @param {(TNode | Payload)[] } items
192+ * @param {(string | Payload)[] } items
193+ * @param {PayloadType } current_type
199194 * @param {AccumulatedContent } content
200195 * @returns {AccumulatedContent }
201196 */
202- static #collect_content( items , content = { head : '' , body : '' } ) {
197+ static #collect_content( items , current_type , content = { head : '' , body : '' } ) {
203198 for ( const item of items ) {
204- if ( item instanceof Payload ) {
205- Payload . #collect_content ( item . out , content ) ;
199+ if ( typeof item === 'string' ) {
200+ content [ current_type ] += item ;
206201 } else {
207- content [ item . type ] += item . content ;
202+ Payload . #collect_content ( item . out , item . type , content ) ;
208203 }
209204 }
210205 return content ;
@@ -216,7 +211,10 @@ export class Payload {
216211 */
217212 static #push_accumulated_content( tree , accumulated_content ) {
218213 for ( const [ type , content ] of Object . entries ( accumulated_content ) ) {
219- tree . out . push ( { type : /** @type {TNode['type'] } */ ( type ) , content } ) ;
214+ if ( ! content ) continue ;
215+ const child = new Payload ( tree . global , tree . local , tree , /** @type {PayloadType } */ ( type ) ) ;
216+ child . push ( content ) ;
217+ tree . out . push ( child ) ;
220218 }
221219 }
222220}
0 commit comments