foldkit 0.165.0 → 0.166.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.
Files changed (34) hide show
  1. package/README.md +2 -1
  2. package/dist/devTools/serialize.d.ts +8 -4
  3. package/dist/devTools/serialize.d.ts.map +1 -1
  4. package/dist/devTools/serialize.js +12 -8
  5. package/dist/experimental/index.d.ts +1 -0
  6. package/dist/experimental/index.d.ts.map +1 -1
  7. package/dist/experimental/index.js +1 -0
  8. package/dist/experimental/query/define.d.ts +15 -0
  9. package/dist/experimental/query/define.d.ts.map +1 -0
  10. package/dist/experimental/query/define.js +10 -0
  11. package/dist/experimental/query/index.d.ts +4 -0
  12. package/dist/experimental/query/index.d.ts.map +1 -0
  13. package/dist/experimental/query/index.js +1 -0
  14. package/dist/experimental/query/internal.d.ts +57 -0
  15. package/dist/experimental/query/internal.d.ts.map +1 -0
  16. package/dist/experimental/query/internal.js +25 -0
  17. package/dist/experimental/query/keyedQuery.d.ts +89 -0
  18. package/dist/experimental/query/keyedQuery.d.ts.map +1 -0
  19. package/dist/experimental/query/keyedQuery.js +144 -0
  20. package/dist/experimental/query/public.d.ts +4 -0
  21. package/dist/experimental/query/public.d.ts.map +1 -0
  22. package/dist/experimental/query/public.js +1 -0
  23. package/dist/experimental/query/query.d.ts +78 -0
  24. package/dist/experimental/query/query.d.ts.map +1 -0
  25. package/dist/experimental/query/query.js +102 -0
  26. package/dist/managedResource/managedResource.d.ts +7 -7
  27. package/dist/managedResource/managedResource.d.ts.map +1 -1
  28. package/dist/managedResource/managedResource.js +7 -7
  29. package/dist/runtime/devToolsConfig.d.ts +2 -0
  30. package/dist/runtime/devToolsConfig.d.ts.map +1 -1
  31. package/dist/subscription/subscription.d.ts +33 -72
  32. package/dist/subscription/subscription.d.ts.map +1 -1
  33. package/dist/subscription/subscription.js +31 -61
  34. package/package.json +5 -1
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
  <a href="https://www.npmjs.com/package/foldkit"><img src="https://img.shields.io/npm/v/foldkit" alt="npm version"></a>
11
11
  </p>
12
12
 
13
- <h3 align="center">The frontend framework for correctness.</h3>
13
+ <h3 align="center">Build faster. Understand what ships.</h3>
14
14
 
15
15
  <p align="center">
16
16
  <a href="https://foldkit.dev"><strong>Documentation</strong></a> · <a href="https://foldkit.dev/introduction/why-foldkit"><strong>Why Foldkit</strong></a> · <a href="https://foldkit.dev/example-apps"><strong>Examples</strong></a> · <a href="https://foldkit.dev/get-started"><strong>Get Started</strong></a> · <a href="https://discord.gg/kav8VNxqGm"><strong>Discord</strong></a>
@@ -153,6 +153,7 @@ Some of what you can build with Foldkit. [See all example apps on foldkit.dev](h
153
153
  - **[Job Application](https://foldkit.dev/example-apps/job-application)**: Multi-step form with cross-field validation, file uploads, and per-step error indicators
154
154
  - **[Weather](https://foldkit.dev/example-apps/weather)**: HTTP requests with async state handling
155
155
  - **[API Cache](https://foldkit.dev/example-apps/api-cache)**: Query caching with stale-while-revalidate, request deduplication, and interval refetching
156
+ - **[API Cache Query](https://foldkit.dev/example-apps/api-cache-query)**: The same app with experimental `Query.define` owning fetch and keyed slots
156
157
  - **[Routing](https://foldkit.dev/example-apps/routing)**: URL routing with parser combinators
157
158
  - **[Route Transitions](https://foldkit.dev/example-apps/route-transitions)**: Live transition log with entry, exit, and stayed navigation policies
158
159
  - **[Query Sync](https://foldkit.dev/example-apps/query-sync)**: URL query parameter sync with filtering and sorting
@@ -1,7 +1,8 @@
1
1
  import type { SerializedCommand, SerializedEntry, SerializedMount } from './protocol.js';
2
2
  import type { CommandRecord, HistoryEntry, MountRecord } from './store.js';
3
3
  /**
4
- * Convert DOM-class instances (File, Blob, Date, URL) to plain-object
4
+ * Convert Effect `Redacted` values to their labeled placeholder strings and
5
+ * DOM-class instances (File, Blob, Date, URL) to plain-object
5
6
  * representations so the tree renderer's key-enumeration walk can see their
6
7
  * meaningful data, which otherwise lives on the prototype as getters and
7
8
  * is invisible to `Object.keys`. Recurses through arrays and records so
@@ -22,20 +23,23 @@ export declare const toInspectableValue: (value: unknown) => unknown;
22
23
  /**
23
24
  * Convert a runtime `CommandRecord` to its wire shape. Args are wrapped in an
24
25
  * `Option` so `None` cleanly distinguishes argless Commands from Commands that
25
- * happen to have an empty args record.
26
+ * happen to have an empty args record. Nested Effect `Redacted` values become
27
+ * their placeholder strings before transmission.
26
28
  */
27
29
  export declare const toSerializedCommand: (command: CommandRecord) => SerializedCommand;
28
30
  /**
29
31
  * Convert a runtime `MountRecord` to its wire shape. Args are wrapped in an
30
32
  * `Option` so `None` cleanly distinguishes argless Mounts from Mounts that
31
- * happen to have an empty args record.
33
+ * happen to have an empty args record. Nested Effect `Redacted` values become
34
+ * their placeholder strings before transmission.
32
35
  */
33
36
  export declare const toSerializedMount: (mount: MountRecord) => SerializedMount;
34
37
  /**
35
38
  * Convert a `HistoryEntry` plus its absolute index into the wire-friendly
36
39
  * `SerializedEntry` shape. Flattens the diff's `HashSet` path collections to
37
40
  * plain string arrays for JSON transmission and runs the message body through
38
- * `toInspectableValue` so DOM-class instances become inspectable objects.
41
+ * `toInspectableValue` so Effect `Redacted` values become placeholders and
42
+ * DOM-class instances become inspectable objects.
39
43
  */
40
44
  export declare const toSerializedEntry: (entry: HistoryEntry, index: number) => SerializedEntry;
41
45
  //# sourceMappingURL=serialize.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../../src/devTools/serialize.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EACf,eAAe,EAChB,MAAM,eAAe,CAAA;AACtB,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAwB1E;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,kBAAkB,UAAW,OAAO,KAAG,OAUnD,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,YACrB,aAAa,KACrB,iBAID,CAAA;AAEF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,UAAW,WAAW,KAAG,eAGrD,CAAA;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,UACrB,YAAY,SACZ,MAAM,KACZ,eAoBF,CAAA"}
1
+ {"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../../src/devTools/serialize.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EACf,eAAe,EAChB,MAAM,eAAe,CAAA;AACtB,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAyB1E;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,kBAAkB,UAAW,OAAO,KAAG,OAUnD,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,YACrB,aAAa,KACrB,iBAOD,CAAA;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,UAAW,WAAW,KAAG,eAMrD,CAAA;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,UACrB,YAAY,SACZ,MAAM,KACZ,eAoBF,CAAA"}
@@ -1,7 +1,7 @@
1
- import { Array, Function, Match, Option, Predicate, Record } from 'effect';
1
+ import { Array, Function, Match, Option, Predicate, Record, Redacted, } from 'effect';
2
2
  import { extractSubmodelInfo } from './submodelPath.js';
3
3
  const inspectableCache = new WeakMap();
4
- const computeInspectableValue = (value) => Match.value(value).pipe(Match.when(Match.instanceOf(File), file => ({
4
+ const computeInspectableValue = (value) => Match.value(value).pipe(Match.when(Redacted.isRedacted, globalThis.String), Match.when(Match.instanceOf(File), file => ({
5
5
  name: file.name,
6
6
  size: file.size,
7
7
  type: file.type,
@@ -11,7 +11,8 @@ const computeInspectableValue = (value) => Match.value(value).pipe(Match.when(Ma
11
11
  type: blob.type,
12
12
  })), Match.when(Match.instanceOf(Date), date => date.toISOString()), Match.when(Match.instanceOf(URL), ({ href }) => href), Match.when(globalThis.Array.isArray, Array.map(toInspectableValue)), Match.when(Predicate.isObject, Record.map(toInspectableValue)), Match.orElse(Function.identity));
13
13
  /**
14
- * Convert DOM-class instances (File, Blob, Date, URL) to plain-object
14
+ * Convert Effect `Redacted` values to their labeled placeholder strings and
15
+ * DOM-class instances (File, Blob, Date, URL) to plain-object
15
16
  * representations so the tree renderer's key-enumeration walk can see their
16
17
  * meaningful data, which otherwise lives on the prototype as getters and
17
18
  * is invisible to `Object.keys`. Recurses through arrays and records so
@@ -42,27 +43,30 @@ export const toInspectableValue = (value) => {
42
43
  /**
43
44
  * Convert a runtime `CommandRecord` to its wire shape. Args are wrapped in an
44
45
  * `Option` so `None` cleanly distinguishes argless Commands from Commands that
45
- * happen to have an empty args record.
46
+ * happen to have an empty args record. Nested Effect `Redacted` values become
47
+ * their placeholder strings before transmission.
46
48
  */
47
49
  export const toSerializedCommand = (command) => ({
48
50
  name: command.name,
49
- args: Option.fromNullishOr(command.args),
51
+ args: Option.map(Option.fromNullishOr(command.args), Record.map(toInspectableValue)),
50
52
  maybeSubmodelPath: command.maybeSubmodelPath,
51
53
  });
52
54
  /**
53
55
  * Convert a runtime `MountRecord` to its wire shape. Args are wrapped in an
54
56
  * `Option` so `None` cleanly distinguishes argless Mounts from Mounts that
55
- * happen to have an empty args record.
57
+ * happen to have an empty args record. Nested Effect `Redacted` values become
58
+ * their placeholder strings before transmission.
56
59
  */
57
60
  export const toSerializedMount = (mount) => ({
58
61
  name: mount.name,
59
- args: Option.fromNullishOr(mount.args),
62
+ args: Option.map(Option.fromNullishOr(mount.args), Record.map(toInspectableValue)),
60
63
  });
61
64
  /**
62
65
  * Convert a `HistoryEntry` plus its absolute index into the wire-friendly
63
66
  * `SerializedEntry` shape. Flattens the diff's `HashSet` path collections to
64
67
  * plain string arrays for JSON transmission and runs the message body through
65
- * `toInspectableValue` so DOM-class instances become inspectable objects.
68
+ * `toInspectableValue` so Effect `Redacted` values become placeholders and
69
+ * DOM-class instances become inspectable objects.
66
70
  */
67
71
  export const toSerializedEntry = (entry, index) => {
68
72
  const { submodelPath, maybeLeafTag } = extractSubmodelInfo(entry.tag, entry.message);
@@ -1,3 +1,4 @@
1
1
  export * as Machine from './machine/index.js';
2
+ export * as Query from './query/index.js';
2
3
  export * as Server from './server/index.js';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/experimental/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,oBAAoB,CAAA;AAC7C,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/experimental/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,oBAAoB,CAAA;AAC7C,OAAO,KAAK,KAAK,MAAM,kBAAkB,CAAA;AACzC,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAA"}
@@ -1,2 +1,3 @@
1
1
  export * as Machine from './machine/index.js';
2
+ export * as Query from './query/index.js';
2
3
  export * as Server from './server/index.js';
@@ -0,0 +1,15 @@
1
+ import { type KeyedQuery, type KeyedQueryConfig, type SyncFields } from './keyedQuery.js';
2
+ import { type Query, type QueryConfig } from './query.js';
3
+ /**
4
+ * Defines a Submodel that fetches data and retains it in the application
5
+ * Model. Add `args` to define a {@link KeyedQuery}; omit them to define a
6
+ * {@link Query}.
7
+ *
8
+ * @experimental Ships from `foldkit/experimental/query`; expect breaking changes while the API settles.
9
+ */
10
+ export declare function define<Name extends string, A, AI, E, EI, Fields extends SyncFields, R = never>(config: KeyedQueryConfig<Name, A, AI, E, EI, Fields, R>): KeyedQuery<Name, A, AI, E, EI, Fields, R>;
11
+ export declare function define<Name extends string, A, AI, E, EI, R = never>(config: QueryConfig<Name, A, AI, E, EI, R> & {
12
+ readonly args?: never;
13
+ readonly toKey?: never;
14
+ }): Query<Name, A, AI, E, EI, R>;
15
+ //# sourceMappingURL=define.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../../../src/experimental/query/define.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,UAAU,EAEhB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,WAAW,EAAe,MAAM,YAAY,CAAA;AA6BtE;;;;;;GAMG;AACH,wBAAgB,MAAM,CACpB,IAAI,SAAS,MAAM,EACnB,CAAC,EACD,EAAE,EACF,CAAC,EACD,EAAE,EACF,MAAM,SAAS,UAAU,EACzB,CAAC,GAAG,KAAK,EAET,MAAM,EAAE,gBAAgB,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,GACtD,UAAU,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;AAC5C,wBAAgB,MAAM,CAAC,IAAI,SAAS,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,KAAK,EACjE,MAAM,EAAE,WAAW,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,GAAG;IAC3C,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CACvB,GACA,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA"}
@@ -0,0 +1,10 @@
1
+ import { Predicate } from 'effect';
2
+ import { defineKeyedQuery, } from './keyedQuery.js';
3
+ import { defineQuery } from './query.js';
4
+ const isKeyedQueryConfig = (config) => Predicate.hasProperty(config, 'args');
5
+ export function define(config) {
6
+ if (isKeyedQueryConfig(config)) {
7
+ return defineKeyedQuery(config);
8
+ }
9
+ return defineQuery(config);
10
+ }
@@ -0,0 +1,4 @@
1
+ export { define } from './define.js';
2
+ export type { KeyedQuery } from './keyedQuery.js';
3
+ export type { Query } from './query.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/experimental/query/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AACjD,YAAY,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA"}
@@ -0,0 +1 @@
1
+ export { define } from './define.js';
@@ -0,0 +1,57 @@
1
+ import { Effect, Option, Schema } from 'effect';
2
+ import * as AsyncData from '../../asyncData/index.js';
3
+ import * as Command from '../../command/index.js';
4
+ import * as Update from '../../update/index.js';
5
+ export type FoldLens<ParentModel, ParentMessage, ChildModel, ChildMessage> = Pick<Update.ChildFold<ParentModel, ParentMessage, ChildModel, never, ChildMessage>, 'read' | 'write' | 'toParentMessage'>;
6
+ export type ParentFieldOf<ParentModel, ChildModel> = Extract<{
7
+ [K in keyof ParentModel]-?: ParentModel[K] extends ChildModel ? K : never;
8
+ }[keyof ParentModel], string>;
9
+ export type ParentFieldConfig<ParentModel, ParentMessage, ChildModel, ChildMessage> = Readonly<{
10
+ parentField: ParentFieldOf<ParentModel, ChildModel>;
11
+ toParentMessage: (message: ChildMessage) => ParentMessage;
12
+ }>;
13
+ export type LiftConfig<ParentModel, ParentMessage, ChildModel, ChildMessage> = ParentFieldConfig<ParentModel, ParentMessage, ChildModel, ChildMessage> | FoldLens<ParentModel, ParentMessage, ChildModel, ChildMessage>;
14
+ export type LiftQuery<ChildModel, ChildMessage, R> = {
15
+ <ParentModel, ParentMessage>(config: ParentFieldConfig<ParentModel, ParentMessage, ChildModel, ChildMessage>): LiftedQuery<ParentModel, ParentMessage, ChildMessage, R>;
16
+ <ParentModel, ParentMessage>(config: FoldLens<ParentModel, ParentMessage, ChildModel, ChildMessage>): LiftedQuery<ParentModel, ParentMessage, ChildMessage, R>;
17
+ };
18
+ export type LiftKeyedQuery<ChildModel, ChildMessage, Args, R> = {
19
+ <ParentModel, ParentMessage>(config: ParentFieldConfig<ParentModel, ParentMessage, ChildModel, ChildMessage>): LiftedKeyedQuery<ParentModel, ParentMessage, ChildMessage, Args, R>;
20
+ <ParentModel, ParentMessage>(config: FoldLens<ParentModel, ParentMessage, ChildModel, ChildMessage>): LiftedKeyedQuery<ParentModel, ParentMessage, ChildMessage, Args, R>;
21
+ };
22
+ export declare const isParentFieldConfig: <ParentModel, ParentMessage, ChildModel, ChildMessage>(config: LiftConfig<ParentModel, ParentMessage, ChildModel, ChildMessage>) => config is Extract<LiftConfig<ParentModel, ParentMessage, ChildModel, ChildMessage>, {
23
+ readonly parentField: string;
24
+ }>;
25
+ export declare const parentFieldToLens: <ParentModel extends Record<ParentFieldOf<ParentModel, ChildModel>, ChildModel>, ParentMessage, ChildModel, ChildMessage>(config: ParentFieldConfig<ParentModel, ParentMessage, ChildModel, ChildMessage>) => FoldLens<ParentModel, ParentMessage, ChildModel, ChildMessage>;
26
+ export declare const liftChildFold: <ParentModel, ParentMessage, ChildModel, ChildMessage, Input, R>(childFold: Update.Fold<ChildModel, ChildMessage, Input, R>, lens: FoldLens<ParentModel, ParentMessage, ChildModel, ChildMessage>) => Update.Fold<ParentModel, ParentMessage, Input, R>;
27
+ export type AsyncDataTransition = <A, E>(data: AsyncData.AsyncData<A, E>) => Option.Option<AsyncData.AsyncData<A, E>>;
28
+ export type QueryStore<Model, Args, A, E, Message, R> = Readonly<{
29
+ read: (model: Model, args: Args) => AsyncData.AsyncData<A, E>;
30
+ start: (model: Model, args: Args, data: AsyncData.AsyncData<A, E>) => Readonly<{
31
+ model: Model;
32
+ generation: number;
33
+ }>;
34
+ fetch: (args: Args, generation: number) => Command.Command<Message, never, R>;
35
+ }>;
36
+ export declare const applyTransition: <Model, Args, A, E, Message, R>(store: QueryStore<Model, Args, A, E, Message, R>, model: Model, args: Args, transition: AsyncDataTransition) => Update.Return<Model, Message, R>;
37
+ export declare const runExecute: <A, E, R>(execute: Effect.Effect<A, E, R>) => Effect.Effect<AsyncData.AsyncData<A, E>, never, R>;
38
+ export type CompletedFetchOf<Message extends Schema.Top> = Extract<Message['Type'], {
39
+ readonly _tag: 'CompletedFetch';
40
+ }>;
41
+ export type KeyedArgs<Fields extends Schema.Struct.Fields> = Schema.Schema.Type<Schema.Struct<Fields>>;
42
+ type LiftedQuery<ParentModel, ParentMessage, ChildMessage, R> = Readonly<{
43
+ fold: Update.Fold<ParentModel, ParentMessage, ChildMessage>;
44
+ reset: Update.Step<ParentModel, ParentMessage>;
45
+ revalidate: Update.Step<ParentModel, ParentMessage, R>;
46
+ revalidateOrLoad: Update.Step<ParentModel, ParentMessage, R>;
47
+ loadIfMissing: Update.Step<ParentModel, ParentMessage, R>;
48
+ }>;
49
+ type LiftedKeyedQuery<ParentModel, ParentMessage, ChildMessage, Args, R> = Readonly<{
50
+ fold: Update.Fold<ParentModel, ParentMessage, ChildMessage>;
51
+ reset: Update.Step<ParentModel, ParentMessage>;
52
+ revalidate: Update.Fold<ParentModel, ParentMessage, Args, R>;
53
+ revalidateOrLoad: Update.Fold<ParentModel, ParentMessage, Args, R>;
54
+ loadIfMissing: Update.Fold<ParentModel, ParentMessage, Args, R>;
55
+ }>;
56
+ export {};
57
+ //# sourceMappingURL=internal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../../../src/experimental/query/internal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { Effect, Option, Predicate, pipe } from 'effect';
2
+ import * as AsyncData from '../../asyncData/index.js';
3
+ import * as Update from '../../update/index.js';
4
+ export const isParentFieldConfig = (config) => Predicate.hasProperty(config, 'parentField');
5
+ const setField = (model, field, value) => ({ ...model, [field]: value });
6
+ export const parentFieldToLens = (config) => ({
7
+ read: (model) => Option.some(model[config.parentField]),
8
+ write: (model, nextChild) => setField(model, config.parentField, nextChild),
9
+ toParentMessage: config.toParentMessage,
10
+ });
11
+ export const liftChildFold = (childFold, lens) => Update.foldChild({
12
+ update: (childModel, input) => childFold(childModel, input),
13
+ ...lens,
14
+ });
15
+ export const applyTransition = (store, model, args, transition) => Option.match(transition(store.read(model, args)), {
16
+ onNone: () => ({ model }),
17
+ onSome: nextData => {
18
+ const queryStart = store.start(model, args, nextData);
19
+ return {
20
+ model: queryStart.model,
21
+ commands: [store.fetch(args, queryStart.generation)],
22
+ };
23
+ },
24
+ });
25
+ export const runExecute = (execute) => pipe(execute, Effect.result, Effect.map(result => AsyncData.settle(AsyncData.Loading(), result)));
@@ -0,0 +1,89 @@
1
+ import { Effect, Schema } from 'effect';
2
+ import * as AsyncData from '../../asyncData/index.js';
3
+ import * as Command from '../../command/index.js';
4
+ import * as Update from '../../update/index.js';
5
+ import { type CompletedFetchOf, type KeyedArgs, type LiftKeyedQuery } from './internal.js';
6
+ export type SyncFields = {
7
+ readonly [x: PropertyKey]: Schema.Codec<unknown, unknown, never, never>;
8
+ };
9
+ export type KeyedQueryConfig<Name extends string, A, AI, E, EI, Fields extends SyncFields, R> = Readonly<{
10
+ name: Name;
11
+ data: Schema.Codec<A, AI, never, never>;
12
+ error: Schema.Codec<E, EI, never, never>;
13
+ args: Fields;
14
+ toKey?: (args: Schema.Schema.Type<Schema.Struct<Fields>>) => string;
15
+ execute: (args: Schema.Schema.Type<Schema.Struct<Fields>>) => Effect.Effect<A, E, R>;
16
+ }>;
17
+ declare const makeKeyedQueryMessage: <A, AI, E, EI, Fields extends SyncFields>(data: Schema.Codec<A, AI>, error: Schema.Codec<E, EI>, Args: Schema.Struct<Fields>) => import("../../schema/index.js").MessageUnion<{
18
+ readonly CompletedFetch: {
19
+ args: Schema.Struct<Fields>;
20
+ generation: Schema.Number;
21
+ result: Schema.Result<Schema.Codec<A, AI, never, never>, Schema.Codec<E, EI, never, never>>;
22
+ };
23
+ }>;
24
+ /**
25
+ * Schema-backed Message union dispatched when a KeyedQuery fetch completes.
26
+ *
27
+ * @experimental Ships from `foldkit/experimental/query`; expect breaking changes while the API settles.
28
+ */
29
+ export type KeyedQueryMessage<A, AI, E, EI, Fields extends SyncFields> = ReturnType<typeof makeKeyedQueryMessage<A, AI, E, EI, Fields>>;
30
+ /** Builds the Model Schema for a KeyedQuery. */
31
+ export declare const makeKeyedQueryModel: <A, AI, E, EI, Fields extends SyncFields>(data: Schema.Codec<A, AI>, error: Schema.Codec<E, EI>, Args: Schema.Struct<Fields>) => Schema.Struct<{
32
+ readonly generation: Schema.Number;
33
+ readonly entries: Schema.HashMap<Schema.String, Schema.Struct<{
34
+ readonly args: Schema.Struct<Fields>;
35
+ readonly data: Schema.Codec<AsyncData.AsyncData<A, E>, AsyncData.AsyncDataEncoded<AI, EI>, never, never>;
36
+ readonly generation: Schema.Number;
37
+ }>>;
38
+ }>;
39
+ /**
40
+ * Model Schema for a KeyedQuery containing retained `AsyncData` entries.
41
+ *
42
+ * @experimental Ships from `foldkit/experimental/query`; expect breaking changes while the API settles.
43
+ */
44
+ export type KeyedQueryModel<A, AI, E, EI, Fields extends SyncFields> = ReturnType<typeof makeKeyedQueryModel<A, AI, E, EI, Fields>>;
45
+ /**
46
+ * Submodel for fetching and retaining `AsyncData` values by argument key.
47
+ *
48
+ * @experimental Ships from `foldkit/experimental/query`; expect breaking changes while the API settles.
49
+ */
50
+ export interface KeyedQuery<Name extends string, A, AI, E, EI, Fields extends SyncFields, R = never> {
51
+ /** Schema for this KeyedQuery's Model. */
52
+ readonly Model: KeyedQueryModel<A, AI, E, EI, Fields>;
53
+ /** Schema-backed union of Messages handled by this KeyedQuery. */
54
+ readonly Message: KeyedQueryMessage<A, AI, E, EI, Fields>;
55
+ /**
56
+ * Command definition for matching this KeyedQuery's pending fetch in Story
57
+ * and Scene tests. Start fetches through a loading operation so the Model
58
+ * and request generation advance together; do not call this directly.
59
+ */
60
+ readonly Fetch: Command.CommandDefinitionWithArgs<`Fetch${Name}`, {
61
+ readonly args: Schema.Struct<Fields>;
62
+ readonly generation: typeof Schema.Number;
63
+ }, Effect.Effect<CompletedFetchOf<KeyedQueryMessage<A, AI, E, EI, Fields>>, never, R>>;
64
+ /**
65
+ * Creates a KeyedQuery Model for initial parent Model construction. Never
66
+ * replace a live KeyedQuery with `init()`: it can reuse an in-flight request
67
+ * generation. Use `reset` instead.
68
+ */
69
+ readonly init: () => KeyedQueryModel<A, AI, E, EI, Fields>['Type'];
70
+ /** Clears every entry while preserving request identity. */
71
+ readonly reset: (model: KeyedQueryModel<A, AI, E, EI, Fields>['Type']) => Update.Return<KeyedQueryModel<A, AI, E, EI, Fields>['Type'], KeyedQueryMessage<A, AI, E, EI, Fields>['Type']>;
72
+ /** Reads one entry, returning `Idle` when that entry does not exist. */
73
+ readonly read: (model: KeyedQueryModel<A, AI, E, EI, Fields>['Type'], args: KeyedArgs<Fields>) => AsyncData.AsyncData<A, E>;
74
+ /** Folds a keyed Fetch completion into the matching entry. */
75
+ readonly update: (model: KeyedQueryModel<A, AI, E, EI, Fields>['Type'], message: KeyedQueryMessage<A, AI, E, EI, Fields>['Type']) => Update.Return<KeyedQueryModel<A, AI, E, EI, Fields>['Type'], KeyedQueryMessage<A, AI, E, EI, Fields>['Type']>;
76
+ /** Refreshes a loaded entry and does nothing when it has no data. */
77
+ readonly revalidate: Update.Fold<KeyedQueryModel<A, AI, E, EI, Fields>['Type'], KeyedQueryMessage<A, AI, E, EI, Fields>['Type'], KeyedArgs<Fields>, R>;
78
+ /** Loads a missing entry or refreshes a loaded entry. */
79
+ readonly revalidateOrLoad: Update.Fold<KeyedQueryModel<A, AI, E, EI, Fields>['Type'], KeyedQueryMessage<A, AI, E, EI, Fields>['Type'], KeyedArgs<Fields>, R>;
80
+ /** Loads an entry only when it has no usable value. */
81
+ readonly loadIfMissing: Update.Fold<KeyedQueryModel<A, AI, E, EI, Fields>['Type'], KeyedQueryMessage<A, AI, E, EI, Fields>['Type'], KeyedArgs<Fields>, R>;
82
+ /** Lifts this KeyedQuery's update and loading operations into a parent Model. */
83
+ readonly lift: LiftKeyedQuery<KeyedQueryModel<A, AI, E, EI, Fields>['Type'], KeyedQueryMessage<A, AI, E, EI, Fields>['Type'], KeyedArgs<Fields>, R>;
84
+ /** Executes one keyed fetch directly and returns settled `AsyncData`. */
85
+ readonly run: (args: KeyedArgs<Fields>) => Effect.Effect<AsyncData.AsyncData<A, E>, never, R>;
86
+ }
87
+ export declare function defineKeyedQuery<Name extends string, A, AI, E, EI, Fields extends SyncFields, R>(config: KeyedQueryConfig<Name, A, AI, E, EI, Fields, R>): KeyedQuery<Name, A, AI, E, EI, Fields, R>;
88
+ export {};
89
+ //# sourceMappingURL=keyedQuery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyedQuery.d.ts","sourceRoot":"","sources":["../../../src/experimental/query/keyedQuery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,144 @@
1
+ import { Array, Effect, Function, HashMap, Number, Option, Order, Predicate, Record, Schema, pipe, } from 'effect';
2
+ import * as AsyncData from '../../asyncData/index.js';
3
+ import * as Command from '../../command/index.js';
4
+ import { defineMessageUnion } from '../../message/index.js';
5
+ import { modifyFields } from '../../struct/index.js';
6
+ import * as Update from '../../update/index.js';
7
+ import { applyTransition, isParentFieldConfig, liftChildFold, parentFieldToLens, runExecute, } from './internal.js';
8
+ const canonicalizeJsonEntry = ([key, value]) => [key, canonicalizeJson(value)];
9
+ const jsonEntryOrder = Order.mapInput(Order.String, ([key]) => key);
10
+ const isJsonObject = (value) => Predicate.isObject(value) && !globalThis.Array.isArray(value);
11
+ const canonicalizeJson = (value) => {
12
+ if (globalThis.Array.isArray(value)) {
13
+ return Array.map(value, canonicalizeJson);
14
+ }
15
+ if (isJsonObject(value)) {
16
+ return pipe(value, Record.toEntries, Array.sort(jsonEntryOrder), Array.map(canonicalizeJsonEntry), Record.fromEntries);
17
+ }
18
+ return value;
19
+ };
20
+ const encodeJsonString = Schema.encodeUnknownSync(Schema.fromJsonString(Schema.Json));
21
+ const encodeKey = (schema) => {
22
+ const encodeJson = Schema.encodeUnknownSync(Schema.toCodecJson(schema));
23
+ return (value) => encodeJsonString(canonicalizeJson(encodeJson(value)));
24
+ };
25
+ const makeKeyedQueryMessage = (data, error, Args) => defineMessageUnion({
26
+ CompletedFetch: {
27
+ args: Args,
28
+ generation: Schema.Number,
29
+ result: Schema.Result(data, error),
30
+ },
31
+ });
32
+ /** Builds the Model Schema for a KeyedQuery. */
33
+ export const makeKeyedQueryModel = (data, error, Args) => {
34
+ const asyncData = AsyncData.Schema(data, error);
35
+ return Schema.Struct({
36
+ generation: Schema.Number,
37
+ entries: Schema.HashMap(Schema.String, Schema.Struct({
38
+ args: Args,
39
+ data: asyncData.schema,
40
+ generation: Schema.Number,
41
+ })),
42
+ });
43
+ };
44
+ export function defineKeyedQuery(config) {
45
+ const asyncData = AsyncData.Schema(config.data, config.error);
46
+ const Args = Schema.Struct(config.args);
47
+ if (Array.isArrayEmpty(Record.keys(config.args))) {
48
+ throw new Error(`Query.define("${config.name}"): keyed args must include at least one field`);
49
+ }
50
+ const argsToKey = config.toKey ?? encodeKey(Args);
51
+ const Message = makeKeyedQueryMessage(config.data, config.error, Args);
52
+ const Fetch = Command.define(`Fetch${config.name}`, {
53
+ args: { args: Args, generation: Schema.Number },
54
+ messages: [Message.CompletedFetch],
55
+ execute: ({ args, generation }) => pipe(config.execute(args), Effect.result, Effect.map((result) =>
56
+ // NOTE: CompletedFetch's constructor input view rejects args that are already the decoded Type.
57
+ ({
58
+ _tag: 'CompletedFetch',
59
+ args,
60
+ generation,
61
+ result,
62
+ }))),
63
+ });
64
+ const Model = makeKeyedQueryModel(config.data, config.error, Args);
65
+ const store = {
66
+ read: (model, args) => AsyncData.fromOptionOrIdle(Option.map(HashMap.get(model.entries, argsToKey(args)), entry => entry.data)),
67
+ start: (model, args, data) => {
68
+ const nextGeneration = Number.increment(model.generation);
69
+ return {
70
+ model: modifyFields(model, {
71
+ entries: HashMap.set(argsToKey(args), {
72
+ args,
73
+ data,
74
+ generation: nextGeneration,
75
+ }),
76
+ generation: () => nextGeneration,
77
+ }),
78
+ generation: nextGeneration,
79
+ };
80
+ },
81
+ fetch: (args, generation) => Fetch({ args, generation }),
82
+ };
83
+ const init = () => Model.make({ entries: HashMap.empty(), generation: 0 });
84
+ const reset = (model) => ({
85
+ model: modifyFields(model, { entries: () => HashMap.empty() }),
86
+ });
87
+ const read = (model, args) => store.read(model, args);
88
+ const liftTransition = (transition) => Function.dual(2, (model, args) => applyTransition(store, model, args, transition));
89
+ const revalidate = liftTransition(AsyncData.revalidate);
90
+ const revalidateOrLoad = liftTransition(AsyncData.revalidateOrLoad);
91
+ const loadIfMissing = liftTransition(AsyncData.loadIfMissing);
92
+ const update = (model, message) => Message.match(message, {
93
+ CompletedFetch({ args, generation, result }) {
94
+ const key = argsToKey(args);
95
+ const maybeEntry = HashMap.get(model.entries, key);
96
+ if (Option.isNone(maybeEntry)) {
97
+ return { model };
98
+ }
99
+ const entry = maybeEntry.value;
100
+ if (!AsyncData.isPending(entry.data) ||
101
+ generation !== entry.generation) {
102
+ return { model };
103
+ }
104
+ return {
105
+ model: modifyFields(model, {
106
+ entries: HashMap.set(key, modifyFields(entry, {
107
+ data: () => AsyncData.settle(entry.data, result),
108
+ })),
109
+ }),
110
+ };
111
+ },
112
+ });
113
+ const liftFromLens = (lens) => ({
114
+ fold: Update.foldChild({ update, ...lens }),
115
+ reset: Update.foldChildStep({
116
+ update: reset,
117
+ ...lens,
118
+ }),
119
+ revalidate: liftChildFold(revalidate, lens),
120
+ revalidateOrLoad: liftChildFold(revalidateOrLoad, lens),
121
+ loadIfMissing: liftChildFold(loadIfMissing, lens),
122
+ });
123
+ function lift(config) {
124
+ if (isParentFieldConfig(config)) {
125
+ return liftFromLens(parentFieldToLens(config));
126
+ }
127
+ return liftFromLens(config);
128
+ }
129
+ const run = (args) => runExecute(config.execute(args));
130
+ return {
131
+ Model,
132
+ Message,
133
+ Fetch,
134
+ init,
135
+ reset,
136
+ read,
137
+ update,
138
+ revalidate,
139
+ revalidateOrLoad,
140
+ loadIfMissing,
141
+ lift,
142
+ run,
143
+ };
144
+ }
@@ -0,0 +1,4 @@
1
+ export { define } from './index.js';
2
+ export type { KeyedQuery } from './keyedQuery.js';
3
+ export type { Query } from './query.js';
4
+ //# sourceMappingURL=public.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/experimental/query/public.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AACnC,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AACjD,YAAY,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA"}
@@ -0,0 +1 @@
1
+ export { define } from './index.js';
@@ -0,0 +1,78 @@
1
+ import { Effect, Schema } from 'effect';
2
+ import * as AsyncData from '../../asyncData/index.js';
3
+ import * as Command from '../../command/index.js';
4
+ import * as Update from '../../update/index.js';
5
+ import { type CompletedFetchOf, type LiftQuery } from './internal.js';
6
+ export type QueryConfig<Name extends string, A, AI, E, EI, R> = Readonly<{
7
+ name: Name;
8
+ data: Schema.Codec<A, AI, never, never>;
9
+ error: Schema.Codec<E, EI, never, never>;
10
+ execute: Effect.Effect<A, E, R>;
11
+ }>;
12
+ declare const makeQueryMessage: <A, AI, E, EI>(data: Schema.Codec<A, AI>, error: Schema.Codec<E, EI>) => import("../../schema/index.js").MessageUnion<{
13
+ readonly CompletedFetch: {
14
+ generation: Schema.Number;
15
+ result: Schema.Result<Schema.Codec<A, AI, never, never>, Schema.Codec<E, EI, never, never>>;
16
+ };
17
+ }>;
18
+ /**
19
+ * Schema-backed Message union dispatched when a Query Fetch completes.
20
+ *
21
+ * @experimental Ships from `foldkit/experimental/query`; expect breaking changes while the API settles.
22
+ */
23
+ export type QueryMessage<A, AI, E, EI> = ReturnType<typeof makeQueryMessage<A, AI, E, EI>>;
24
+ /** Builds the Model Schema for a Query. */
25
+ export declare const makeQueryModel: <A, AI, E, EI>(data: Schema.Codec<A, AI>, error: Schema.Codec<E, EI>) => Schema.Struct<{
26
+ readonly data: Schema.Codec<AsyncData.AsyncData<A, E>, AsyncData.AsyncDataEncoded<AI, EI>, never, never>;
27
+ readonly generation: Schema.Number;
28
+ }>;
29
+ /**
30
+ * Model Schema for a Query containing one `AsyncData` value.
31
+ *
32
+ * @experimental Ships from `foldkit/experimental/query`; expect breaking changes while the API settles.
33
+ */
34
+ export type QueryModel<A, AI, E, EI> = ReturnType<typeof makeQueryModel<A, AI, E, EI>>;
35
+ /**
36
+ * Submodel for fetching and retaining one `AsyncData` value.
37
+ *
38
+ * @experimental Ships from `foldkit/experimental/query`; expect breaking changes while the API settles.
39
+ */
40
+ export interface Query<Name extends string, A, AI, E, EI, R = never> {
41
+ /** Schema for this Query's Model. */
42
+ readonly Model: QueryModel<A, AI, E, EI>;
43
+ /** Schema-backed union of Messages handled by this Query. */
44
+ readonly Message: QueryMessage<A, AI, E, EI>;
45
+ /**
46
+ * Command definition for matching this Query's pending fetch in Story and
47
+ * Scene tests. Start fetches through a loading operation so the Model and
48
+ * request generation advance together; do not call this directly.
49
+ */
50
+ readonly Fetch: Command.CommandDefinitionWithArgs<`Fetch${Name}`, {
51
+ readonly generation: typeof Schema.Number;
52
+ }, Effect.Effect<CompletedFetchOf<QueryMessage<A, AI, E, EI>>, never, R>>;
53
+ /**
54
+ * Creates a Query Model for initial parent Model construction. Never replace
55
+ * a live Query with `init()`: it can reuse an in-flight request generation.
56
+ * Use `reset` instead.
57
+ */
58
+ readonly init: () => QueryModel<A, AI, E, EI>['Type'];
59
+ /** Clears the Query while preserving its request identity. */
60
+ readonly reset: (model: QueryModel<A, AI, E, EI>['Type']) => Update.Return<QueryModel<A, AI, E, EI>['Type'], QueryMessage<A, AI, E, EI>['Type']>;
61
+ /** Reads the `AsyncData` value from the Query Model. */
62
+ readonly read: (model: QueryModel<A, AI, E, EI>['Type']) => AsyncData.AsyncData<A, E>;
63
+ /** Folds a Fetch completion into the Query Model. */
64
+ readonly update: (model: QueryModel<A, AI, E, EI>['Type'], message: QueryMessage<A, AI, E, EI>['Type']) => Update.Return<QueryModel<A, AI, E, EI>['Type'], QueryMessage<A, AI, E, EI>['Type']>;
65
+ /** Refreshes loaded data and does nothing when no data is present. */
66
+ readonly revalidate: (model: QueryModel<A, AI, E, EI>['Type']) => Update.Return<QueryModel<A, AI, E, EI>['Type'], QueryMessage<A, AI, E, EI>['Type'], R>;
67
+ /** Loads missing data or refreshes loaded data. */
68
+ readonly revalidateOrLoad: (model: QueryModel<A, AI, E, EI>['Type']) => Update.Return<QueryModel<A, AI, E, EI>['Type'], QueryMessage<A, AI, E, EI>['Type'], R>;
69
+ /** Loads data only when the Query has no usable value. */
70
+ readonly loadIfMissing: (model: QueryModel<A, AI, E, EI>['Type']) => Update.Return<QueryModel<A, AI, E, EI>['Type'], QueryMessage<A, AI, E, EI>['Type'], R>;
71
+ /** Lifts this Query's update and loading operations into a parent Model. */
72
+ readonly lift: LiftQuery<QueryModel<A, AI, E, EI>['Type'], QueryMessage<A, AI, E, EI>['Type'], R>;
73
+ /** Executes the configured fetch directly and returns settled `AsyncData`. */
74
+ readonly run: Effect.Effect<AsyncData.AsyncData<A, E>, never, R>;
75
+ }
76
+ export declare function defineQuery<Name extends string, A, AI, E, EI, R>(config: QueryConfig<Name, A, AI, E, EI, R>): Query<Name, A, AI, E, EI, R>;
77
+ export {};
78
+ //# sourceMappingURL=query.d.ts.map