@rikalabs/effect-react 0.0.1

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.
Files changed (167) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +179 -0
  3. package/dist/actions/http.d.ts +18 -0
  4. package/dist/actions/index.d.ts +4 -0
  5. package/dist/actions/react.d.ts +7 -0
  6. package/dist/actions/service.d.ts +18 -0
  7. package/dist/actions/types.d.ts +33 -0
  8. package/dist/boundary/codecs.d.ts +40 -0
  9. package/dist/boundary/errors.d.ts +22 -0
  10. package/dist/boundary/index.d.ts +2 -0
  11. package/dist/chunk-2GIUCKL2.js +16 -0
  12. package/dist/chunk-2GIUCKL2.js.map +1 -0
  13. package/dist/chunk-2TG7YEVD.js +11 -0
  14. package/dist/chunk-2TG7YEVD.js.map +1 -0
  15. package/dist/chunk-6FI4ROTW.js +152 -0
  16. package/dist/chunk-6FI4ROTW.js.map +1 -0
  17. package/dist/chunk-C5JI7D7W.js +213 -0
  18. package/dist/chunk-C5JI7D7W.js.map +1 -0
  19. package/dist/chunk-EEYASTXR.js +99 -0
  20. package/dist/chunk-EEYASTXR.js.map +1 -0
  21. package/dist/chunk-H7MOLKTU.js +301 -0
  22. package/dist/chunk-H7MOLKTU.js.map +1 -0
  23. package/dist/chunk-IVIYY6S5.js +77 -0
  24. package/dist/chunk-IVIYY6S5.js.map +1 -0
  25. package/dist/chunk-JKN75OYC.js +87 -0
  26. package/dist/chunk-JKN75OYC.js.map +1 -0
  27. package/dist/chunk-M2CJG6T7.js +24 -0
  28. package/dist/chunk-M2CJG6T7.js.map +1 -0
  29. package/dist/chunk-MDGEGQZB.js +206 -0
  30. package/dist/chunk-MDGEGQZB.js.map +1 -0
  31. package/dist/chunk-NI2GNZ7S.js +78 -0
  32. package/dist/chunk-NI2GNZ7S.js.map +1 -0
  33. package/dist/chunk-O7XTA7H3.js +423 -0
  34. package/dist/chunk-O7XTA7H3.js.map +1 -0
  35. package/dist/chunk-S67FHWAR.js +88 -0
  36. package/dist/chunk-S67FHWAR.js.map +1 -0
  37. package/dist/chunk-SKC3HMF3.js +17 -0
  38. package/dist/chunk-SKC3HMF3.js.map +1 -0
  39. package/dist/chunk-TUJZ6XJY.js +127 -0
  40. package/dist/chunk-TUJZ6XJY.js.map +1 -0
  41. package/dist/chunk-WPV3WFMS.js +38 -0
  42. package/dist/chunk-WPV3WFMS.js.map +1 -0
  43. package/dist/chunk-XIBEKS5A.js +301 -0
  44. package/dist/chunk-XIBEKS5A.js.map +1 -0
  45. package/dist/chunk-YG22YP5K.js +68 -0
  46. package/dist/chunk-YG22YP5K.js.map +1 -0
  47. package/dist/chunk-ZMZQBREU.js +262 -0
  48. package/dist/chunk-ZMZQBREU.js.map +1 -0
  49. package/dist/client/index.cjs +191 -0
  50. package/dist/client/index.cjs.map +1 -0
  51. package/dist/client/index.d.ts +8 -0
  52. package/dist/client/index.js +14 -0
  53. package/dist/client/index.js.map +1 -0
  54. package/dist/config/index.cjs +63 -0
  55. package/dist/config/index.cjs.map +1 -0
  56. package/dist/config/index.d.ts +32 -0
  57. package/dist/config/index.js +9 -0
  58. package/dist/config/index.js.map +1 -0
  59. package/dist/data/index.d.ts +3 -0
  60. package/dist/data/react.d.ts +10 -0
  61. package/dist/data/service.d.ts +20 -0
  62. package/dist/data/types.d.ts +31 -0
  63. package/dist/devtools/events.d.ts +37 -0
  64. package/dist/devtools/index.cjs +149 -0
  65. package/dist/devtools/index.cjs.map +1 -0
  66. package/dist/devtools/index.d.ts +2 -0
  67. package/dist/devtools/index.js +18 -0
  68. package/dist/devtools/index.js.map +1 -0
  69. package/dist/devtools/react.d.ts +8 -0
  70. package/dist/form/index.cjs +301 -0
  71. package/dist/form/index.cjs.map +1 -0
  72. package/dist/form/index.d.ts +3 -0
  73. package/dist/form/index.js +14 -0
  74. package/dist/form/index.js.map +1 -0
  75. package/dist/form/react.d.ts +9 -0
  76. package/dist/form/service.d.ts +3 -0
  77. package/dist/form/types.d.ts +41 -0
  78. package/dist/framework/app.d.ts +21 -0
  79. package/dist/framework/cache.d.ts +10 -0
  80. package/dist/framework/contracts.d.ts +32 -0
  81. package/dist/framework/index.cjs +1006 -0
  82. package/dist/framework/index.cjs.map +1 -0
  83. package/dist/framework/index.d.ts +4 -0
  84. package/dist/framework/index.js +35 -0
  85. package/dist/framework/index.js.map +1 -0
  86. package/dist/framework/manifest.d.ts +12 -0
  87. package/dist/framework/vite.d.ts +13 -0
  88. package/dist/framework-vite/index.cjs +163 -0
  89. package/dist/framework-vite/index.cjs.map +1 -0
  90. package/dist/framework-vite/index.d.ts +1 -0
  91. package/dist/framework-vite/index.js +125 -0
  92. package/dist/framework-vite/index.js.map +1 -0
  93. package/dist/grid/grid.d.ts +8 -0
  94. package/dist/grid/index.cjs +238 -0
  95. package/dist/grid/index.cjs.map +1 -0
  96. package/dist/grid/index.d.ts +2 -0
  97. package/dist/grid/index.js +19 -0
  98. package/dist/grid/index.js.map +1 -0
  99. package/dist/grid/types.d.ts +35 -0
  100. package/dist/index.cjs +2512 -0
  101. package/dist/index.cjs.map +1 -0
  102. package/dist/index.d.ts +13 -0
  103. package/dist/index.js +207 -0
  104. package/dist/index.js.map +1 -0
  105. package/dist/kernel/app.d.ts +26 -0
  106. package/dist/kernel/index.d.ts +3 -0
  107. package/dist/kernel/runtime.d.ts +5 -0
  108. package/dist/kernel/telemetry.d.ts +37 -0
  109. package/dist/navigation/index.d.ts +4 -0
  110. package/dist/navigation/matcher.d.ts +13 -0
  111. package/dist/navigation/react.d.ts +12 -0
  112. package/dist/navigation/service.d.ts +23 -0
  113. package/dist/navigation/types.d.ts +65 -0
  114. package/dist/query/index.cjs +361 -0
  115. package/dist/query/index.cjs.map +1 -0
  116. package/dist/query/index.d.ts +3 -0
  117. package/dist/query/index.js +30 -0
  118. package/dist/query/index.js.map +1 -0
  119. package/dist/query/react.d.ts +27 -0
  120. package/dist/query/service.d.ts +10 -0
  121. package/dist/query/types.d.ts +5 -0
  122. package/dist/react/index.d.ts +1 -0
  123. package/dist/react/provider.d.ts +10 -0
  124. package/dist/realtime/channel.d.ts +15 -0
  125. package/dist/realtime/index.cjs +117 -0
  126. package/dist/realtime/index.cjs.map +1 -0
  127. package/dist/realtime/index.d.ts +2 -0
  128. package/dist/realtime/index.js +15 -0
  129. package/dist/realtime/index.js.map +1 -0
  130. package/dist/realtime/presence.d.ts +22 -0
  131. package/dist/render/hydration.d.ts +24 -0
  132. package/dist/render/index.d.ts +2 -0
  133. package/dist/render/ssr.d.ts +13 -0
  134. package/dist/router/helpers.d.ts +26 -0
  135. package/dist/router/index.cjs +236 -0
  136. package/dist/router/index.cjs.map +1 -0
  137. package/dist/router/index.d.ts +4 -0
  138. package/dist/router/index.js +40 -0
  139. package/dist/router/index.js.map +1 -0
  140. package/dist/router/react.d.ts +5 -0
  141. package/dist/router/service.d.ts +5 -0
  142. package/dist/router/types.d.ts +1 -0
  143. package/dist/server/index.cjs +174 -0
  144. package/dist/server/index.cjs.map +1 -0
  145. package/dist/server/index.d.ts +16 -0
  146. package/dist/server/index.js +12 -0
  147. package/dist/server/index.js.map +1 -0
  148. package/dist/state/index.cjs +128 -0
  149. package/dist/state/index.cjs.map +1 -0
  150. package/dist/state/index.d.ts +2 -0
  151. package/dist/state/index.js +36 -0
  152. package/dist/state/index.js.map +1 -0
  153. package/dist/state/react.d.ts +3 -0
  154. package/dist/state/service.d.ts +28 -0
  155. package/dist/testing/index.cjs +970 -0
  156. package/dist/testing/index.cjs.map +1 -0
  157. package/dist/testing/index.d.ts +2 -0
  158. package/dist/testing/index.js +13 -0
  159. package/dist/testing/index.js.map +1 -0
  160. package/dist/virtual/index.cjs +160 -0
  161. package/dist/virtual/index.cjs.map +1 -0
  162. package/dist/virtual/index.d.ts +2 -0
  163. package/dist/virtual/index.js +21 -0
  164. package/dist/virtual/index.js.map +1 -0
  165. package/dist/virtual/types.d.ts +25 -0
  166. package/dist/virtual/virtual.d.ts +9 -0
  167. package/package.json +156 -0
@@ -0,0 +1,301 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/form/index.ts
21
+ var form_exports = {};
22
+ __export(form_exports, {
23
+ FormValidationError: () => FormValidationError,
24
+ defineForm: () => defineForm,
25
+ makeForm: () => makeForm,
26
+ useForm: () => useForm
27
+ });
28
+ module.exports = __toCommonJS(form_exports);
29
+
30
+ // src/form/types.ts
31
+ var defineForm = (contract) => contract;
32
+ var FormValidationError = class extends Error {
33
+ constructor(errors) {
34
+ super("Form validation failed");
35
+ this.errors = errors;
36
+ this.name = "FormValidationError";
37
+ }
38
+ _tag = "FormValidationError";
39
+ };
40
+
41
+ // src/form/service.ts
42
+ var import_effect = require("effect");
43
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
44
+ var cloneUnknown = (value) => {
45
+ if (Array.isArray(value)) {
46
+ return value.map((entry) => cloneUnknown(entry));
47
+ }
48
+ if (isRecord(value)) {
49
+ const clone = {};
50
+ for (const [key, entry] of Object.entries(value)) {
51
+ clone[key] = cloneUnknown(entry);
52
+ }
53
+ return clone;
54
+ }
55
+ return value;
56
+ };
57
+ var cloneValue = (value) => {
58
+ return cloneUnknown(value);
59
+ };
60
+ var deepEqual = (left, right) => {
61
+ if (Object.is(left, right)) {
62
+ return true;
63
+ }
64
+ if (Array.isArray(left) && Array.isArray(right)) {
65
+ if (left.length !== right.length) {
66
+ return false;
67
+ }
68
+ for (let index = 0; index < left.length; index += 1) {
69
+ if (!deepEqual(left[index], right[index])) {
70
+ return false;
71
+ }
72
+ }
73
+ return true;
74
+ }
75
+ if (isRecord(left) && isRecord(right)) {
76
+ const leftKeys = Object.keys(left);
77
+ const rightKeys = Object.keys(right);
78
+ if (leftKeys.length !== rightKeys.length) {
79
+ return false;
80
+ }
81
+ for (const key of leftKeys) {
82
+ if (!(key in right)) {
83
+ return false;
84
+ }
85
+ if (!deepEqual(left[key], right[key])) {
86
+ return false;
87
+ }
88
+ }
89
+ return true;
90
+ }
91
+ return false;
92
+ };
93
+ var toFormErrors = (error, fieldNames) => {
94
+ const formatted = import_effect.ParseResult.ArrayFormatter.formatErrorSync(error);
95
+ const errors = {};
96
+ for (const entry of formatted) {
97
+ const root = entry.path[0];
98
+ if (typeof root === "string" && fieldNames.has(root)) {
99
+ const key = root;
100
+ errors[key] ??= entry.message;
101
+ continue;
102
+ }
103
+ errors._form ??= entry.message;
104
+ }
105
+ if (formatted.length === 0 && errors._form === void 0) {
106
+ errors._form = "Invalid form values.";
107
+ }
108
+ return errors;
109
+ };
110
+ var createInitialState = (defaults) => ({
111
+ values: cloneValue(defaults),
112
+ errors: {},
113
+ touched: {},
114
+ dirty: false,
115
+ submitting: false,
116
+ submitted: false
117
+ });
118
+ var makeForm = (contract) => import_effect.Effect.gen(function* () {
119
+ const decode = import_effect.Schema.decodeUnknown(contract.schema, { errors: "all" });
120
+ const defaults = cloneValue(contract.defaults);
121
+ const fieldNames = new Set(Object.keys(defaults));
122
+ const snapshots = yield* import_effect.SubscriptionRef.make(createInitialState(defaults));
123
+ const validateCurrent = (current) => decode(current.values).pipe(
124
+ import_effect.Effect.match({
125
+ onFailure: (issue) => ({
126
+ _tag: "invalid",
127
+ errors: toFormErrors(issue, fieldNames),
128
+ issue
129
+ }),
130
+ onSuccess: (values) => ({
131
+ _tag: "valid",
132
+ values: cloneValue(values)
133
+ })
134
+ })
135
+ );
136
+ const setField = (field, value) => import_effect.SubscriptionRef.update(snapshots, (current) => {
137
+ const nextValues = {
138
+ ...current.values,
139
+ [field]: value
140
+ };
141
+ const nextErrors = {
142
+ ...current.errors
143
+ };
144
+ delete nextErrors[field];
145
+ return {
146
+ ...current,
147
+ values: nextValues,
148
+ errors: nextErrors,
149
+ touched: {
150
+ ...current.touched,
151
+ [field]: true
152
+ },
153
+ dirty: !deepEqual(nextValues, defaults),
154
+ submitted: false
155
+ };
156
+ }).pipe(import_effect.Effect.asVoid);
157
+ const reset = import_effect.SubscriptionRef.set(
158
+ snapshots,
159
+ createInitialState(defaults)
160
+ ).pipe(import_effect.Effect.asVoid);
161
+ const validate = import_effect.Effect.gen(function* () {
162
+ const current = yield* import_effect.SubscriptionRef.get(snapshots);
163
+ const result = yield* validateCurrent(current);
164
+ if (result._tag === "valid") {
165
+ yield* import_effect.SubscriptionRef.update(snapshots, (snapshot) => ({
166
+ ...snapshot,
167
+ values: result.values,
168
+ errors: {},
169
+ dirty: !deepEqual(result.values, defaults)
170
+ }));
171
+ } else {
172
+ yield* import_effect.SubscriptionRef.update(snapshots, (snapshot) => ({
173
+ ...snapshot,
174
+ errors: result.errors
175
+ }));
176
+ }
177
+ return result;
178
+ });
179
+ const submit = (handler) => import_effect.Effect.gen(function* () {
180
+ yield* import_effect.SubscriptionRef.update(snapshots, (current2) => ({
181
+ ...current2,
182
+ submitting: true,
183
+ submitted: false
184
+ }));
185
+ const current = yield* import_effect.SubscriptionRef.get(snapshots);
186
+ const validation = yield* validateCurrent(current);
187
+ if (validation._tag === "invalid") {
188
+ yield* import_effect.SubscriptionRef.update(snapshots, (snapshot) => ({
189
+ ...snapshot,
190
+ submitting: false,
191
+ submitted: false,
192
+ errors: validation.errors
193
+ }));
194
+ return yield* import_effect.Effect.fail(new FormValidationError(validation.errors));
195
+ }
196
+ yield* import_effect.SubscriptionRef.update(snapshots, (snapshot) => ({
197
+ ...snapshot,
198
+ values: validation.values,
199
+ errors: {},
200
+ dirty: !deepEqual(validation.values, defaults)
201
+ }));
202
+ return yield* handler(validation.values).pipe(
203
+ import_effect.Effect.tap(
204
+ () => import_effect.SubscriptionRef.update(snapshots, (snapshot) => ({
205
+ ...snapshot,
206
+ submitting: false,
207
+ submitted: true
208
+ })).pipe(import_effect.Effect.asVoid)
209
+ ),
210
+ import_effect.Effect.tapError(
211
+ () => import_effect.SubscriptionRef.update(snapshots, (snapshot) => ({
212
+ ...snapshot,
213
+ submitting: false,
214
+ submitted: false
215
+ })).pipe(import_effect.Effect.asVoid)
216
+ )
217
+ );
218
+ });
219
+ return {
220
+ getSnapshot: import_effect.SubscriptionRef.get(snapshots),
221
+ snapshots: snapshots.changes,
222
+ setField,
223
+ reset,
224
+ validate,
225
+ submit
226
+ };
227
+ });
228
+
229
+ // src/form/react.tsx
230
+ var import_effect2 = require("effect");
231
+ var import_react2 = require("react");
232
+
233
+ // src/react/provider.tsx
234
+ var import_react = require("react");
235
+ var import_jsx_runtime = require("react/jsx-runtime");
236
+ var RuntimeContext = (0, import_react.createContext)(null);
237
+ var useEffectRuntime = () => {
238
+ const runtime = (0, import_react.useContext)(RuntimeContext);
239
+ if (runtime === null) {
240
+ throw new Error("Effect runtime is not available. Wrap your app with <EffectProvider>.");
241
+ }
242
+ return runtime;
243
+ };
244
+
245
+ // src/form/react.tsx
246
+ var useFormStore = (contract) => {
247
+ const runtime = useEffectRuntime();
248
+ return (0, import_react2.useMemo)(() => runtime.runSync(makeForm(contract)), [contract, runtime]);
249
+ };
250
+ var useForm = (contract) => {
251
+ const runtime = useEffectRuntime();
252
+ const form = useFormStore(contract);
253
+ const subscribe = (0, import_react2.useCallback)(
254
+ (listener) => {
255
+ const fiber = runtime.runFork(import_effect2.Stream.runForEach(form.snapshots, () => import_effect2.Effect.sync(listener)));
256
+ return () => {
257
+ runtime.runFork(import_effect2.Fiber.interrupt(fiber));
258
+ };
259
+ },
260
+ [form, runtime]
261
+ );
262
+ const getSnapshot = (0, import_react2.useCallback)(() => runtime.runSync(form.getSnapshot), [form, runtime]);
263
+ const snapshot = (0, import_react2.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
264
+ const setField = (0, import_react2.useCallback)(
265
+ (field, value) => runtime.runPromise(form.setField(field, value)),
266
+ [form, runtime]
267
+ );
268
+ const reset = (0, import_react2.useCallback)(
269
+ () => runtime.runPromise(form.reset),
270
+ [form, runtime]
271
+ );
272
+ const validate = (0, import_react2.useCallback)(
273
+ () => runtime.runPromise(form.validate),
274
+ [form, runtime]
275
+ );
276
+ const submit = (0, import_react2.useCallback)(
277
+ (handler) => runtime.runPromise(form.submit(handler)),
278
+ [form, runtime]
279
+ );
280
+ return {
281
+ ...snapshot,
282
+ commands: {
283
+ setField: form.setField,
284
+ reset: form.reset,
285
+ validate: form.validate,
286
+ submit: form.submit
287
+ },
288
+ setField,
289
+ reset,
290
+ validate,
291
+ submit
292
+ };
293
+ };
294
+ // Annotate the CommonJS export names for ESM import in node:
295
+ 0 && (module.exports = {
296
+ FormValidationError,
297
+ defineForm,
298
+ makeForm,
299
+ useForm
300
+ });
301
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/form/index.ts","../../src/form/types.ts","../../src/form/service.ts","../../src/form/react.tsx","../../src/react/provider.tsx"],"sourcesContent":["export * from \"./types\";\nexport * from \"./service\";\nexport * from \"./react\";\n","import type { Effect, ParseResult, Schema, Stream } from \"effect\";\n\nexport type FormValues = Readonly<Record<string, unknown>>;\n\nexport type FormFieldName<Values extends FormValues> = Extract<keyof Values, string>;\n\nexport type FormErrorKey<Values extends FormValues> = FormFieldName<Values> | \"_form\";\n\nexport type FormErrors<Values extends FormValues> = Readonly<\n Partial<Record<FormErrorKey<Values>, string>>\n>;\n\nexport type FormTouched<Values extends FormValues> = Readonly<\n Partial<Record<FormFieldName<Values>, boolean>>\n>;\n\nexport interface FormContract<Values extends FormValues, Encoded = Values> {\n readonly schema: Schema.Schema<Values, Encoded, never>;\n readonly defaults: Values;\n}\n\nexport const defineForm = <Values extends FormValues, Encoded = Values>(\n contract: FormContract<Values, Encoded>,\n): FormContract<Values, Encoded> => contract;\n\nexport interface FormState<Values extends FormValues> {\n readonly values: Values;\n readonly errors: FormErrors<Values>;\n readonly touched: FormTouched<Values>;\n readonly dirty: boolean;\n readonly submitting: boolean;\n readonly submitted: boolean;\n}\n\nexport type FormValidationResult<Values extends FormValues> =\n | {\n readonly _tag: \"valid\";\n readonly values: Values;\n }\n | {\n readonly _tag: \"invalid\";\n readonly errors: FormErrors<Values>;\n readonly issue: ParseResult.ParseError;\n };\n\nexport class FormValidationError<Values extends FormValues> extends Error {\n readonly _tag = \"FormValidationError\" as const;\n\n constructor(readonly errors: FormErrors<Values>) {\n super(\"Form validation failed\");\n this.name = \"FormValidationError\";\n }\n}\n\nexport type FormSubmitHandler<Values extends FormValues, A, E, R = never> = (\n values: Values,\n) => Effect.Effect<A, E, R>;\n\nexport interface FormStore<Values extends FormValues> {\n readonly getSnapshot: Effect.Effect<FormState<Values>, never, never>;\n readonly snapshots: Stream.Stream<FormState<Values>>;\n readonly setField: <Field extends FormFieldName<Values>>(\n field: Field,\n value: Values[Field],\n ) => Effect.Effect<void, never, never>;\n readonly reset: Effect.Effect<void, never, never>;\n readonly validate: Effect.Effect<FormValidationResult<Values>, never, never>;\n readonly submit: <A, E, R = never>(\n handler: FormSubmitHandler<Values, A, E, R>,\n ) => Effect.Effect<A, E | FormValidationError<Values>, R>;\n}\n","import { Effect, ParseResult, Schema, SubscriptionRef } from \"effect\";\nimport {\n type FormContract,\n type FormErrorKey,\n type FormErrors,\n type FormFieldName,\n type FormState,\n type FormStore,\n FormValidationError,\n type FormValidationResult,\n type FormValues,\n} from \"./types\";\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst cloneUnknown = (value: unknown): unknown => {\n if (Array.isArray(value)) {\n return value.map((entry) => cloneUnknown(entry));\n }\n\n if (isRecord(value)) {\n const clone: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(value)) {\n clone[key] = cloneUnknown(entry);\n }\n return clone;\n }\n\n return value;\n};\n\nconst cloneValue = <A>(value: A): A => {\n return cloneUnknown(value) as A;\n};\n\nconst deepEqual = (left: unknown, right: unknown): boolean => {\n if (Object.is(left, right)) {\n return true;\n }\n\n if (Array.isArray(left) && Array.isArray(right)) {\n if (left.length !== right.length) {\n return false;\n }\n for (let index = 0; index < left.length; index += 1) {\n if (!deepEqual(left[index], right[index])) {\n return false;\n }\n }\n return true;\n }\n\n if (isRecord(left) && isRecord(right)) {\n const leftKeys = Object.keys(left);\n const rightKeys = Object.keys(right);\n\n if (leftKeys.length !== rightKeys.length) {\n return false;\n }\n\n for (const key of leftKeys) {\n if (!(key in right)) {\n return false;\n }\n if (!deepEqual(left[key], right[key])) {\n return false;\n }\n }\n\n return true;\n }\n\n return false;\n};\n\nconst toFormErrors = <Values extends FormValues>(\n error: ParseResult.ParseError,\n fieldNames: ReadonlySet<string>,\n): FormErrors<Values> => {\n const formatted = ParseResult.ArrayFormatter.formatErrorSync(error);\n const errors: Partial<Record<FormErrorKey<Values>, string>> = {};\n\n for (const entry of formatted) {\n const root = entry.path[0];\n if (typeof root === \"string\" && fieldNames.has(root)) {\n const key = root as FormFieldName<Values>;\n errors[key] ??= entry.message;\n continue;\n }\n\n errors._form ??= entry.message;\n }\n\n if (formatted.length === 0 && errors._form === undefined) {\n errors._form = \"Invalid form values.\";\n }\n\n return errors;\n};\n\nconst createInitialState = <Values extends FormValues>(defaults: Values): FormState<Values> => ({\n values: cloneValue(defaults),\n errors: {} as FormErrors<Values>,\n touched: {} as FormState<Values>[\"touched\"],\n dirty: false,\n submitting: false,\n submitted: false,\n});\n\nexport const makeForm = <Values extends FormValues, Encoded = Values>(\n contract: FormContract<Values, Encoded>,\n): Effect.Effect<FormStore<Values>, never, never> =>\n Effect.gen(function* () {\n const decode = Schema.decodeUnknown(contract.schema, { errors: \"all\" });\n const defaults = cloneValue(contract.defaults);\n const fieldNames = new Set(Object.keys(defaults));\n const snapshots = yield* SubscriptionRef.make<FormState<Values>>(createInitialState(defaults));\n\n const validateCurrent = (\n current: FormState<Values>,\n ): Effect.Effect<FormValidationResult<Values>, never, never> =>\n decode(current.values).pipe(\n Effect.match({\n onFailure: (issue) => ({\n _tag: \"invalid\",\n errors: toFormErrors<Values>(issue, fieldNames),\n issue,\n }),\n onSuccess: (values) => ({\n _tag: \"valid\",\n values: cloneValue(values),\n }),\n }),\n );\n\n const setField: FormStore<Values>[\"setField\"] = (field, value) =>\n SubscriptionRef.update(snapshots, (current) => {\n const nextValues = {\n ...current.values,\n [field]: value,\n } as Values;\n\n const nextErrors: Partial<Record<FormErrorKey<Values>, string>> = {\n ...current.errors,\n };\n delete nextErrors[field];\n\n return {\n ...current,\n values: nextValues,\n errors: nextErrors,\n touched: {\n ...current.touched,\n [field]: true,\n },\n dirty: !deepEqual(nextValues, defaults),\n submitted: false,\n };\n }).pipe(Effect.asVoid);\n\n const reset: FormStore<Values>[\"reset\"] = SubscriptionRef.set(\n snapshots,\n createInitialState(defaults),\n ).pipe(Effect.asVoid);\n\n const validate: FormStore<Values>[\"validate\"] = Effect.gen(function* () {\n const current = yield* SubscriptionRef.get(snapshots);\n const result = yield* validateCurrent(current);\n\n if (result._tag === \"valid\") {\n yield* SubscriptionRef.update(snapshots, (snapshot) => ({\n ...snapshot,\n values: result.values,\n errors: {} as FormErrors<Values>,\n dirty: !deepEqual(result.values, defaults),\n }));\n } else {\n yield* SubscriptionRef.update(snapshots, (snapshot) => ({\n ...snapshot,\n errors: result.errors,\n }));\n }\n\n return result;\n });\n\n const submit: FormStore<Values>[\"submit\"] = (handler) =>\n Effect.gen(function* () {\n yield* SubscriptionRef.update(snapshots, (current) => ({\n ...current,\n submitting: true,\n submitted: false,\n }));\n\n const current = yield* SubscriptionRef.get(snapshots);\n const validation = yield* validateCurrent(current);\n\n if (validation._tag === \"invalid\") {\n yield* SubscriptionRef.update(snapshots, (snapshot) => ({\n ...snapshot,\n submitting: false,\n submitted: false,\n errors: validation.errors,\n }));\n\n return yield* Effect.fail(new FormValidationError(validation.errors));\n }\n\n yield* SubscriptionRef.update(snapshots, (snapshot) => ({\n ...snapshot,\n values: validation.values,\n errors: {} as FormErrors<Values>,\n dirty: !deepEqual(validation.values, defaults),\n }));\n\n return yield* handler(validation.values).pipe(\n Effect.tap(() =>\n SubscriptionRef.update(snapshots, (snapshot) => ({\n ...snapshot,\n submitting: false,\n submitted: true,\n })).pipe(Effect.asVoid),\n ),\n Effect.tapError(() =>\n SubscriptionRef.update(snapshots, (snapshot) => ({\n ...snapshot,\n submitting: false,\n submitted: false,\n })).pipe(Effect.asVoid),\n ),\n );\n });\n\n return {\n getSnapshot: SubscriptionRef.get(snapshots),\n snapshots: snapshots.changes,\n setField,\n reset,\n validate,\n submit,\n } satisfies FormStore<Values>;\n });\n","import { Effect, Fiber, Stream } from \"effect\";\nimport { useCallback, useMemo, useSyncExternalStore } from \"react\";\nimport { useEffectRuntime } from \"../react/provider\";\nimport { makeForm } from \"./service\";\nimport type {\n FormContract,\n FormFieldName,\n FormState,\n FormStore,\n FormSubmitHandler,\n FormValidationResult,\n FormValues,\n} from \"./types\";\n\nconst useFormStore = <Values extends FormValues, Encoded = Values>(\n contract: FormContract<Values, Encoded>,\n): FormStore<Values> => {\n const runtime = useEffectRuntime();\n return useMemo(() => runtime.runSync(makeForm(contract)), [contract, runtime]);\n};\n\nexport interface UseFormResult<Values extends FormValues> extends FormState<Values> {\n readonly commands: Pick<FormStore<Values>, \"setField\" | \"reset\" | \"validate\" | \"submit\">;\n readonly setField: <Field extends FormFieldName<Values>>(\n field: Field,\n value: Values[Field],\n ) => Promise<void>;\n readonly reset: () => Promise<void>;\n readonly validate: () => Promise<FormValidationResult<Values>>;\n readonly submit: <A, E>(handler: FormSubmitHandler<Values, A, E, never>) => Promise<A>;\n}\n\nexport const useForm = <Values extends FormValues, Encoded = Values>(\n contract: FormContract<Values, Encoded>,\n): UseFormResult<Values> => {\n const runtime = useEffectRuntime();\n const form = useFormStore(contract);\n\n const subscribe = useCallback(\n (listener: () => void) => {\n const fiber = runtime.runFork(Stream.runForEach(form.snapshots, () => Effect.sync(listener)));\n\n return () => {\n runtime.runFork(Fiber.interrupt(fiber));\n };\n },\n [form, runtime],\n );\n\n const getSnapshot = useCallback(() => runtime.runSync(form.getSnapshot), [form, runtime]);\n\n const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n const setField = useCallback<UseFormResult<Values>[\"setField\"]>(\n (field, value) => runtime.runPromise(form.setField(field, value)),\n [form, runtime],\n );\n\n const reset = useCallback<UseFormResult<Values>[\"reset\"]>(\n () => runtime.runPromise(form.reset),\n [form, runtime],\n );\n\n const validate = useCallback<UseFormResult<Values>[\"validate\"]>(\n () => runtime.runPromise(form.validate),\n [form, runtime],\n );\n\n const submit = useCallback<UseFormResult<Values>[\"submit\"]>(\n (handler) => runtime.runPromise(form.submit(handler)),\n [form, runtime],\n );\n\n return {\n ...snapshot,\n commands: {\n setField: form.setField,\n reset: form.reset,\n validate: form.validate,\n submit: form.submit,\n },\n setField,\n reset,\n validate,\n submit,\n };\n};\n","import type { ManagedRuntime } from \"effect\";\nimport { createContext, useContext, useEffect, type ReactNode } from \"react\";\nimport type { AppServices } from \"../kernel/app\";\n\nexport type EffectReactManagedRuntime<R = AppServices> = ManagedRuntime.ManagedRuntime<R, never>;\n\nconst RuntimeContext = createContext<EffectReactManagedRuntime | null>(null);\n\nexport interface EffectProviderProps {\n readonly runtime: EffectReactManagedRuntime;\n readonly children?: ReactNode;\n}\n\nexport const EffectProvider = ({ runtime, children }: EffectProviderProps) => {\n useEffect(\n () => () => {\n void runtime.dispose();\n },\n [runtime],\n );\n\n return <RuntimeContext.Provider value={runtime}>{children}</RuntimeContext.Provider>;\n};\n\nexport const useEffectRuntime = (): EffectReactManagedRuntime => {\n const runtime = useContext(RuntimeContext);\n if (runtime === null) {\n throw new Error(\"Effect runtime is not available. Wrap your app with <EffectProvider>.\");\n }\n return runtime;\n};\n"],"mappings":"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","names":["current","import_effect","import_react"]}
@@ -0,0 +1,3 @@
1
+ export * from "./types";
2
+ export * from "./service";
3
+ export * from "./react";
@@ -0,0 +1,14 @@
1
+ import {
2
+ FormValidationError,
3
+ defineForm,
4
+ makeForm,
5
+ useForm
6
+ } from "../chunk-ZMZQBREU.js";
7
+ import "../chunk-2GIUCKL2.js";
8
+ export {
9
+ FormValidationError,
10
+ defineForm,
11
+ makeForm,
12
+ useForm
13
+ };
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,9 @@
1
+ import type { FormContract, FormFieldName, FormState, FormStore, FormSubmitHandler, FormValidationResult, FormValues } from "./types";
2
+ export interface UseFormResult<Values extends FormValues> extends FormState<Values> {
3
+ readonly commands: Pick<FormStore<Values>, "setField" | "reset" | "validate" | "submit">;
4
+ readonly setField: <Field extends FormFieldName<Values>>(field: Field, value: Values[Field]) => Promise<void>;
5
+ readonly reset: () => Promise<void>;
6
+ readonly validate: () => Promise<FormValidationResult<Values>>;
7
+ readonly submit: <A, E>(handler: FormSubmitHandler<Values, A, E, never>) => Promise<A>;
8
+ }
9
+ export declare const useForm: <Values extends FormValues, Encoded = Values>(contract: FormContract<Values, Encoded>) => UseFormResult<Values>;
@@ -0,0 +1,3 @@
1
+ import { Effect } from "effect";
2
+ import { type FormContract, type FormStore, type FormValues } from "./types";
3
+ export declare const makeForm: <Values extends FormValues, Encoded = Values>(contract: FormContract<Values, Encoded>) => Effect.Effect<FormStore<Values>, never, never>;
@@ -0,0 +1,41 @@
1
+ import type { Effect, ParseResult, Schema, Stream } from "effect";
2
+ export type FormValues = Readonly<Record<string, unknown>>;
3
+ export type FormFieldName<Values extends FormValues> = Extract<keyof Values, string>;
4
+ export type FormErrorKey<Values extends FormValues> = FormFieldName<Values> | "_form";
5
+ export type FormErrors<Values extends FormValues> = Readonly<Partial<Record<FormErrorKey<Values>, string>>>;
6
+ export type FormTouched<Values extends FormValues> = Readonly<Partial<Record<FormFieldName<Values>, boolean>>>;
7
+ export interface FormContract<Values extends FormValues, Encoded = Values> {
8
+ readonly schema: Schema.Schema<Values, Encoded, never>;
9
+ readonly defaults: Values;
10
+ }
11
+ export declare const defineForm: <Values extends FormValues, Encoded = Values>(contract: FormContract<Values, Encoded>) => FormContract<Values, Encoded>;
12
+ export interface FormState<Values extends FormValues> {
13
+ readonly values: Values;
14
+ readonly errors: FormErrors<Values>;
15
+ readonly touched: FormTouched<Values>;
16
+ readonly dirty: boolean;
17
+ readonly submitting: boolean;
18
+ readonly submitted: boolean;
19
+ }
20
+ export type FormValidationResult<Values extends FormValues> = {
21
+ readonly _tag: "valid";
22
+ readonly values: Values;
23
+ } | {
24
+ readonly _tag: "invalid";
25
+ readonly errors: FormErrors<Values>;
26
+ readonly issue: ParseResult.ParseError;
27
+ };
28
+ export declare class FormValidationError<Values extends FormValues> extends Error {
29
+ readonly errors: FormErrors<Values>;
30
+ readonly _tag: "FormValidationError";
31
+ constructor(errors: FormErrors<Values>);
32
+ }
33
+ export type FormSubmitHandler<Values extends FormValues, A, E, R = never> = (values: Values) => Effect.Effect<A, E, R>;
34
+ export interface FormStore<Values extends FormValues> {
35
+ readonly getSnapshot: Effect.Effect<FormState<Values>, never, never>;
36
+ readonly snapshots: Stream.Stream<FormState<Values>>;
37
+ readonly setField: <Field extends FormFieldName<Values>>(field: Field, value: Values[Field]) => Effect.Effect<void, never, never>;
38
+ readonly reset: Effect.Effect<void, never, never>;
39
+ readonly validate: Effect.Effect<FormValidationResult<Values>, never, never>;
40
+ readonly submit: <A, E, R = never>(handler: FormSubmitHandler<Values, A, E, R>) => Effect.Effect<A, E | FormValidationError<Values>, R>;
41
+ }
@@ -0,0 +1,21 @@
1
+ import { type AnyActionDefinition } from "../actions";
2
+ import { type AppServices } from "../kernel/app";
3
+ import type { AppManagedRuntime } from "../kernel/runtime";
4
+ import { type AppManifest } from "./manifest";
5
+ import { type EffectReactConfig, type EffectReactResolvedConfig } from "../config";
6
+ import type { AnyPageDefinition } from "./contracts";
7
+ export interface CreateAppOptions {
8
+ readonly manifest: AppManifest;
9
+ readonly config?: EffectReactConfig;
10
+ readonly initialHref?: string;
11
+ }
12
+ export interface EffectReactApp {
13
+ readonly manifest: AppManifest;
14
+ readonly config: EffectReactResolvedConfig;
15
+ readonly runtime: AppManagedRuntime<AppServices>;
16
+ readonly actions: readonly AnyActionDefinition[];
17
+ readonly matchPage: (href: string) => AnyPageDefinition | undefined;
18
+ readonly handleActionRequest: (request: Request) => Promise<Response>;
19
+ readonly dispose: () => Promise<void>;
20
+ }
21
+ export declare const createApp: (options: CreateAppOptions) => EffectReactApp;
@@ -0,0 +1,10 @@
1
+ import type { Duration } from "effect";
2
+ export type CacheMode = "no-store" | "force-cache";
3
+ export interface CachePolicy {
4
+ readonly mode: CacheMode;
5
+ readonly ttl?: Duration.DurationInput;
6
+ readonly tags?: readonly string[];
7
+ readonly key?: string;
8
+ }
9
+ export declare const cachePolicy: (policy: CachePolicy) => CachePolicy;
10
+ export declare const noStore: () => CachePolicy;
@@ -0,0 +1,32 @@
1
+ import type { Effect, Layer } from "effect";
2
+ import type { ComponentType } from "react";
3
+ import type { AnyLoaderDefinition, AnyRouteDefinition } from "../navigation";
4
+ import type { CachePolicy } from "./cache";
5
+ export interface PageDefinition<TRoute extends AnyRouteDefinition = AnyRouteDefinition> {
6
+ readonly id: string;
7
+ readonly route: TRoute;
8
+ readonly loader?: AnyLoaderDefinition;
9
+ readonly cache?: CachePolicy;
10
+ readonly component: ComponentType;
11
+ }
12
+ export type AnyPageDefinition = PageDefinition<AnyRouteDefinition>;
13
+ export interface LayoutDefinition {
14
+ readonly id: string;
15
+ readonly component: ComponentType<{
16
+ readonly children?: unknown;
17
+ }>;
18
+ readonly loader?: AnyLoaderDefinition;
19
+ }
20
+ export interface MiddlewareDefinition<R = never> {
21
+ readonly provide?: Layer.Layer<R, never, never>;
22
+ readonly use: (options: {
23
+ readonly request: Request;
24
+ readonly next: () => Effect.Effect<Response, unknown, R>;
25
+ }) => Effect.Effect<Response, unknown, R>;
26
+ }
27
+ export declare const definePage: <TRoute extends AnyRouteDefinition>(page: PageDefinition<TRoute>) => PageDefinition<TRoute>;
28
+ export declare const defineLayout: (layout: LayoutDefinition) => LayoutDefinition;
29
+ export declare const defineMiddleware: <R>(middleware: MiddlewareDefinition<R>) => MiddlewareDefinition<R>;
30
+ export type { CachePolicy } from "./cache";
31
+ export { defineRoute, defineLoader, type RouteDefinition, type LoaderDefinition, type AnyRouteDefinition, type AnyLoaderDefinition, } from "../navigation";
32
+ export { defineAction, type ActionDefinition, type AnyActionDefinition, } from "../actions";