foldkit 0.102.0 → 0.103.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,5 +1,5 @@
1
1
  export { tag, ResourceNotAvailable } from './index.js';
2
2
  export type { ManagedResource, ManagedResourceService, Value, ServiceOf, } from './index.js';
3
- export { makeManagedResources } from '../runtime/managedResource.js';
4
- export type { ManagedResourceConfig, ManagedResources, ManagedResourceServicesOf, } from '../runtime/managedResource.js';
3
+ export { make, lift, aggregate } from '../runtime/managedResource.js';
4
+ export type { Entry, ManagedResources, ManagedResourceConfig, ServicesOf, } from '../runtime/managedResource.js';
5
5
  //# sourceMappingURL=public.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/managedResource/public.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAEtD,YAAY,EACV,eAAe,EACf,sBAAsB,EACtB,KAAK,EACL,SAAS,GACV,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAA;AAEpE,YAAY,EACV,qBAAqB,EACrB,gBAAgB,EAChB,yBAAyB,GAC1B,MAAM,+BAA+B,CAAA"}
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/managedResource/public.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAEtD,YAAY,EACV,eAAe,EACf,sBAAsB,EACtB,KAAK,EACL,SAAS,GACV,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAA;AAErE,YAAY,EACV,KAAK,EACL,gBAAgB,EAChB,qBAAqB,EACrB,UAAU,GACX,MAAM,+BAA+B,CAAA"}
@@ -1,2 +1,2 @@
1
1
  export { tag, ResourceNotAvailable } from './index.js';
2
- export { makeManagedResources } from '../runtime/managedResource.js';
2
+ export { make, lift, aggregate } from '../runtime/managedResource.js';
@@ -1,33 +1,88 @@
1
- import { type Effect, type Option, type Schema } from 'effect';
1
+ import { type Effect, Option, type Schema } from 'effect';
2
2
  import type { ManagedResource } from '../managedResource/index.js';
3
- /** Internal configuration for a single managed resource, used by the runtime. */
3
+ /** Internal configuration for a single Managed Resource, used by the runtime. */
4
4
  export type ManagedResourceConfig<Model, Message> = {
5
5
  readonly schema: Schema.Schema<any>;
6
6
  readonly resource: ManagedResource<any>;
7
- readonly modelToMaybeRequirements: (model: Model) => unknown;
8
- readonly acquire: (params: unknown) => Effect.Effect<unknown, unknown>;
9
- readonly release: (value: unknown) => Effect.Effect<void>;
10
- readonly onAcquired: (value: unknown) => Message;
7
+ readonly modelToMaybeRequirements: (model: Model) => any;
8
+ readonly acquire: (params: any) => Effect.Effect<any, unknown>;
9
+ readonly release: (value: any) => Effect.Effect<void>;
10
+ readonly onAcquired: (value: any) => Message;
11
11
  readonly onReleased: () => Message;
12
12
  readonly onAcquireError: (error: unknown) => Message;
13
13
  };
14
- declare const ManagedResourceServicesPhantom: unique symbol;
15
- /** A record of named managed resource configurations, keyed by dependency field name. */
14
+ /** A record of named Managed Resource configurations, keyed by resource name. */
16
15
  export type ManagedResources<Model, Message, Services = never> = Record<string, ManagedResourceConfig<Model, Message>> & {
17
- readonly [ManagedResourceServicesPhantom]?: Services;
16
+ readonly __managedResourceServices?: Services;
18
17
  };
19
- /** Type-level utility to extract the service union from a ManagedResources type. */
20
- export type ManagedResourceServicesOf<MR> = MR extends ManagedResources<any, any, infer S> ? S : never;
18
+ type EntryBrand = {
19
+ readonly __managedResourceEntry: never;
20
+ };
21
+ /**
22
+ * The requirements value the runtime hands to `acquire`. When the requirements
23
+ * schema is wrapped in `S.Option`, the runtime unwraps the `Some` before
24
+ * calling `acquire`, so the parameter is the inner type.
25
+ */
26
+ type AcquireParams<Requirements> = Requirements extends Option.Option<infer Params> ? Params : Requirements;
27
+ /**
28
+ * A single Managed Resource entry produced by `ManagedResource.make`,
29
+ * `ManagedResource.lift`, or `ManagedResource.aggregate`. The brand field is
30
+ * `never`, so application code cannot manually construct one: it must go
31
+ * through a constructor.
32
+ *
33
+ * The `Service` parameter carries the resource tag's identity so `make`,
34
+ * `lift`, and `aggregate` can union the services a record requires. Read the
35
+ * union off a finished record with `ManagedResource.ServicesOf`.
36
+ */
37
+ export type Entry<Model, Message, Requirements, Service = unknown> = {
38
+ readonly schema: Schema.Schema<Requirements>;
39
+ readonly resource: ManagedResource<any, Service>;
40
+ readonly modelToMaybeRequirements: (model: Model) => Requirements;
41
+ readonly acquire: (params: AcquireParams<Requirements>) => Effect.Effect<any, unknown>;
42
+ readonly release: (value: any) => Effect.Effect<void>;
43
+ readonly onAcquired: (value: any) => Message;
44
+ readonly onReleased: () => Message;
45
+ readonly onAcquireError: (error: unknown) => Message;
46
+ } & EntryBrand;
47
+ /** Type-level utility to extract the service union from a Managed Resources record. */
48
+ export type ServicesOf<Resources> = {
49
+ [Key in keyof Resources]: Resources[Key] extends {
50
+ readonly resource: ManagedResource<any, infer Service>;
51
+ } ? Service : never;
52
+ }[keyof Resources];
53
+ /**
54
+ * Builds a single Managed Resource entry from a requirements schema and a
55
+ * config. Reading the schema as a positional argument (rather than a property
56
+ * on the config literal) lets TypeScript fully resolve the requirements type
57
+ * before contextually typing `modelToMaybeRequirements` and `acquire`, so
58
+ * destructuring patterns are inferred correctly even when the schema uses
59
+ * transforms like `S.Option`.
60
+ */
61
+ export type EntryBuilder<Model, Message> = <RequirementsSchema extends Schema.Schema<any>, Service>(schema: RequirementsSchema, config: {
62
+ readonly resource: ManagedResource<any, Service>;
63
+ readonly modelToMaybeRequirements: (model: Model) => Schema.Schema.Type<RequirementsSchema>;
64
+ readonly acquire: (params: AcquireParams<Schema.Schema.Type<RequirementsSchema>>) => Effect.Effect<any, unknown>;
65
+ readonly release: (value: any) => Effect.Effect<void>;
66
+ readonly onAcquired: (value: any) => Message;
67
+ readonly onReleased: () => Message;
68
+ readonly onAcquireError: (error: unknown) => Message;
69
+ }) => Entry<Model, Message, Schema.Schema.Type<RequirementsSchema>, Service>;
21
70
  /**
22
- * Creates type-safe managed resource configurations from a dependency schema.
71
+ * Declares a Managed Resources record. The Model and Message generics are
72
+ * provided up front; the entries record follows, built from calls to the
73
+ * `entry` builder passed into the inner function.
23
74
  *
24
75
  * Use this when a resource is expensive or stateful and should only exist while
25
- * the model is in a particular state a camera stream during a video call, a
76
+ * the model is in a particular state: a camera stream during a video call, a
26
77
  * WebSocket connection while on a chat page, or a Web Worker pool during a
27
78
  * computation. For resources that live for the entire application lifetime, use
28
79
  * the static `resources` config instead.
29
80
  *
30
- * **Lifecycle** The runtime watches each config's `modelToMaybeRequirements`
81
+ * Reach for `ManagedResource.aggregate` to combine multiple records, and
82
+ * `ManagedResource.lift` to translate a child Submodel's record into a parent
83
+ * context.
84
+ *
85
+ * **Lifecycle** — The runtime watches each entry's `modelToMaybeRequirements`
31
86
  * after every model update, structurally comparing the result against the
32
87
  * previous value:
33
88
  *
@@ -39,8 +94,8 @@ export type ManagedResourceServicesOf<MR> = MR extends ManagedResources<any, any
39
94
  * dispatches `onReleased()`. No re-acquisition occurs.
40
95
  *
41
96
  * If `acquire` fails, `onAcquireError` is dispatched and the resource daemon
42
- * continues watching for the next deps change a failed acquisition does not
43
- * crash the application.
97
+ * continues watching for the next requirements change: a failed acquisition
98
+ * does not crash the application.
44
99
  *
45
100
  * **Config fields:**
46
101
  *
@@ -49,13 +104,12 @@ export type ManagedResourceServicesOf<MR> = MR extends ManagedResources<any, any
49
104
  * - `modelToMaybeRequirements` — Extracts requirements from the model.
50
105
  * `Option.none()` means "release", `Option.some(params)` means
51
106
  * "acquire/re-acquire if params changed". For resources with no
52
- * parameters, use `S.Option(S.Null)` and return `Option.some(null)` —
53
- * not `S.Struct({})`, which has no fields for equivalence comparison.
107
+ * parameters, use `S.Option(S.Null)` and return `Option.some(null)`.
54
108
  * - `acquire` — Creates the resource from the unwrapped params. The returned
55
- * Effect should fail when acquisition fails errors in the error channel
109
+ * Effect should fail when acquisition fails: errors in the error channel
56
110
  * flow to `onAcquireError` as a message instead of crashing the runtime.
57
111
  * - `release` — Tears down the resource. Errors thrown here are silently
58
- * swallowed release must not block cleanup.
112
+ * swallowed: release must not block cleanup.
59
113
  * - `onAcquired` — Message dispatched when `acquire` succeeds.
60
114
  * - `onAcquireError` — Message dispatched when `acquire` fails.
61
115
  * - `onReleased` — Message dispatched after `release` completes.
@@ -64,14 +118,8 @@ export type ManagedResourceServicesOf<MR> = MR extends ManagedResources<any, any
64
118
  * ```ts
65
119
  * const CameraStream = ManagedResource.tag<MediaStream>()('CameraStream')
66
120
  *
67
- * const ManagedResourceDeps = S.Struct({
68
- * camera: S.Option(S.Struct({ facingMode: S.String })),
69
- * })
70
- *
71
- * const managedResources = ManagedResource.makeManagedResources(
72
- * ManagedResourceDeps,
73
- * )<Model, Message>({
74
- * camera: {
121
+ * const managedResources = ManagedResource.make<Model, Message>()(entry => ({
122
+ * camera: entry(S.Option(S.Struct({ facingMode: S.String })), {
75
123
  * resource: CameraStream,
76
124
  * modelToMaybeRequirements: model =>
77
125
  * pipe(
@@ -91,24 +139,37 @@ export type ManagedResourceServicesOf<MR> = MR extends ManagedResources<any, any
91
139
  * onAcquired: () => AcquiredCamera(),
92
140
  * onAcquireError: error => FailedAcquireCamera({ error: String(error) }),
93
141
  * onReleased: () => ReleasedCamera(),
94
- * },
95
- * })
142
+ * }),
143
+ * }))
96
144
  * ```
97
145
  *
98
- * @param ManagedResourceDeps - An Effect Schema struct where each field's type
99
- * drives the requirements for one managed resource. Wrap in `S.Option(...)` for
100
- * resources that can be released (most cases).
101
- *
102
146
  * @see {@link ManagedResource.tag} for creating the resource identity.
103
147
  */
104
- export declare const makeManagedResources: <ManagedResourceDeps extends Schema.Struct<any>>(ManagedResourceDeps: ManagedResourceDeps) => <Model, Message>(configs: { [K in keyof Schema.Schema.Type<ManagedResourceDeps>]: {
105
- readonly resource: ManagedResource<any, any>;
106
- readonly modelToMaybeRequirements: (model: Model) => Schema.Schema.Type<ManagedResourceDeps>[K];
107
- readonly acquire: (params: Schema.Schema.Type<ManagedResourceDeps>[K] extends Option.Option<infer P> ? P : Schema.Schema.Type<ManagedResourceDeps>[K]) => Effect.Effect<any, unknown>;
108
- readonly release: (value: any) => Effect.Effect<void>;
109
- readonly onAcquired: (value: any) => Message;
110
- readonly onReleased: () => Message;
111
- readonly onAcquireError: (error: unknown) => Message;
112
- }; }) => ManagedResources<Model, Message>;
148
+ export declare const make: <Model, Message>() => <Entries extends Record<string, Entry<Model, Message, any, any>>>(build: (entry: EntryBuilder<Model, Message>) => Entries) => Entries;
149
+ type ChildModelOf<Resources> = Resources[keyof Resources] extends Entry<infer ChildModel, any, any, any> ? ChildModel : never;
150
+ type ChildMessageOf<Resources> = Resources[keyof Resources] extends Entry<any, infer ChildMessage, any, any> ? ChildMessage : never;
151
+ /**
152
+ * Lifts a record of child Managed Resources into a parent's Model and Message
153
+ * context, applying a Model accessor and a Message wrapper uniformly to every
154
+ * entry. Per-entry requirements schemas and resource services are preserved.
155
+ *
156
+ * Unlike `Subscription.lift`, `toChildModel` returns an `Option`: a managed
157
+ * resource already speaks in `Option` (`modelToMaybeRequirements` returns
158
+ * `Option.none()` to release), and a child Submodel that owns a managed
159
+ * resource is itself something that mounts and unmounts. A missing child is
160
+ * just another `None` and flows through the same acquire/release channel, so
161
+ * each lifted entry's requirements must be `S.Option`-wrapped.
162
+ */
163
+ export declare const lift: <Resources extends Record<string, Entry<any, any, Option.Option<any>, any>>>(resources: Resources) => <ParentModel, ParentMessage>(config: {
164
+ readonly toChildModel: (parentModel: ParentModel) => Option.Option<ChildModelOf<Resources>>;
165
+ readonly toParentMessage: (message: ChildMessageOf<Resources>) => ParentMessage;
166
+ }) => { readonly [Key in keyof Resources]: Resources[Key] extends Entry<any, any, infer Requirements, infer Service> ? Entry<ParentModel, ParentMessage, Requirements, Service> : never; };
167
+ type MergeRecords<Records extends ReadonlyArray<unknown>> = Records extends readonly [infer Head, ...infer Rest] ? Head & (Rest extends ReadonlyArray<unknown> ? MergeRecords<Rest> : {}) : {};
168
+ /**
169
+ * Combines multiple Managed Resources records into one. Throws on duplicate
170
+ * keys so a misconfigured aggregate fails loudly at startup rather than
171
+ * silently overriding.
172
+ */
173
+ export declare const aggregate: <Model, Message>() => <Records extends ReadonlyArray<Record<string, Entry<Model, Message, any, any>>>>(...records: Records) => MergeRecords<Records>;
113
174
  export {};
114
175
  //# sourceMappingURL=managedResource.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"managedResource.d.ts","sourceRoot":"","sources":["../../src/runtime/managedResource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,MAAM,EAAU,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAA;AAEtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAElE,iFAAiF;AACjF,MAAM,MAAM,qBAAqB,CAAC,KAAK,EAAE,OAAO,IAAI;IAClD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IACnC,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC,GAAG,CAAC,CAAA;IACvC,QAAQ,CAAC,wBAAwB,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAA;IAC5D,QAAQ,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;IACtE,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACzD,QAAQ,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAA;IAChD,QAAQ,CAAC,UAAU,EAAE,MAAM,OAAO,CAAA;IAClC,QAAQ,CAAC,cAAc,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAA;CACrD,CAAA;AAED,OAAO,CAAC,MAAM,8BAA8B,EAAE,OAAO,MAAM,CAAA;AAE3D,yFAAyF;AACzF,MAAM,MAAM,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,GAAG,KAAK,IAAI,MAAM,CACrE,MAAM,EACN,qBAAqB,CAAC,KAAK,EAAE,OAAO,CAAC,CACtC,GAAG;IACF,QAAQ,CAAC,CAAC,8BAA8B,CAAC,CAAC,EAAE,QAAQ,CAAA;CACrD,CAAA;AAED,oFAAoF;AACpF,MAAM,MAAM,yBAAyB,CAAC,EAAE,IACtC,EAAE,SAAS,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkFG;AACH,eAAO,MAAM,oBAAoB,GAC9B,mBAAmB,SAAS,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,EAC7C,qBAAqB,mBAAmB,MAEzC,KAAK,EAAE,OAAO,EAAE,SAAS,GACvB,CAAC,IAAI,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,GAAG;IACpD,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IAC5C,QAAQ,CAAC,wBAAwB,EAAE,CACjC,KAAK,EAAE,KAAK,KACT,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAA;IAC/C,QAAQ,CAAC,OAAO,EAAE,CAChB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,CACtE,MAAM,CAAC,CACR,GACG,CAAC,GACD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAC3C,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IAChC,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACrD,QAAQ,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,CAAA;IAC5C,QAAQ,CAAC,UAAU,EAAE,MAAM,OAAO,CAAA;IAClC,QAAQ,CAAC,cAAc,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAA;CACrD,GACF,KAAG,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAUI,CAAA"}
1
+ {"version":3,"file":"managedResource.d.ts","sourceRoot":"","sources":["../../src/runtime/managedResource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,EAAU,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAA;AAEjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAElE,iFAAiF;AACjF,MAAM,MAAM,qBAAqB,CAAC,KAAK,EAAE,OAAO,IAAI;IAClD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IACnC,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC,GAAG,CAAC,CAAA;IACvC,QAAQ,CAAC,wBAAwB,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,GAAG,CAAA;IACxD,QAAQ,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IAC9D,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACrD,QAAQ,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,CAAA;IAC5C,QAAQ,CAAC,UAAU,EAAE,MAAM,OAAO,CAAA;IAClC,QAAQ,CAAC,cAAc,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAA;CACrD,CAAA;AAED,iFAAiF;AACjF,MAAM,MAAM,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,GAAG,KAAK,IAAI,MAAM,CACrE,MAAM,EACN,qBAAqB,CAAC,KAAK,EAAE,OAAO,CAAC,CACtC,GAAG;IACF,QAAQ,CAAC,yBAAyB,CAAC,EAAE,QAAQ,CAAA;CAC9C,CAAA;AAED,KAAK,UAAU,GAAG;IAChB,QAAQ,CAAC,sBAAsB,EAAE,KAAK,CAAA;CACvC,CAAA;AAED;;;;GAIG;AACH,KAAK,aAAa,CAAC,YAAY,IAC7B,YAAY,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,MAAM,CAAC,GAAG,MAAM,GAAG,YAAY,CAAA;AAE1E;;;;;;;;;GASG;AACH,MAAM,MAAM,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,GAAG,OAAO,IAAI;IACnE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA;IAC5C,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IAChD,QAAQ,CAAC,wBAAwB,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,YAAY,CAAA;IACjE,QAAQ,CAAC,OAAO,EAAE,CAChB,MAAM,EAAE,aAAa,CAAC,YAAY,CAAC,KAChC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IAChC,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACrD,QAAQ,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,CAAA;IAC5C,QAAQ,CAAC,UAAU,EAAE,MAAM,OAAO,CAAA;IAClC,QAAQ,CAAC,cAAc,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAA;CACrD,GAAG,UAAU,CAAA;AAEd,uFAAuF;AACvF,MAAM,MAAM,UAAU,CAAC,SAAS,IAAI;KACjC,GAAG,IAAI,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,CAAC,SAAS;QAC/C,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC,GAAG,EAAE,MAAM,OAAO,CAAC,CAAA;KACvD,GACG,OAAO,GACP,KAAK;CACV,CAAC,MAAM,SAAS,CAAC,CAAA;AAElB;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,CAAC,KAAK,EAAE,OAAO,IAAI,CACzC,kBAAkB,SAAS,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,EAC7C,OAAO,EAEP,MAAM,EAAE,kBAAkB,EAC1B,MAAM,EAAE;IACN,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IAChD,QAAQ,CAAC,wBAAwB,EAAE,CACjC,KAAK,EAAE,KAAK,KACT,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAA;IAC3C,QAAQ,CAAC,OAAO,EAAE,CAChB,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,KAC1D,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IAChC,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACrD,QAAQ,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,CAAA;IAC5C,QAAQ,CAAC,UAAU,EAAE,MAAM,OAAO,CAAA;IAClC,QAAQ,CAAC,cAAc,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAA;CACrD,KACE,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC,CAAA;AAE3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6EG;AACH,eAAO,MAAM,IAAI,GACd,KAAK,EAAE,OAAO,QACd,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,EAC9D,OAAO,CAAC,KAAK,EAAE,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,OAAO,KACtD,OAUF,CAAA;AAEH,KAAK,YAAY,CAAC,SAAS,IACzB,SAAS,CAAC,MAAM,SAAS,CAAC,SAAS,KAAK,CAAC,MAAM,UAAU,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GACrE,UAAU,GACV,KAAK,CAAA;AAEX,KAAK,cAAc,CAAC,SAAS,IAC3B,SAAS,CAAC,MAAM,SAAS,CAAC,SAAS,KAAK,CAAC,GAAG,EAAE,MAAM,YAAY,EAAE,GAAG,EAAE,GAAG,CAAC,GACvE,YAAY,GACZ,KAAK,CAAA;AAEX;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,IAAI,GACd,SAAS,SAAS,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,EACzE,WAAW,SAAS,MAErB,WAAW,EAAE,aAAa,EAAE,QAAQ;IACnC,QAAQ,CAAC,YAAY,EAAE,CACrB,WAAW,EAAE,WAAW,KACrB,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAA;IAC3C,QAAQ,CAAC,eAAe,EAAE,CACxB,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,KAC/B,aAAa,CAAA;CACnB,KAAG,EACF,QAAQ,EAAE,GAAG,IAAI,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,CAAC,SAAS,KAAK,CAC7D,GAAG,EACH,GAAG,EACH,MAAM,YAAY,EAClB,MAAM,OAAO,CACd,GACG,KAAK,CAAC,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,OAAO,CAAC,GACxD,KAAK,GAkBC,CAAA;AAEd,KAAK,YAAY,CAAC,OAAO,SAAS,aAAa,CAAC,OAAO,CAAC,IACtD,OAAO,SAAS,SAAS,CAAC,MAAM,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,GAChD,IAAI,GAAG,CAAC,IAAI,SAAS,aAAa,CAAC,OAAO,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,GACtE,EAAE,CAAA;AAER;;;;GAIG;AACH,eAAO,MAAM,SAAS,GACnB,KAAK,EAAE,OAAO,QAEb,OAAO,SAAS,aAAa,CAC3B,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAChD,EAED,GAAG,SAAS,OAAO,KAClB,YAAY,CAAC,OAAO,CAetB,CAAA"}
@@ -1,14 +1,20 @@
1
- import { Record } from 'effect';
1
+ import { Option, Record } from 'effect';
2
2
  /**
3
- * Creates type-safe managed resource configurations from a dependency schema.
3
+ * Declares a Managed Resources record. The Model and Message generics are
4
+ * provided up front; the entries record follows, built from calls to the
5
+ * `entry` builder passed into the inner function.
4
6
  *
5
7
  * Use this when a resource is expensive or stateful and should only exist while
6
- * the model is in a particular state a camera stream during a video call, a
8
+ * the model is in a particular state: a camera stream during a video call, a
7
9
  * WebSocket connection while on a chat page, or a Web Worker pool during a
8
10
  * computation. For resources that live for the entire application lifetime, use
9
11
  * the static `resources` config instead.
10
12
  *
11
- * **Lifecycle** The runtime watches each config's `modelToMaybeRequirements`
13
+ * Reach for `ManagedResource.aggregate` to combine multiple records, and
14
+ * `ManagedResource.lift` to translate a child Submodel's record into a parent
15
+ * context.
16
+ *
17
+ * **Lifecycle** — The runtime watches each entry's `modelToMaybeRequirements`
12
18
  * after every model update, structurally comparing the result against the
13
19
  * previous value:
14
20
  *
@@ -20,8 +26,8 @@ import { Record } from 'effect';
20
26
  * dispatches `onReleased()`. No re-acquisition occurs.
21
27
  *
22
28
  * If `acquire` fails, `onAcquireError` is dispatched and the resource daemon
23
- * continues watching for the next deps change a failed acquisition does not
24
- * crash the application.
29
+ * continues watching for the next requirements change: a failed acquisition
30
+ * does not crash the application.
25
31
  *
26
32
  * **Config fields:**
27
33
  *
@@ -30,13 +36,12 @@ import { Record } from 'effect';
30
36
  * - `modelToMaybeRequirements` — Extracts requirements from the model.
31
37
  * `Option.none()` means "release", `Option.some(params)` means
32
38
  * "acquire/re-acquire if params changed". For resources with no
33
- * parameters, use `S.Option(S.Null)` and return `Option.some(null)` —
34
- * not `S.Struct({})`, which has no fields for equivalence comparison.
39
+ * parameters, use `S.Option(S.Null)` and return `Option.some(null)`.
35
40
  * - `acquire` — Creates the resource from the unwrapped params. The returned
36
- * Effect should fail when acquisition fails errors in the error channel
41
+ * Effect should fail when acquisition fails: errors in the error channel
37
42
  * flow to `onAcquireError` as a message instead of crashing the runtime.
38
43
  * - `release` — Tears down the resource. Errors thrown here are silently
39
- * swallowed release must not block cleanup.
44
+ * swallowed: release must not block cleanup.
40
45
  * - `onAcquired` — Message dispatched when `acquire` succeeds.
41
46
  * - `onAcquireError` — Message dispatched when `acquire` fails.
42
47
  * - `onReleased` — Message dispatched after `release` completes.
@@ -45,14 +50,8 @@ import { Record } from 'effect';
45
50
  * ```ts
46
51
  * const CameraStream = ManagedResource.tag<MediaStream>()('CameraStream')
47
52
  *
48
- * const ManagedResourceDeps = S.Struct({
49
- * camera: S.Option(S.Struct({ facingMode: S.String })),
50
- * })
51
- *
52
- * const managedResources = ManagedResource.makeManagedResources(
53
- * ManagedResourceDeps,
54
- * )<Model, Message>({
55
- * camera: {
53
+ * const managedResources = ManagedResource.make<Model, Message>()(entry => ({
54
+ * camera: entry(S.Option(S.Struct({ facingMode: S.String })), {
56
55
  * resource: CameraStream,
57
56
  * modelToMaybeRequirements: model =>
58
57
  * pipe(
@@ -72,21 +71,60 @@ import { Record } from 'effect';
72
71
  * onAcquired: () => AcquiredCamera(),
73
72
  * onAcquireError: error => FailedAcquireCamera({ error: String(error) }),
74
73
  * onReleased: () => ReleasedCamera(),
75
- * },
76
- * })
74
+ * }),
75
+ * }))
77
76
  * ```
78
77
  *
79
- * @param ManagedResourceDeps - An Effect Schema struct where each field's type
80
- * drives the requirements for one managed resource. Wrap in `S.Option(...)` for
81
- * resources that can be released (most cases).
82
- *
83
78
  * @see {@link ManagedResource.tag} for creating the resource identity.
84
79
  */
85
- export const makeManagedResources = (ManagedResourceDeps) => (configs) =>
86
- /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
87
- Record.map(configs, (config, key) =>
80
+ export const make = () => (build) => {
81
+ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
82
+ const entry = ((schema, config) => ({
83
+ schema,
84
+ ...config,
85
+ }));
86
+ return build(entry);
87
+ };
88
+ /**
89
+ * Lifts a record of child Managed Resources into a parent's Model and Message
90
+ * context, applying a Model accessor and a Message wrapper uniformly to every
91
+ * entry. Per-entry requirements schemas and resource services are preserved.
92
+ *
93
+ * Unlike `Subscription.lift`, `toChildModel` returns an `Option`: a managed
94
+ * resource already speaks in `Option` (`modelToMaybeRequirements` returns
95
+ * `Option.none()` to release), and a child Submodel that owns a managed
96
+ * resource is itself something that mounts and unmounts. A missing child is
97
+ * just another `None` and flows through the same acquire/release channel, so
98
+ * each lifted entry's requirements must be `S.Option`-wrapped.
99
+ */
100
+ export const lift = (resources) => (config) =>
88
101
  /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
89
- ({
90
- schema: ManagedResourceDeps.fields[key],
91
- ...config,
102
+ Record.map(resources, resource => ({
103
+ schema: resource.schema,
104
+ resource: resource.resource,
105
+ modelToMaybeRequirements: (parentModel) => Option.flatMap(config.toChildModel(parentModel), resource.modelToMaybeRequirements),
106
+ acquire: resource.acquire,
107
+ release: resource.release,
108
+ onAcquired: (value) => config.toParentMessage(resource.onAcquired(value)),
109
+ onReleased: () => config.toParentMessage(resource.onReleased()),
110
+ onAcquireError: (error) => config.toParentMessage(resource.onAcquireError(error)),
92
111
  }));
112
+ /**
113
+ * Combines multiple Managed Resources records into one. Throws on duplicate
114
+ * keys so a misconfigured aggregate fails loudly at startup rather than
115
+ * silently overriding.
116
+ */
117
+ export const aggregate = () => (...records) => {
118
+ const result = {};
119
+ for (const record of records) {
120
+ for (const key of Object.keys(record)) {
121
+ if (Object.hasOwn(result, key)) {
122
+ throw new Error(`ManagedResource.aggregate: duplicate key "${key}" across records`);
123
+ }
124
+ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
125
+ result[key] = record[key];
126
+ }
127
+ }
128
+ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
129
+ return result;
130
+ };
@@ -3,6 +3,7 @@ import type { Command } from '../command/index.js';
3
3
  import { Document } from '../html/index.js';
4
4
  import { UrlRequest } from '../navigation/urlRequest.js';
5
5
  import { Url } from '../url/index.js';
6
+ import { VNode } from '../vdom.js';
6
7
  import type { ManagedResources } from './managedResource.js';
7
8
  import type { Subscriptions } from './subscription.js';
8
9
  /** Position of the DevTools badge and panel on screen. */
@@ -88,11 +89,14 @@ export type RoutingConfig<Message> = Readonly<{
88
89
  onUrlRequest: (request: UrlRequest) => Message;
89
90
  onUrlChange: (url: Url) => Message;
90
91
  }>;
91
- /** Context provided to crash.view and crash.report when the runtime encounters an unrecoverable error. */
92
+ /** Context provided to crash.view and crash.report when the runtime encounters
93
+ * an unrecoverable error. `message` is the Message being processed when the
94
+ * crash occurred, present as an `Option` because a crash during the initial
95
+ * render has no triggering Message. */
92
96
  export type CrashContext<Model, Message> = Readonly<{
93
97
  error: Error;
94
98
  model: Model;
95
- message: Message;
99
+ message: Option.Option<Message>;
96
100
  }>;
97
101
  /** Configuration for crash handling, with custom crash UI and/or crash reporting. */
98
102
  export type CrashConfig<Model, Message> = Readonly<{
@@ -170,6 +174,7 @@ export type MakeRuntimeReturn = Readonly<{
170
174
  runtimeId: string;
171
175
  start: (hmrModel?: unknown) => Effect.Effect<void>;
172
176
  }>;
177
+ export declare const patchVNode: (maybeCurrentVNode: Option.Option<VNode>, nextVNode: VNode | null, container: HTMLElement) => VNode;
173
178
  /** Creates a Foldkit program and returns a runtime that can be passed to `run`. Add a `routing` config for URL routing. */
174
179
  export declare function makeProgram<Model, Message extends {
175
180
  _tag: string;
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/runtime/runtime.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/runtime/runtime.ts"],"names":[],"mappings":"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"}
@@ -7,7 +7,7 @@ import { startWebSocketBridge } from '../devTools/webSocketBridge.js';
7
7
  import { __beginRender as beginHtmlRender, __clearRuntime as clearHtmlRuntime, __createBoundaryRegistry as createHtmlBoundaryRegistry, __setRuntime as setHtmlRuntime, } from '../html/index.js';
8
8
  import { MountTracker } from '../mount/index.js';
9
9
  import { fromString as urlFromString } from '../url/index.js';
10
- import { patch, toVNode } from '../vdom.js';
10
+ import { dedupeSharedVNodes, patch, toVNode } from '../vdom.js';
11
11
  import { addBfcacheRestoreListener, addNavigationEventListeners, } from './browserListeners.js';
12
12
  import { defaultCrashView, noOpDispatch } from './crashUI.js';
13
13
  import { deepFreeze } from './deepFreeze.js';
@@ -208,6 +208,16 @@ const makeRuntime = ({ Model, flags: resolveFlags, init, update, view, subscript
208
208
  }
209
209
  const modelRef = yield* Ref.make(initModel);
210
210
  const maybeCurrentVNodeRef = yield* Ref.make(Option.none());
211
+ // NOTE: shared by every perpetual fiber's crash path (init render,
212
+ // render loop, message drain). Each fiber catches its own cause so a
213
+ // failure surfaces as the crash view instead of dying silently and
214
+ // leaving the DOM frozen at the last successful render.
215
+ const crashWith = (cause, maybeMessage) => Effect.gen(function* () {
216
+ const model = yield* Ref.get(modelRef);
217
+ const squashed = Cause.squash(cause);
218
+ const error = squashed instanceof Error ? squashed : new Error(String(squashed));
219
+ renderCrashView({ error, model, message: maybeMessage }, crash, container, maybeCurrentVNodeRef);
220
+ });
211
221
  // NOTE: queue-drain-fiber-local state. Kept as plain closure
212
222
  // variables instead of `Ref`s because nothing else reads or writes
213
223
  // them concurrently, and JS's single-threaded model already orders
@@ -230,7 +240,7 @@ const makeRuntime = ({ Model, flags: resolveFlags, init, update, view, subscript
230
240
  enqueueHigh(message);
231
241
  const dispatch = { dispatchAsync, dispatchSync };
232
242
  const isRenderPendingRef = yield* SubscriptionRef.make(false);
233
- const lastDirtyMessageRef = yield* Ref.make(Option.none());
243
+ const maybeLastDirtyMessageRef = yield* Ref.make(Option.none());
234
244
  const isPausedEffect = Effect.suspend(() => Option.match(maybeDevToolsStore, {
235
245
  onNone: () => Effect.succeed(false),
236
246
  onSome: ({ stateRef }) => SubscriptionRef.get(stateRef).pipe(Effect.map(({ isPaused }) => isPaused)),
@@ -259,7 +269,7 @@ const makeRuntime = ({ Model, flags: resolveFlags, init, update, view, subscript
259
269
  if (currentModel !== nextModel) {
260
270
  yield* Ref.set(modelRef, nextModel);
261
271
  yield* SubscriptionRef.set(isRenderPendingRef, true);
262
- yield* Ref.set(lastDirtyMessageRef, Option.some(message));
272
+ yield* Ref.set(maybeLastDirtyMessageRef, Option.some(message));
263
273
  PubSub.publishUnsafe(modelPubSub, nextModel);
264
274
  yield* schedulePreserveModel(nextModel);
265
275
  }
@@ -395,25 +405,28 @@ const makeRuntime = ({ Model, flags: resolveFlags, init, update, view, subscript
395
405
  maybeMessageSchema);
396
406
  }
397
407
  }
398
- yield* render(initModel, Option.none());
408
+ const initRenderExit = yield* Effect.exit(render(initModel, Option.none()));
409
+ if (Exit.isFailure(initRenderExit)) {
410
+ return yield* crashWith(initRenderExit.cause, Option.none());
411
+ }
399
412
  const initMountEvents = drainMountEvents();
400
413
  yield* Option.match(maybeDevToolsStore, {
401
414
  onNone: () => Effect.void,
402
415
  onSome: store => store.recordInit(initModel, Array.map(initCommands, toCommandRecord), initMountEvents.starts),
403
416
  });
404
- // NOTE: lastDirtyMessageRef holds the most recent dirtying Message, so
405
- // slow-view callbacks during high-rate bursts attribute to the last
406
- // Message in the frame batch, not the specific one that pushed the
407
- // view past threshold. Acceptable for a debug callback; full
408
- // attribution would require correlating each message with its render
409
- // contribution, which isn't worth the complexity.
417
+ // NOTE: maybeLastDirtyMessageRef holds the most recent dirtying
418
+ // Message, so slow-view callbacks during high-rate bursts attribute
419
+ // to the last Message in the frame batch, not the specific one that
420
+ // pushed the view past threshold. Acceptable for a debug callback;
421
+ // full attribution would require correlating each message with its
422
+ // render contribution, which isn't worth the complexity.
410
423
  const renderLoop = makeRenderLoop({
411
424
  pendingRef: isRenderPendingRef,
412
425
  awaitNextFrame,
413
426
  isPaused: isPausedEffect,
414
427
  render: Effect.gen(function* () {
415
428
  const model = yield* Ref.get(modelRef);
416
- const maybeMessage = yield* Ref.get(lastDirtyMessageRef);
429
+ const maybeMessage = yield* Ref.get(maybeLastDirtyMessageRef);
417
430
  yield* render(model, maybeMessage);
418
431
  const mountEvents = drainMountEvents();
419
432
  yield* Option.match(maybeDevToolsStore, {
@@ -422,7 +435,10 @@ const makeRuntime = ({ Model, flags: resolveFlags, init, update, view, subscript
422
435
  });
423
436
  }),
424
437
  });
425
- yield* Effect.forkDetach(renderLoop);
438
+ yield* Effect.forkDetach(renderLoop.pipe(Effect.catchCause(cause => Effect.gen(function* () {
439
+ const maybeMessage = yield* Ref.get(maybeLastDirtyMessageRef);
440
+ yield* crashWith(cause, maybeMessage);
441
+ }))));
426
442
  addBfcacheRestoreListener();
427
443
  if (subscriptions) {
428
444
  yield* pipe(subscriptions, Record.toEntries, Effect.forEach(([_key, { dependenciesSchema, modelToDependencies, keepAliveEquivalence, dependenciesToStream, },]) => Effect.gen(function* () {
@@ -534,25 +550,19 @@ const makeRuntime = ({ Model, flags: resolveFlags, init, update, view, subscript
534
550
  const rest = yield* pollAvailable;
535
551
  yield* processBatch(Array.prepend(rest, first));
536
552
  yield* drainQueue;
537
- })), Effect.catchCause(cause => Effect.sync(() => {
538
- const squashed = Cause.squash(cause);
539
- const appError = squashed instanceof Error
540
- ? squashed
541
- : new Error(String(squashed));
542
- const model = Effect.runSync(Ref.get(modelRef));
543
- const message = Option.getOrThrow(currentMessage);
544
- renderCrashView({ error: appError, model, message }, crash, container, maybeCurrentVNodeRef);
545
- })));
553
+ })), Effect.catchCause(cause => crashWith(cause, currentMessage)));
546
554
  }));
547
555
  return { runtimeId, start };
548
556
  };
549
- const patchVNode = (maybeCurrentVNode, nullableNextVNode, container) => {
550
- const nextVNode = Predicate.isNotNull(nullableNextVNode)
551
- ? nullableNextVNode
557
+ // NOTE: exported for `patchVNode.test.ts` to assert the dedupeSharedVNodes
558
+ // wiring; not part of the public surface (`runtime/public.ts` is curated).
559
+ export const patchVNode = (maybeCurrentVNode, nextVNode, container) => {
560
+ const dedupedVNode = Predicate.isNotNull(nextVNode)
561
+ ? dedupeSharedVNodes(nextVNode)
552
562
  : h('!');
553
563
  return Option.match(maybeCurrentVNode, {
554
- onNone: () => patch(toVNode(container), nextVNode),
555
- onSome: currentVNode => patch(currentVNode, nextVNode),
564
+ onNone: () => patch(toVNode(container), dedupedVNode),
565
+ onSome: currentVNode => patch(currentVNode, dedupedVNode),
556
566
  });
557
567
  };
558
568
  const currentLocationUrl = () => {
@@ -40,7 +40,7 @@ export const aggregate = () => (...records) => {
40
40
  const result = {};
41
41
  for (const record of records) {
42
42
  for (const key of Object.keys(record)) {
43
- if (key in result) {
43
+ if (Object.hasOwn(result, key)) {
44
44
  throw new Error(`Subscription.aggregate: duplicate key "${key}" across records`);
45
45
  }
46
46
  result[key] = record[key];
@@ -0,0 +1,64 @@
1
+ import { Effect, Schema as S } from 'effect';
2
+ import * as Command from '../../command/index.js';
3
+ import { type Document } from '../../html/index.js';
4
+ export declare const RawSource: S.Struct<{
5
+ readonly kind: S.Literal<"Book">;
6
+ readonly id: S.String;
7
+ }>;
8
+ export type RawSource = typeof RawSource.Type;
9
+ export declare const Source: S.Struct<{
10
+ readonly kind: S.Literal<"Book">;
11
+ readonly id: S.String;
12
+ }>;
13
+ export type Source = typeof Source.Type;
14
+ export declare const Model: S.Struct<{
15
+ readonly sources: S.$Array<S.Struct<{
16
+ readonly kind: S.Literal<"Book">;
17
+ readonly id: S.String;
18
+ }>>;
19
+ }>;
20
+ export type Model = typeof Model.Type;
21
+ export declare const ClickedReload: import("../../schema/index.js").CallableTaggedStruct<"ClickedReload", {}>;
22
+ export declare const LoadedSources: import("../../schema/index.js").CallableTaggedStruct<"LoadedSources", {
23
+ sources: S.$Array<S.Struct<{
24
+ readonly kind: S.Literal<"Book">;
25
+ readonly id: S.String;
26
+ }>>;
27
+ }>;
28
+ export declare const SelectedSource: import("../../schema/index.js").CallableTaggedStruct<"SelectedSource", {
29
+ source: S.Struct<{
30
+ readonly kind: S.Literal<"Book">;
31
+ readonly id: S.String;
32
+ }>;
33
+ }>;
34
+ export declare const SubmittedNewSourceId: import("../../schema/index.js").CallableTaggedStruct<"SubmittedNewSourceId", {
35
+ id: S.String;
36
+ }>;
37
+ export declare const Message: S.Union<readonly [import("../../schema/index.js").CallableTaggedStruct<"ClickedReload", {}>, import("../../schema/index.js").CallableTaggedStruct<"LoadedSources", {
38
+ sources: S.$Array<S.Struct<{
39
+ readonly kind: S.Literal<"Book">;
40
+ readonly id: S.String;
41
+ }>>;
42
+ }>, import("../../schema/index.js").CallableTaggedStruct<"SelectedSource", {
43
+ source: S.Struct<{
44
+ readonly kind: S.Literal<"Book">;
45
+ readonly id: S.String;
46
+ }>;
47
+ }>, import("../../schema/index.js").CallableTaggedStruct<"SubmittedNewSourceId", {
48
+ id: S.String;
49
+ }>]>;
50
+ export type Message = typeof Message.Type;
51
+ export declare const reloadedSources: ReadonlyArray<RawSource>;
52
+ export declare const ReloadSources: Command.CommandDefinitionNoArgs<"ReloadSources", Effect.Effect<{
53
+ readonly _tag: "LoadedSources";
54
+ readonly sources: readonly {
55
+ readonly kind: "Book";
56
+ readonly id: string;
57
+ }[];
58
+ }, never, never>>;
59
+ export declare const validSources: ReadonlyArray<RawSource>;
60
+ export declare const malformedSources: ReadonlyArray<RawSource>;
61
+ export declare const initialModel: Model;
62
+ export declare const update: (model: Model, message: Message) => readonly [Model, ReadonlyArray<Command.Command<Message>>];
63
+ export declare const view: (model: Model) => Document;
64
+ //# sourceMappingURL=crashOnRender.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crashOnRender.d.ts","sourceRoot":"","sources":["../../../src/test/apps/crashOnRender.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,MAAM,EAAc,MAAM,IAAI,CAAC,EAAE,MAAM,QAAQ,CAAA;AAE/D,OAAO,KAAK,OAAO,MAAM,wBAAwB,CAAA;AACjD,OAAO,EAAE,KAAK,QAAQ,EAAQ,MAAM,qBAAqB,CAAA;AAMzD,eAAO,MAAM,SAAS;;;EAGpB,CAAA;AACF,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC,IAAI,CAAA;AAK7C,eAAO,MAAM,MAAM;;;EAGjB,CAAA;AACF,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,IAAI,CAAA;AAEvC,eAAO,MAAM,KAAK;;;;;EAEhB,CAAA;AACF,MAAM,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,IAAI,CAAA;AAIrC,eAAO,MAAM,aAAa,2EAAqB,CAAA;AAC/C,eAAO,MAAM,aAAa;;;;;EAAsD,CAAA;AAChF,eAAO,MAAM,cAAc;;;;;EAA0C,CAAA;AACrE,eAAO,MAAM,oBAAoB;;EAA8C,CAAA;AAE/E,eAAO,MAAM,OAAO;;;;;;;;;;;;IAKlB,CAAA;AACF,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAA;AAMzC,eAAO,MAAM,eAAe,EAAE,aAAa,CAAC,SAAS,CAGpD,CAAA;AAED,eAAO,MAAM,aAAa;;;;;;iBAGoC,CAAA;AAI9D,eAAO,MAAM,YAAY,EAAE,aAAa,CAAC,SAAS,CAEjD,CAAA;AAED,eAAO,MAAM,gBAAgB,EAAE,aAAa,CAAC,SAAS,CAErD,CAAA;AAED,eAAO,MAAM,YAAY,EAAE,KAAiC,CAAA;AAI5D,eAAO,MAAM,MAAM,GACjB,OAAO,KAAK,EACZ,SAAS,OAAO,KACf,SAAS,CAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAiBxD,CAAA;AAIH,eAAO,MAAM,IAAI,GAAI,OAAO,KAAK,KAAG,QA8BnC,CAAA"}
@@ -0,0 +1,72 @@
1
+ import { Array, Effect, Match as M, Schema as S } from 'effect';
2
+ import * as Command from '../../command/index.js';
3
+ import { html } from '../../html/index.js';
4
+ import { m } from '../../message/index.js';
5
+ import { evo } from '../../struct/index.js';
6
+ // MODEL
7
+ export const RawSource = S.Struct({
8
+ kind: S.Literal('Book'),
9
+ id: S.String,
10
+ });
11
+ // NOTE: the strict counterpart of RawSource, requiring a non-empty id.
12
+ // Constructing one (or a SelectedSource Message carrying one) from an empty id
13
+ // throws at construction time, which is what drives the render and update crash.
14
+ export const Source = S.Struct({
15
+ kind: S.Literal('Book'),
16
+ id: S.String.check(S.isNonEmpty()),
17
+ });
18
+ export const Model = S.Struct({
19
+ sources: S.Array(RawSource),
20
+ });
21
+ // MESSAGE
22
+ export const ClickedReload = m('ClickedReload');
23
+ export const LoadedSources = m('LoadedSources', { sources: S.Array(RawSource) });
24
+ export const SelectedSource = m('SelectedSource', { source: Source });
25
+ export const SubmittedNewSourceId = m('SubmittedNewSourceId', { id: S.String });
26
+ export const Message = S.Union([
27
+ ClickedReload,
28
+ LoadedSources,
29
+ SelectedSource,
30
+ SubmittedNewSourceId,
31
+ ]);
32
+ // COMMAND
33
+ // NOTE: the malformed entry (empty id) sits inertly as a RawSource until the
34
+ // view rebuilds its SelectedSource handler, which constructs a Source and throws.
35
+ export const reloadedSources = [
36
+ { kind: 'Book', id: '1' },
37
+ { kind: 'Book', id: '' },
38
+ ];
39
+ export const ReloadSources = Command.define('ReloadSources', LoadedSources)(Effect.succeed(LoadedSources({ sources: reloadedSources })));
40
+ // INIT
41
+ export const validSources = [
42
+ { kind: 'Book', id: '1' },
43
+ ];
44
+ export const malformedSources = [
45
+ { kind: 'Book', id: '' },
46
+ ];
47
+ export const initialModel = { sources: validSources };
48
+ // UPDATE
49
+ export const update = (model, message) => M.value(message).pipe(M.withReturnType(), M.tagsExhaustive({
50
+ ClickedReload: () => [model, [ReloadSources()]],
51
+ LoadedSources: ({ sources }) => [
52
+ evo(model, { sources: () => sources }),
53
+ [],
54
+ ],
55
+ SelectedSource: () => [model, []],
56
+ SubmittedNewSourceId: ({ id }) => {
57
+ const source = Source.make({ kind: 'Book', id });
58
+ return [evo(model, { sources: Array.append(source) }), []];
59
+ },
60
+ }));
61
+ // VIEW
62
+ export const view = (model) => {
63
+ const h = html();
64
+ const body = h.div([], [
65
+ h.button([h.OnClick(ClickedReload()), h.Role('button')], ['Reload']),
66
+ h.button([h.OnClick(SubmittedNewSourceId({ id: '' })), h.Role('button')], ['Add source']),
67
+ h.ul([h.Role('list')], Array.map(model.sources, source => h.keyed('li')(source.id, [], [
68
+ h.button([h.OnClick(SelectedSource({ source })), h.Role('button')], [`Select ${source.id}`]),
69
+ ]))),
70
+ ]);
71
+ return { title: 'Sources', body };
72
+ };
package/dist/vdom.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { toVNode } from 'snabbdom';
1
+ import { type VNode, toVNode } from 'snabbdom';
2
2
  export type { VNode } from 'snabbdom';
3
3
  export { toVNode };
4
- export declare const patch: (oldVnode: import("snabbdom").VNode | Element | DocumentFragment, vnode: import("snabbdom").VNode) => import("snabbdom").VNode;
4
+ export declare const patch: (oldVnode: VNode | Element | DocumentFragment, vnode: VNode) => VNode;
5
+ export declare const dedupeSharedVNodes: (root: VNode) => VNode;
5
6
  //# sourceMappingURL=vdom.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"vdom.d.ts","sourceRoot":"","sources":["../src/vdom.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,OAAO,EACR,MAAM,UAAU,CAAA;AAIjB,YAAY,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;AACrC,OAAO,EAAE,OAAO,EAAE,CAAA;AAElB,eAAO,MAAM,KAAK,gIAOhB,CAAA"}
1
+ {"version":3,"file":"vdom.d.ts","sourceRoot":"","sources":["../src/vdom.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,KAAK,EAOV,OAAO,EACR,MAAM,UAAU,CAAA;AAIjB,YAAY,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;AACrC,OAAO,EAAE,OAAO,EAAE,CAAA;AAElB,eAAO,MAAM,KAAK,uEAOhB,CAAA;AAeF,eAAO,MAAM,kBAAkB,GAAI,MAAM,KAAK,KAAG,KA8BhD,CAAA"}
package/dist/vdom.js CHANGED
@@ -9,3 +9,47 @@ export const patch = init([
9
9
  propsModule,
10
10
  styleModule,
11
11
  ]);
12
+ // NOTE: snabbdom records each element's live DOM node on `vnode.elm` by
13
+ // mutating the vnode object during patch. A vnode object placed in more than
14
+ // one tree position would share a single `.elm`, so removals and text updates
15
+ // land on the wrong DOM node. View code legitimately reuses vnode values, e.g.
16
+ // `const checkmark = h.span(...)` dropped into several slots, so before each
17
+ // patch we clone any vnode object reached a second time, giving every position
18
+ // its own object.
19
+ // Detection is keyed off a per-patch Set, so a vnode reused across renders (a
20
+ // memoized `createLazy` subtree, the identical object each render) is reached
21
+ // only once per patch and passes through untouched, leaving snabbdom's
22
+ // same-object subtree short-circuit intact. Allocation happens only along
23
+ // paths where a duplicate is actually found; a tree with no reuse returns
24
+ // unchanged.
25
+ export const dedupeSharedVNodes = (root) => {
26
+ const seen = new Set();
27
+ const visit = (node) => {
28
+ const base = seen.has(node) ? { ...node, elm: undefined } : node;
29
+ seen.add(base);
30
+ const children = base.children;
31
+ if (children === undefined) {
32
+ return base;
33
+ }
34
+ let nextChildren;
35
+ for (let index = 0; index < children.length; index++) {
36
+ const child = children[index];
37
+ const deduped = typeof child === 'string' ? child : visit(child);
38
+ if (deduped !== child) {
39
+ if (nextChildren === undefined) {
40
+ nextChildren = children.slice();
41
+ }
42
+ nextChildren[index] = deduped;
43
+ }
44
+ }
45
+ if (nextChildren === undefined) {
46
+ return base;
47
+ }
48
+ if (base === node) {
49
+ return { ...node, children: nextChildren };
50
+ }
51
+ base.children = nextChildren;
52
+ return base;
53
+ };
54
+ return visit(root);
55
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foldkit",
3
- "version": "0.102.0",
3
+ "version": "0.103.0",
4
4
  "description": "A TypeScript frontend framework, built on Effect and architected like Elm",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",