@confect/foldkit 10.0.0-next.22 → 10.0.0-next.23

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 +1 @@
1
- {"version":3,"file":"Subscription.js","names":["Client.WebSocketClientError","PaginatedQuery.FunctionError","PaginatedQuery.getSubscriptionRequest","Client.Client","PaginatedQuery.allocateRequest"],"sources":["../src/Subscription.ts"],"sourcesContent":["import * as PaginationError from \"@confect/core/PaginationError\";\nimport type * as Ref from \"@confect/core/Ref\";\nimport * as Effect from \"effect/Effect\";\nimport type * as Equivalence from \"effect/Equivalence\";\nimport * as Match from \"effect/Match\";\nimport * as Option from \"effect/Option\";\nimport * as Result from \"effect/Result\";\nimport * as Schema from \"effect/Schema\";\nimport * as Stream from \"effect/Stream\";\nimport type * as FoldkitSubscription from \"foldkit/subscription\";\nimport * as PaginatedQuery from \"./PaginatedQuery\";\nimport * as Client from \"./Client\";\n\n/**\n * Everything a reactive query subscription against `Query` can fail with: the\n * ref's typed error (if it declares an `error` schema), a transport-level\n * `WebSocketClientError`, or a `SchemaError` from encoding args or decoding\n * returns.\n */\nexport type Error<Query extends Ref.AnyConfectPublicQuery> =\n | Ref.Error<Query>\n | Client.WebSocketClientError\n | Schema.SchemaError;\n\nconst paginatedError = <Query extends Ref.AnyConfectPublicPaginatedQuery>(\n error: Error<Query>,\n): PaginatedQuery.Error<Query> =>\n Match.value(error).pipe(\n Match.when(Schema.isSchemaError, (schemaError) => schemaError),\n Match.when(Match.instanceOf(Client.WebSocketClientError), (clientError) =>\n PaginationError.fromConvexQueryError(clientError.cause).pipe(\n Option.getOrElse(() => clientError),\n ),\n ),\n Match.when(Match.any, (functionError) =>\n PaginationError.fromConvexErrorData(functionError).pipe(\n Option.getOrElse(() =>\n PaginatedQuery.FunctionError({ error: functionError }),\n ),\n ),\n ),\n Match.exhaustive,\n ) as PaginatedQuery.Error<Query>;\n\n/**\n * The dependency record of a `reactiveQuery` entry. `None` means the\n * subscription is closed; a change from one `Some` to another tears the\n * server subscription down and reopens it with the new args.\n */\nexport interface Dependencies<Query extends Ref.AnyConfectPublicQuery> {\n readonly args: Option.Option<Ref.Args<Query>>;\n}\n\n/**\n * Maps a reactive query's emissions into the app's Messages. Every failure—the ref's typed error, a transport error, or a codec error—arrives via\n * `onError`, so the resulting stream's error channel is `never`, as Foldkit\n * requires.\n */\nexport interface Handlers<\n Query extends Ref.AnyConfectPublicQuery,\n SuccessMessage,\n ErrorMessage,\n> {\n readonly onSuccess: (returns: Ref.Returns<Query>) => SuccessMessage;\n readonly onError: (error: Error<Query>) => ErrorMessage;\n}\n\n/**\n * The `args` extractor is required whenever the query declares args; queries\n * without args may omit it, in which case the subscription is always open.\n */\ntype ArgsConfig<Query extends Ref.AnyConfectPublicQuery, Model> = {\n readonly args?: (model: Model) => Option.Option<Ref.Args<Query>>;\n} & (keyof Ref.Args<Query> extends never\n ? unknown\n : {\n readonly args: (model: Model) => Option.Option<Ref.Args<Query>>;\n });\n\n/**\n * A `dependenciesToStream` body for a hand-written subscription entry: runs\n * the ref as a reactive query against the `Client` resource and maps\n * every emission and every failure into a Message. Query failures are values,\n * so the subscription remains live and can later emit a successful result.\n *\n * Prefer `reactiveQuery`, which builds the whole entry; reach for this when\n * you need control over the entry's dependencies (custom gating, extra\n * dependencies).\n */\nexport const reactiveQueryStream =\n <Query extends Ref.AnyConfectPublicQuery, SuccessMessage, ErrorMessage>(\n ref: Query,\n handlers: Handlers<Query, SuccessMessage, ErrorMessage>,\n ) =>\n (\n ...args: Ref.OptionalArgs<Query>\n ): Stream.Stream<SuccessMessage | ErrorMessage, never, Client.Client> =>\n Client.Client.pipe(\n Effect.map((client) => client.reactiveQueryResult(ref, ...args)),\n Stream.unwrap,\n Stream.map(\n Result.match({\n onSuccess: handlers.onSuccess,\n onFailure: handlers.onError,\n }),\n ),\n );\n\n/**\n * A complete Foldkit subscription entry for a Confect reactive query. The\n * leading thunk fixes the `Model` type (TypeScript cannot partially infer\n * type arguments), so the `args` extractor's parameter is already typed.\n * Pass the result as an entry value to `Subscription.make`:\n *\n * ```ts\n * import * as Confect from \"@confect/foldkit\"\n * import * as Subscription from \"foldkit/subscription\"\n *\n * const subscriptions = Subscription.make<\n * Model,\n * Message,\n * Confect.Client.Client\n * >()(() => ({\n * note: Confect.Subscription.reactiveQuery<Model>()(refs.public.notes.get, {\n * args: (model) => Option.map(model.noteId, (noteId) => ({ noteId })),\n * onSuccess: (note) => SucceededGetNote({ note }),\n * onError: (error) => FailedGetNote({ message: String(error) }),\n * }),\n * }))\n * ```\n *\n * The entry's dependencies are the query args wrapped in `Option`: `None`\n * closes the subscription, a change from one `Some` to another unsubscribes\n * and resubscribes with the new args, and structurally equal args leave the\n * subscription running (equivalence is derived from the ref's args schema).\n * Queries without args may omit `args`, leaving the subscription always open.\n */\nexport const reactiveQuery =\n <Model>() =>\n <Query extends Ref.AnyConfectPublicQuery, SuccessMessage, ErrorMessage>(\n ref: Query,\n config: Handlers<Query, SuccessMessage, ErrorMessage> &\n ArgsConfig<Query, Model>,\n ): FoldkitSubscription.EntryWithoutKeepAlive<\n Model,\n SuccessMessage | ErrorMessage,\n Dependencies<Query>,\n Client.Client\n > => {\n const modelToArgs = config.args;\n\n return {\n dependenciesSchema: Schema.Struct({\n args: Schema.Option(ref.args),\n }),\n modelToDependencies: (model) => ({\n args:\n modelToArgs === undefined\n ? Option.some({} as Ref.Args<Query>)\n : modelToArgs(model),\n }),\n dependenciesToStream: ({ args }) =>\n Option.match(args, {\n onNone: () => Stream.empty,\n onSome: (someArgs) => reactiveQueryStream(ref, config)(someArgs),\n }),\n };\n };\n\n/**\n * A complete Foldkit subscription entry that keeps exactly one live reactive\n * page subscription in sync with a `PaginatedQuery` machine in the Model.\n * The machine state is the single source of truth: navigation and\n * split-pinning change the derived args, while `Idle` closes the subscription.\n * Pagination ids are allocated here and installed by the first settlement.\n *\n * The leading thunk fixes the `Model` type (TypeScript cannot partially\n * infer type arguments), so the `state` extractor's parameter is already\n * typed:\n *\n * ```ts\n * import * as Confect from \"@confect/foldkit\"\n * import * as Subscription from \"foldkit/subscription\"\n *\n * const subscriptions = Subscription.make<\n * Model,\n * Message,\n * Confect.Client.Client\n * >()(() => ({\n * notesPage: Confect.Subscription.paginatedQuery<Model>()(Notes, {\n * state: (model) => model.notes,\n * onSettled: (settlement) => SettledGetNotesPage({ settlement }),\n * }),\n * }))\n * ```\n *\n * Pass the settlement to `PaginatedQuery.settle` in `update`. The request\n * identity is included in both success and failure Messages, so superseded\n * outcomes cannot settle a newer page or pagination session. Query failures\n * do not close the Convex subscription; a later result can recover naturally.\n */\ntype DependenciesSchema<Dependencies_> = Schema.Schema<Dependencies_> & {\n readonly fields: Schema.Struct.Fields;\n};\n\ntype EntryWithKeepAlive<Model, Message, Dependencies_, Services> = {\n readonly dependenciesSchema: DependenciesSchema<Dependencies_>;\n readonly modelToDependencies: (model: Model) => Dependencies_;\n readonly keepAliveEquivalence: Equivalence.Equivalence<Dependencies_>;\n readonly dependenciesToStream: (\n dependencies: Dependencies_,\n readDependencies: () => Dependencies_,\n ) => Stream.Stream<Message, never, Services>;\n};\n\nexport const paginatedQuery =\n <Model>() =>\n <\n PaginatedQueryRef extends Ref.AnyConfectPublicPaginatedQuery,\n SettledMessage,\n >(\n machine: PaginatedQuery.PaginatedQuery<PaginatedQueryRef>,\n config: {\n readonly state: (\n model: Model,\n ) => PaginatedQuery.State<\n PaginatedQuery.Item<PaginatedQueryRef>,\n PaginatedQuery.UserArgs<PaginatedQueryRef>,\n PaginatedQuery.Error<PaginatedQueryRef>\n >;\n readonly onSettled: (\n settlement: PaginatedQuery.Settlement<\n PaginatedQuery.Item<PaginatedQueryRef>,\n PaginatedQuery.UserArgs<PaginatedQueryRef>,\n PaginatedQuery.Error<PaginatedQueryRef>\n >,\n ) => SettledMessage;\n },\n ): EntryWithKeepAlive<\n Model,\n SettledMessage,\n {\n readonly request: Option.Option<\n PaginatedQuery.SubscriptionRequest<\n PaginatedQuery.UserArgs<PaginatedQueryRef>\n >\n >;\n },\n Client.Client\n > => {\n const ref = machine.ref;\n const composedArgsSchema = ref.args;\n const requestEquivalence = Schema.toEquivalence(\n machine.subscriptionRequestSchema,\n );\n const optionalRequestEquivalence = Option.makeEquivalence<\n PaginatedQuery.SubscriptionRequest<\n PaginatedQuery.UserArgs<PaginatedQueryRef>\n >\n >((left, right) =>\n requestEquivalence(\n { ...left, paginationId: Option.none() },\n { ...right, paginationId: Option.none() },\n ),\n );\n\n return {\n dependenciesSchema: Schema.Struct({\n request: Schema.Option(machine.subscriptionRequestSchema),\n }),\n modelToDependencies: (model) => ({\n request: Match.value(config.state(model)).pipe(\n Match.tag(\"Idle\", () => Option.none()),\n Match.tag(\"Active\", (state) =>\n Option.some(PaginatedQuery.getSubscriptionRequest(state)),\n ),\n Match.exhaustive,\n ),\n }),\n keepAliveEquivalence: (left, right) =>\n optionalRequestEquivalence(left.request, right.request),\n dependenciesToStream: ({\n request,\n }): Stream.Stream<SettledMessage, never, Client.Client> =>\n Option.match(request, {\n onNone: () => Stream.empty,\n onSome: (subscriptionRequest) =>\n Stream.unwrap(\n Effect.gen(function* () {\n const client = yield* Client.Client;\n const paginationId = yield* client.resolvePaginationId(\n subscriptionRequest.paginationId,\n );\n const allocatedRequest = PaginatedQuery.allocateRequest(\n subscriptionRequest,\n paginationId,\n );\n const { descriptor, options } = allocatedRequest;\n const composedArgs = composedArgsSchema.make({\n ...allocatedRequest.args,\n paginationOpts: {\n numItems: options.initialNumItems,\n cursor: descriptor.cursor,\n id: paginationId,\n ...Match.value(options.maximumRowsRead).pipe(\n Match.when(undefined, () => ({})),\n Match.when(Match.defined, (maximumRowsRead) => ({\n maximumRowsRead,\n })),\n Match.exhaustive,\n ),\n ...Match.value(options.maximumBytesRead).pipe(\n Match.when(undefined, () => ({})),\n Match.when(Match.defined, (maximumBytesRead) => ({\n maximumBytesRead,\n })),\n Match.exhaustive,\n ),\n ...Option.match(descriptor.endCursor, {\n onNone: () => ({}),\n onSome: (endCursor) => ({ endCursor }),\n }),\n },\n });\n return client.reactiveQueryResult(ref, composedArgs).pipe(\n Stream.map((result) =>\n config.onSettled({\n request: allocatedRequest,\n result: Result.mapError(result, paginatedError),\n }),\n ),\n );\n }),\n ),\n }),\n };\n };\n"],"mappings":"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"}
1
+ {"version":3,"file":"Subscription.js","names":["Client.WebSocketClientError","PaginatedQuery.FunctionError","PaginatedQuery.getSubscriptionRequest","Client.Client","PaginatedQuery.allocateRequest"],"sources":["../src/Subscription.ts"],"sourcesContent":["import * as PaginationError from \"@confect/core/PaginationError\";\nimport type * as Ref from \"@confect/core/Ref\";\nimport * as Effect from \"effect/Effect\";\nimport type * as Equivalence from \"effect/Equivalence\";\nimport * as Match from \"effect/Match\";\nimport * as Option from \"effect/Option\";\nimport * as Result from \"effect/Result\";\nimport * as Schema from \"effect/Schema\";\nimport * as Stream from \"effect/Stream\";\nimport type * as FoldkitSubscription from \"foldkit/subscription\";\nimport * as PaginatedQuery from \"./PaginatedQuery\";\nimport * as Client from \"./Client\";\n\n/**\n * Everything a reactive query subscription against `Query` can fail with: the\n * ref's typed error (if it declares an `error` schema), a transport-level\n * `WebSocketClientError`, or a `SchemaError` from encoding args or decoding\n * returns.\n */\nexport type Error<Query extends Ref.AnyConfectPublicQuery> =\n | Ref.Error<Query>\n | Client.WebSocketClientError\n | Schema.SchemaError;\n\nconst paginatedError = <Query extends Ref.AnyConfectPublicPaginatedQuery>(\n error: Error<Query>,\n): PaginatedQuery.Error<Query> =>\n Match.value(error).pipe(\n Match.when(Schema.isSchemaError, (schemaError) => schemaError),\n Match.when(Match.instanceOf(Client.WebSocketClientError), (clientError) =>\n PaginationError.fromConvexQueryError(clientError.cause).pipe(\n Option.getOrElse(() => clientError),\n ),\n ),\n Match.when(Match.any, (functionError) =>\n PaginationError.fromConvexErrorData(functionError).pipe(\n Option.getOrElse(() =>\n PaginatedQuery.FunctionError({ error: functionError }),\n ),\n ),\n ),\n Match.exhaustive,\n ) as PaginatedQuery.Error<Query>;\n\n/**\n * The dependency record of a `reactiveQuery` entry. `None` means the\n * subscription is closed; a change from one `Some` to another tears the server\n * subscription down and reopens it with the new args.\n */\nexport interface Dependencies<Query extends Ref.AnyConfectPublicQuery> {\n readonly args: Option.Option<Ref.Args<Query>>;\n}\n\n/**\n * Maps a reactive query's emissions into the app's Messages. Every failure—the\n * ref's typed error, a transport error, or a codec error—arrives via `onError`,\n * so the resulting stream's error channel is `never`, as Foldkit requires.\n */\nexport interface Handlers<\n Query extends Ref.AnyConfectPublicQuery,\n SuccessMessage,\n ErrorMessage,\n> {\n readonly onSuccess: (returns: Ref.Returns<Query>) => SuccessMessage;\n readonly onError: (error: Error<Query>) => ErrorMessage;\n}\n\n/**\n * The `args` extractor is required whenever the query declares args; queries\n * without args may omit it, in which case the subscription is always open.\n */\ntype ArgsConfig<Query extends Ref.AnyConfectPublicQuery, Model> = {\n readonly args?: (model: Model) => Option.Option<Ref.Args<Query>>;\n} & (keyof Ref.Args<Query> extends never\n ? unknown\n : {\n readonly args: (model: Model) => Option.Option<Ref.Args<Query>>;\n });\n\n/**\n * A `dependenciesToStream` body for a hand-written subscription entry: runs the\n * ref as a reactive query against the `Client` resource and maps every emission\n * and every failure into a Message. Query failures are values, so the\n * subscription remains live and can later emit a successful result.\n *\n * Prefer `reactiveQuery`, which builds the whole entry; reach for this when you\n * need control over the entry's dependencies (custom gating, extra\n * dependencies).\n */\nexport const reactiveQueryStream =\n <Query extends Ref.AnyConfectPublicQuery, SuccessMessage, ErrorMessage>(\n ref: Query,\n handlers: Handlers<Query, SuccessMessage, ErrorMessage>,\n ) =>\n (\n ...args: Ref.OptionalArgs<Query>\n ): Stream.Stream<SuccessMessage | ErrorMessage, never, Client.Client> =>\n Client.Client.pipe(\n Effect.map((client) => client.reactiveQueryResult(ref, ...args)),\n Stream.unwrap,\n Stream.map(\n Result.match({\n onSuccess: handlers.onSuccess,\n onFailure: handlers.onError,\n }),\n ),\n );\n\n/**\n * A complete Foldkit subscription entry for a Confect reactive query. The\n * leading thunk fixes the `Model` type (TypeScript cannot partially infer type\n * arguments), so the `args` extractor's parameter is already typed. Pass the\n * result as an entry value to `Subscription.make`:\n *\n * ```ts\n * import * as Confect from \"@confect/foldkit\";\n * import * as Subscription from \"foldkit/subscription\";\n *\n * const subscriptions = Subscription.make<\n * Model,\n * Message,\n * Confect.Client.Client\n * >()(() => ({\n * note: Confect.Subscription.reactiveQuery<Model>()(\n * refs.public.notes.get,\n * {\n * args: (model) =>\n * Option.map(model.noteId, (noteId) => ({ noteId })),\n * onSuccess: (note) => SucceededGetNote({ note }),\n * onError: (error) => FailedGetNote({ message: String(error) }),\n * },\n * ),\n * }));\n * ```\n *\n * The entry's dependencies are the query args wrapped in `Option`: `None`\n * closes the subscription, a change from one `Some` to another unsubscribes and\n * resubscribes with the new args, and structurally equal args leave the\n * subscription running (equivalence is derived from the ref's args schema).\n * Queries without args may omit `args`, leaving the subscription always open.\n */\nexport const reactiveQuery =\n <Model>() =>\n <Query extends Ref.AnyConfectPublicQuery, SuccessMessage, ErrorMessage>(\n ref: Query,\n config: Handlers<Query, SuccessMessage, ErrorMessage> &\n ArgsConfig<Query, Model>,\n ): FoldkitSubscription.EntryWithoutKeepAlive<\n Model,\n SuccessMessage | ErrorMessage,\n Dependencies<Query>,\n Client.Client\n > => {\n const modelToArgs = config.args;\n\n return {\n dependenciesSchema: Schema.Struct({\n args: Schema.Option(ref.args),\n }),\n modelToDependencies: (model) => ({\n args:\n modelToArgs === undefined\n ? Option.some({} as Ref.Args<Query>)\n : modelToArgs(model),\n }),\n dependenciesToStream: ({ args }) =>\n Option.match(args, {\n onNone: () => Stream.empty,\n onSome: (someArgs) => reactiveQueryStream(ref, config)(someArgs),\n }),\n };\n };\n\n/**\n * A complete Foldkit subscription entry that keeps exactly one live reactive\n * page subscription in sync with a `PaginatedQuery` machine in the Model. The\n * machine state is the single source of truth: navigation and split-pinning\n * change the derived args, while `Idle` closes the subscription. Pagination ids\n * are allocated here and installed by the first settlement.\n *\n * The leading thunk fixes the `Model` type (TypeScript cannot partially infer\n * type arguments), so the `state` extractor's parameter is already typed:\n *\n * ```ts\n * import * as Confect from \"@confect/foldkit\";\n * import * as Subscription from \"foldkit/subscription\";\n *\n * const subscriptions = Subscription.make<\n * Model,\n * Message,\n * Confect.Client.Client\n * >()(() => ({\n * notesPage: Confect.Subscription.paginatedQuery<Model>()(Notes, {\n * state: (model) => model.notes,\n * onSettled: (settlement) => SettledGetNotesPage({ settlement }),\n * }),\n * }));\n * ```\n *\n * Pass the settlement to `PaginatedQuery.settle` in `update`. The request\n * identity is included in both success and failure Messages, so superseded\n * outcomes cannot settle a newer page or pagination session. Query failures do\n * not close the Convex subscription; a later result can recover naturally.\n */\ntype DependenciesSchema<Dependencies_> = Schema.Schema<Dependencies_> & {\n readonly fields: Schema.Struct.Fields;\n};\n\ntype EntryWithKeepAlive<Model, Message, Dependencies_, Services> = {\n readonly dependenciesSchema: DependenciesSchema<Dependencies_>;\n readonly modelToDependencies: (model: Model) => Dependencies_;\n readonly keepAliveEquivalence: Equivalence.Equivalence<Dependencies_>;\n readonly dependenciesToStream: (\n dependencies: Dependencies_,\n readDependencies: () => Dependencies_,\n ) => Stream.Stream<Message, never, Services>;\n};\n\nexport const paginatedQuery =\n <Model>() =>\n <\n PaginatedQueryRef extends Ref.AnyConfectPublicPaginatedQuery,\n SettledMessage,\n >(\n machine: PaginatedQuery.PaginatedQuery<PaginatedQueryRef>,\n config: {\n readonly state: (\n model: Model,\n ) => PaginatedQuery.State<\n PaginatedQuery.Item<PaginatedQueryRef>,\n PaginatedQuery.UserArgs<PaginatedQueryRef>,\n PaginatedQuery.Error<PaginatedQueryRef>\n >;\n readonly onSettled: (\n settlement: PaginatedQuery.Settlement<\n PaginatedQuery.Item<PaginatedQueryRef>,\n PaginatedQuery.UserArgs<PaginatedQueryRef>,\n PaginatedQuery.Error<PaginatedQueryRef>\n >,\n ) => SettledMessage;\n },\n ): EntryWithKeepAlive<\n Model,\n SettledMessage,\n {\n readonly request: Option.Option<\n PaginatedQuery.SubscriptionRequest<\n PaginatedQuery.UserArgs<PaginatedQueryRef>\n >\n >;\n },\n Client.Client\n > => {\n const ref = machine.ref;\n const composedArgsSchema = ref.args;\n const requestEquivalence = Schema.toEquivalence(\n machine.subscriptionRequestSchema,\n );\n const optionalRequestEquivalence = Option.makeEquivalence<\n PaginatedQuery.SubscriptionRequest<\n PaginatedQuery.UserArgs<PaginatedQueryRef>\n >\n >((left, right) =>\n requestEquivalence(\n { ...left, paginationId: Option.none() },\n { ...right, paginationId: Option.none() },\n ),\n );\n\n return {\n dependenciesSchema: Schema.Struct({\n request: Schema.Option(machine.subscriptionRequestSchema),\n }),\n modelToDependencies: (model) => ({\n request: Match.value(config.state(model)).pipe(\n Match.tag(\"Idle\", () => Option.none()),\n Match.tag(\"Active\", (state) =>\n Option.some(PaginatedQuery.getSubscriptionRequest(state)),\n ),\n Match.exhaustive,\n ),\n }),\n keepAliveEquivalence: (left, right) =>\n optionalRequestEquivalence(left.request, right.request),\n dependenciesToStream: ({\n request,\n }): Stream.Stream<SettledMessage, never, Client.Client> =>\n Option.match(request, {\n onNone: () => Stream.empty,\n onSome: (subscriptionRequest) =>\n Stream.unwrap(\n Effect.gen(function* () {\n const client = yield* Client.Client;\n const paginationId = yield* client.resolvePaginationId(\n subscriptionRequest.paginationId,\n );\n const allocatedRequest = PaginatedQuery.allocateRequest(\n subscriptionRequest,\n paginationId,\n );\n const { descriptor, options } = allocatedRequest;\n const composedArgs = composedArgsSchema.make({\n ...allocatedRequest.args,\n paginationOpts: {\n numItems: options.initialNumItems,\n cursor: descriptor.cursor,\n id: paginationId,\n ...Match.value(options.maximumRowsRead).pipe(\n Match.when(undefined, () => ({})),\n Match.when(Match.defined, (maximumRowsRead) => ({\n maximumRowsRead,\n })),\n Match.exhaustive,\n ),\n ...Match.value(options.maximumBytesRead).pipe(\n Match.when(undefined, () => ({})),\n Match.when(Match.defined, (maximumBytesRead) => ({\n maximumBytesRead,\n })),\n Match.exhaustive,\n ),\n ...Option.match(descriptor.endCursor, {\n onNone: () => ({}),\n onSome: (endCursor) => ({ endCursor }),\n }),\n },\n });\n return client.reactiveQueryResult(ref, composedArgs).pipe(\n Stream.map((result) =>\n config.onSettled({\n request: allocatedRequest,\n result: Result.mapError(result, paginatedError),\n }),\n ),\n );\n }),\n ),\n }),\n };\n };\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@confect/foldkit",
3
3
  "description": "Client-side bindings for Foldkit apps",
4
- "version": "10.0.0-next.22",
4
+ "version": "10.0.0-next.23",
5
5
  "author": "RJ Dellecese",
6
6
  "bugs": {
7
7
  "url": "https://github.com/rjdellecese/confect/issues"
@@ -9,6 +9,7 @@
9
9
  "devDependencies": {
10
10
  "@effect/vitest": "4.0.0-rc.115",
11
11
  "@types/node": "24.13.3",
12
+ "confect-test-types": "0.0.0",
12
13
  "convex": "1.45.0",
13
14
  "effect": "4.0.0-rc.115",
14
15
  "foldkit": "0.148.1",
@@ -50,8 +51,8 @@
50
51
  "main": "./dist/index.js",
51
52
  "module": "./dist/index.js",
52
53
  "peerDependencies": {
53
- "@confect/core": "^10.0.0-next.22",
54
- "@confect/js": "^10.0.0-next.22",
54
+ "@confect/core": "^10.0.0-next.23",
55
+ "@confect/js": "^10.0.0-next.23",
55
56
  "effect": "^4.0.0-rc.115",
56
57
  "foldkit": "^0.148.0"
57
58
  },
@@ -66,6 +67,7 @@
66
67
  "clean": "shx rm -rf dist coverage node_modules",
67
68
  "dev": "tsdown --watch --no-clean --config-loader unrun",
68
69
  "test": "vitest run",
69
- "typecheck": "tsc -b tsconfig.src.json"
70
+ "typecheck": "tsc -b tsconfig.src.json",
71
+ "typecheck:inexact": "tsc --noEmit --project tsconfig.json --exactOptionalPropertyTypes false"
70
72
  }
71
73
  }
package/src/Command.ts CHANGED
@@ -6,10 +6,10 @@ import * as FoldkitCommand from "foldkit/command";
6
6
  import * as Client from "./Client";
7
7
 
8
8
  /**
9
- * Everything a Command against `Ref_` can fail with before it is folded into
10
- * an `onError` Message: the ref's typed error (if it declares an `error`
11
- * schema), a transport-level `WebSocketClientError`, or a `SchemaError` from
12
- * encoding args or decoding returns.
9
+ * Everything a Command against `Ref_` can fail with before it is folded into an
10
+ * `onError` Message: the ref's typed error (if it declares an `error` schema),
11
+ * a transport-level `WebSocketClientError`, or a `SchemaError` from encoding
12
+ * args or decoding returns.
13
13
  */
14
14
  export type Error<Ref_ extends Ref.AnyConfect> =
15
15
  | Ref.Error<Ref_>
@@ -17,9 +17,10 @@ export type Error<Ref_ extends Ref.AnyConfect> =
17
17
  | Schema.SchemaError;
18
18
 
19
19
  /**
20
- * Maps a Confect function's outcome into the app's Messages. Every failure—the ref's typed error, a transport error, or a codec error—arrives via
21
- * `onError`, so the resulting Command's error channel is `never`, as Foldkit
22
- * requires. Follow Foldkit's naming convention: past-tense facts like
20
+ * Maps a Confect function's outcome into the app's Messages. Every failure—the
21
+ * ref's typed error, a transport error, or a codec error—arrives via `onError`,
22
+ * so the resulting Command's error channel is `never`, as Foldkit requires.
23
+ * Follow Foldkit's naming convention: past-tense facts like
23
24
  * `SucceededSaveNote`/`FailedSaveNote`.
24
25
  */
25
26
  export interface Handlers<
@@ -49,13 +50,13 @@ type SchemaArgs<Ref_ extends Ref.AnyConfect> = Schema.Schema.Type<
49
50
  >;
50
51
 
51
52
  /**
52
- * A Foldkit Command definition whose args are the ref's args: our
53
- * ref-derived call signature (args optional when the ref declares none)
54
- * intersected with Foldkit's own `CommandDefinitionWithArgs`, which supplies
55
- * the `CommandDefinitionTypeId` brand and makes the definition assignable
56
- * wherever Foldkit accepts one—including Story/Scene `Command.resolve`
57
- * and `expectExact` matchers. Call it from `update` to construct a Command
58
- * instance; nothing runs until the Foldkit runtime executes it.
53
+ * A Foldkit Command definition whose args are the ref's args: our ref-derived
54
+ * call signature (args optional when the ref declares none) intersected with
55
+ * Foldkit's own `CommandDefinitionWithArgs`, which supplies the
56
+ * `CommandDefinitionTypeId` brand and makes the definition assignable wherever
57
+ * Foldkit accepts one—including Story/Scene `Command.resolve` and `expectExact`
58
+ * matchers. Call it from `update` to construct a Command instance; nothing runs
59
+ * until the Foldkit runtime executes it.
59
60
  */
60
61
  export type Definition<
61
62
  Name extends string,
@@ -76,8 +77,8 @@ export type Definition<
76
77
  * invocations can be targeted independently.
77
78
  *
78
79
  * Interrupting stops the client-side Effect and guarantees the invocation's
79
- * result Messages never dispatch—it does not cancel the Convex function on
80
- * the server, which runs to completion once the call is on the wire.
80
+ * result Messages never dispatch—it does not cancel the Convex function on the
81
+ * server, which runs to completion once the call is on the wire.
81
82
  */
82
83
  export type InterruptOption<
83
84
  Ref_ extends Ref.AnyConfect,
@@ -97,8 +98,8 @@ export type KeyedInterrupt<
97
98
  /**
98
99
  * An interruptible Command definition whose key is the Command name—the
99
100
  * ref-derived call signature intersected with Foldkit's
100
- * `Interruptible.DefinitionWithArgsNameKeyed`, which supplies the brand and
101
- * the `Interrupt` constructor.
101
+ * `Interruptible.DefinitionWithArgsNameKeyed`, which supplies the brand and the
102
+ * `Interrupt` constructor.
102
103
  */
103
104
  export type InterruptibleDefinition<
104
105
  Name extends string,
@@ -149,8 +150,8 @@ const run = <Ref_ extends Ref.AnyConfect, SuccessMessage, ErrorMessage>(
149
150
 
150
151
  /**
151
152
  * An execute body for a hand-written `Command.define`—every failure already
152
- * folded into a Message. Reach for these when the factory below doesn't fit:
153
- * a custom args schema, `interrupt`, or a Command that makes several calls.
153
+ * folded into a Message. Reach for these when the factory below doesn't fit: a
154
+ * custom args schema, `interrupt`, or a Command that makes several calls.
154
155
  */
155
156
  export const queryEffect =
156
157
  <Query extends Ref.AnyConfectPublicQuery, SuccessMessage, ErrorMessage>(
@@ -411,22 +412,26 @@ const makeFactory = <BoundRef extends Ref.AnyConfect>(
411
412
  * messages: [SucceededGetNote, FailedGetNote],
412
413
  * onSuccess: (note) => SucceededGetNote({ note }),
413
414
  * onError: (error) => FailedGetNote({ message: String(error) }),
414
- * })
415
+ * });
415
416
  * // In update:
416
417
  * // [model, [FetchNote({ noteId })]]
417
418
  * ```
418
419
  *
419
420
  * Pass `interrupt` to make invocations interruptible via the returned
420
- * definition's `Interrupt` constructor—`true` keys them by the Command
421
- * name, `{ keyFields, toKey }` by a part derived from the ref's args:
421
+ * definition's `Interrupt` constructor—`true` keys them by the Command name, `{
422
+ * keyFields, toKey }` by a part derived from the ref's args:
422
423
  *
423
424
  * ```ts
424
- * const SaveDraft = Command.mutation("SaveDraft", refs.public.notes.insert, {
425
- * messages: [SucceededSaveDraft, FailedSaveDraft],
426
- * onSuccess: (noteId) => SucceededSaveDraft({ noteId }),
427
- * onError: (error) => FailedSaveDraft({ message: String(error) }),
428
- * interrupt: true,
429
- * })
425
+ * const SaveDraft = Command.mutation(
426
+ * "SaveDraft",
427
+ * refs.public.notes.insert,
428
+ * {
429
+ * messages: [SucceededSaveDraft, FailedSaveDraft],
430
+ * onSuccess: (noteId) => SucceededSaveDraft({ noteId }),
431
+ * onError: (error) => FailedSaveDraft({ message: String(error) }),
432
+ * interrupt: true,
433
+ * },
434
+ * );
430
435
  * // In update:
431
436
  * // [model, [SaveDraft.Interrupt((outcome) => CompletedCancelSaveDraft({ outcome }))]]
432
437
  * ```
@@ -443,8 +448,8 @@ export const mutation: ReturnType<
443
448
  > = makeFactory(mutationEffect);
444
449
 
445
450
  /**
446
- * A Foldkit Command definition for a Confect action whose Command args are
447
- * the ref's args. See `query`.
451
+ * A Foldkit Command definition for a Confect action whose Command args are the
452
+ * ref's args. See `query`.
448
453
  */
449
454
  export const action: ReturnType<
450
455
  typeof makeFactory<Ref.AnyConfectPublicAction>
@@ -11,7 +11,9 @@ import * as SchemaGetter from "effect/SchemaGetter";
11
11
  import * as SchemaIssue from "effect/SchemaIssue";
12
12
  import * as Client from "./Client";
13
13
 
14
- /** Identifies the cursor range fetched for one page. */
14
+ /**
15
+ * Identifies the cursor range fetched for one page.
16
+ */
15
17
  export interface PageDescriptor {
16
18
  readonly cursor: string | null;
17
19
  readonly endCursor: Option.Option<string>;
@@ -22,7 +24,9 @@ export const PageDescriptor = Schema.Struct({
22
24
  endCursor: Schema.Option(Schema.String),
23
25
  });
24
26
 
25
- /** Options that remain fixed for one pagination session. */
27
+ /**
28
+ * Options that remain fixed for one pagination session.
29
+ */
26
30
  export interface Options {
27
31
  readonly initialNumItems: number;
28
32
  readonly maximumRowsRead?: number;
@@ -38,7 +42,9 @@ export const Options = Schema.Struct({
38
42
  maximumBytesRead: Schema.optionalKey(PositiveInt),
39
43
  });
40
44
 
41
- /** Why the machine is fetching its current target. */
45
+ /**
46
+ * Why the machine is fetching its current target.
47
+ */
42
48
  export type Direction =
43
49
  | "Initial"
44
50
  | "Next"
@@ -47,7 +53,9 @@ export type Direction =
47
53
  | "Split"
48
54
  | "Reset";
49
55
 
50
- /** A complete page that can remain on screen while another page is fetched. */
56
+ /**
57
+ * A complete page that can remain on screen while another page is fetched.
58
+ */
51
59
  export interface Page<Item_> {
52
60
  readonly descriptor: PageDescriptor;
53
61
  readonly number: number;
@@ -89,12 +97,16 @@ interface PhaseDefinition extends Data.TaggedEnum.WithGenerics<2> {
89
97
 
90
98
  const Phase = Data.taggedEnum<PhaseDefinition>();
91
99
 
92
- /** A closed machine that retains its generation for stale-result rejection. */
100
+ /**
101
+ * A closed machine that retains its generation for stale-result rejection.
102
+ */
93
103
  export type Idle = Data.TaggedEnum<{
94
104
  Idle: { readonly generation: number };
95
105
  }>;
96
106
 
97
- /** A live cursor-pagination session. */
107
+ /**
108
+ * A live cursor-pagination session.
109
+ */
98
110
  export type Active<Item_, UserArgs_, Error_> = Data.TaggedEnum<{
99
111
  Active: {
100
112
  readonly generation: number;
@@ -108,7 +120,9 @@ export type Active<Item_, UserArgs_, Error_> = Data.TaggedEnum<{
108
120
  };
109
121
  }>;
110
122
 
111
- /** A schema-backed cursor-pagination machine. */
123
+ /**
124
+ * A schema-backed cursor-pagination machine.
125
+ */
112
126
  export type State<Item_, UserArgs_, Error_> =
113
127
  | Idle
114
128
  | Active<Item_, UserArgs_, Error_>;
@@ -119,7 +133,9 @@ interface StateDefinition extends Data.TaggedEnum.WithGenerics<3> {
119
133
 
120
134
  const State = Data.taggedEnum<StateDefinition>();
121
135
 
122
- /** The logical request from which a subscription allocates a session id. */
136
+ /**
137
+ * The logical request from which a subscription allocates a session id.
138
+ */
123
139
  export interface SubscriptionRequest<UserArgs_> {
124
140
  readonly generation: number;
125
141
  readonly args: UserArgs_;
@@ -129,7 +145,9 @@ export interface SubscriptionRequest<UserArgs_> {
129
145
  readonly descriptor: PageDescriptor;
130
146
  }
131
147
 
132
- /** The complete identity and arguments of a subscribed page request. */
148
+ /**
149
+ * The complete identity and arguments of a subscribed page request.
150
+ */
133
151
  export interface Request<UserArgs_> {
134
152
  readonly generation: number;
135
153
  readonly args: UserArgs_;
@@ -139,7 +157,9 @@ export interface Request<UserArgs_> {
139
157
  readonly descriptor: PageDescriptor;
140
158
  }
141
159
 
142
- /** The successful payload returned by a Convex paginated query. */
160
+ /**
161
+ * The successful payload returned by a Convex paginated query.
162
+ */
143
163
  export interface PageResult<Item_> {
144
164
  readonly page: ReadonlyArray<Item_>;
145
165
  readonly isDone: boolean;
@@ -149,8 +169,8 @@ export interface PageResult<Item_> {
149
169
  }
150
170
 
151
171
  /**
152
- * One correlated subscription outcome. Both success and failure travel
153
- * through this value so `settle` can mirror `AsyncData.settle`.
172
+ * One correlated subscription outcome. Both success and failure travel through
173
+ * this value so `settle` can mirror `AsyncData.settle`.
154
174
  */
155
175
  export interface Settlement<Item_, UserArgs_, Error_> {
156
176
  readonly request: Request<UserArgs_>;
@@ -173,18 +193,24 @@ interface FunctionErrorDefinition extends Data.TaggedEnum.WithGenerics<1> {
173
193
  readonly taggedEnum: TaggedFunctionError<this["A"]>;
174
194
  }
175
195
 
176
- /** Constructs a declared function or middleware failure envelope. */
196
+ /**
197
+ * Constructs a declared function or middleware failure envelope.
198
+ */
177
199
  export const FunctionError =
178
200
  Data.taggedEnum<FunctionErrorDefinition>().FunctionError;
179
201
 
180
- /** A declared function or middleware failure, kept distinct from client errors. */
202
+ /**
203
+ * A declared function or middleware failure, kept distinct from client errors.
204
+ */
181
205
  export type FunctionError<Query extends Ref.AnyConfectPublicPaginatedQuery> = [
182
206
  Ref.Error<Query>,
183
207
  ] extends [never]
184
208
  ? never
185
209
  : TaggedFunctionError<Ref.Error<Query>>;
186
210
 
187
- /** Every failure a paginated query subscription can settle with. */
211
+ /**
212
+ * Every failure a paginated query subscription can settle with.
213
+ */
188
214
  export type Error<Query extends Ref.AnyConfectPublicPaginatedQuery> =
189
215
  | FunctionError<Query>
190
216
  | PaginationError.InvalidCursor
@@ -205,7 +231,9 @@ type MachineActive<Query extends Ref.AnyConfectPublicPaginatedQuery> = Active<
205
231
  Error<Query>
206
232
  >;
207
233
 
208
- /** The schema-and-constructor bundle returned by `make`. */
234
+ /**
235
+ * The schema-and-constructor bundle returned by `make`.
236
+ */
209
237
  export interface PaginatedQuery<
210
238
  Query extends Ref.AnyConfectPublicPaginatedQuery,
211
239
  > {
@@ -295,7 +323,9 @@ const SerializableSchemaError = Schema.instanceOf(Schema.SchemaError, {
295
323
  }),
296
324
  });
297
325
 
298
- /** Builds a page machine from a paginated ref. */
326
+ /**
327
+ * Builds a page machine from a paginated ref.
328
+ */
299
329
  export const make = <Query extends Ref.AnyConfectPublicPaginatedQuery>(
300
330
  ref: Query,
301
331
  ): PaginatedQuery<Query> => {
@@ -483,7 +513,9 @@ const descriptorEquals = (a: PageDescriptor, b: PageDescriptor): boolean =>
483
513
  }),
484
514
  });
485
515
 
486
- /** Returns the logical request currently driving the subscription. */
516
+ /**
517
+ * Returns the logical request currently driving the subscription.
518
+ */
487
519
  export const getSubscriptionRequest = <Item_, UserArgs_, Error_>(
488
520
  state: Active<Item_, UserArgs_, Error_>,
489
521
  ): SubscriptionRequest<UserArgs_> => ({
@@ -495,7 +527,9 @@ export const getSubscriptionRequest = <Item_, UserArgs_, Error_>(
495
527
  descriptor: state.current,
496
528
  });
497
529
 
498
- /** Attaches the client-allocated pagination id to a logical request. */
530
+ /**
531
+ * Attaches the client-allocated pagination id to a logical request.
532
+ */
499
533
  export const allocateRequest = <UserArgs_>(
500
534
  request: SubscriptionRequest<UserArgs_>,
501
535
  paginationId: number,
@@ -525,12 +559,16 @@ const pendingPhase = <Item_>(
525
559
  onSome: (page) => Phase.Refreshing<Item_, never>({ data: page, direction }),
526
560
  });
527
561
 
528
- /** Close the subscription while retaining its generation tombstone. */
562
+ /**
563
+ * Close the subscription while retaining its generation tombstone.
564
+ */
529
565
  export const close = <Item_, UserArgs_, Error_>(
530
566
  state: Active<Item_, UserArgs_, Error_>,
531
567
  ): Idle => State.Idle({ generation: state.generation });
532
568
 
533
- /** Start a fresh pagination session at page one while retaining visible data. */
569
+ /**
570
+ * Start a fresh pagination session at page one while retaining visible data.
571
+ */
534
572
  export const reset = <Item_, UserArgs_, Error_>(
535
573
  state: Active<Item_, UserArgs_, Error_>,
536
574
  ): Active<Item_, UserArgs_, Error_> => ({
@@ -546,13 +584,13 @@ export const reset = <Item_, UserArgs_, Error_>(
546
584
  /**
547
585
  * Navigate to the next page, retaining the current page while it loads.
548
586
  *
549
- * The page being left is pushed onto the stack *pinned* to the range it
550
- * displayed—its cursor to its continuation cursor—so `prev` reloads
551
- * exactly that range rather than the first `initialNumItems` documents
552
- * after its cursor, however the data has moved meanwhile. Convex's own
553
- * pagination keeps that range in its query journal; stream-paginated
554
- * queries have no journal, so the pin is what keeps consecutive pages
555
- * gap-free and duplicate-free for them.
587
+ * The page being left is pushed onto the stack _pinned_ to the range it
588
+ * displayed—its cursor to its continuation cursor—so `prev` reloads exactly
589
+ * that range rather than the first `initialNumItems` documents after its
590
+ * cursor, however the data has moved meanwhile. Convex's own pagination keeps
591
+ * that range in its query journal; stream-paginated queries have no journal, so
592
+ * the pin is what keeps consecutive pages gap-free and duplicate-free for
593
+ * them.
556
594
  */
557
595
  export const next = <Item_, UserArgs_, Error_>(
558
596
  state: Active<Item_, UserArgs_, Error_>,
@@ -589,7 +627,9 @@ export const next = <Item_, UserArgs_, Error_>(
589
627
  Match.exhaustive,
590
628
  );
591
629
 
592
- /** Navigate to the previous page, retaining the current page while it loads. */
630
+ /**
631
+ * Navigate to the previous page, retaining the current page while it loads.
632
+ */
593
633
  export const prev = <Item_, UserArgs_, Error_>(
594
634
  state: Active<Item_, UserArgs_, Error_>,
595
635
  ): Option.Option<Active<Item_, UserArgs_, Error_>> =>
@@ -619,7 +659,9 @@ export const prev = <Item_, UserArgs_, Error_>(
619
659
  Match.exhaustive,
620
660
  );
621
661
 
622
- /** Navigate to page one without invalidating the current pagination session. */
662
+ /**
663
+ * Navigate to page one without invalidating the current pagination session.
664
+ */
623
665
  export const first = <Item_, UserArgs_, Error_>(
624
666
  state: Active<Item_, UserArgs_, Error_>,
625
667
  ): Option.Option<Active<Item_, UserArgs_, Error_>> =>
@@ -641,7 +683,9 @@ export const first = <Item_, UserArgs_, Error_>(
641
683
  Match.exhaustive,
642
684
  );
643
685
 
644
- /** Whether a request still names the machine's live pagination session/page. */
686
+ /**
687
+ * Whether a request still names the machine's live pagination session/page.
688
+ */
645
689
  export const isCurrentRequest = <Item_, UserArgs_, Error_>(
646
690
  state: State<Item_, UserArgs_, Error_>,
647
691
  candidate: Request<UserArgs_>,
@@ -810,7 +854,9 @@ export const settle: {
810
854
  ),
811
855
  );
812
856
 
813
- /** The complete page currently available to render. */
857
+ /**
858
+ * The complete page currently available to render.
859
+ */
814
860
  export const getPage = <Item_, UserArgs_, Error_>(
815
861
  state: State<Item_, UserArgs_, Error_>,
816
862
  ): Option.Option<Page<Item_>> =>
@@ -820,13 +866,17 @@ export const getPage = <Item_, UserArgs_, Error_>(
820
866
  Match.exhaustive,
821
867
  );
822
868
 
823
- /** The items currently available to render. */
869
+ /**
870
+ * The items currently available to render.
871
+ */
824
872
  export const getItems = <Item_, UserArgs_, Error_>(
825
873
  state: State<Item_, UserArgs_, Error_>,
826
874
  ): Option.Option<ReadonlyArray<Item_>> =>
827
875
  Option.map(getPage(state), (page) => page.items);
828
876
 
829
- /** The error from the most recent failed request, if any. */
877
+ /**
878
+ * The error from the most recent failed request, if any.
879
+ */
830
880
  export const getError = <Item_, UserArgs_, Error_>(
831
881
  state: State<Item_, UserArgs_, Error_>,
832
882
  ): Option.Option<Error_> =>
@@ -844,7 +894,9 @@ export const getError = <Item_, UserArgs_, Error_>(
844
894
  Match.exhaustive,
845
895
  );
846
896
 
847
- /** The 1-indexed page number currently targeted by the subscription. */
897
+ /**
898
+ * The 1-indexed page number currently targeted by the subscription.
899
+ */
848
900
  export const targetPageNumber = <Item_, UserArgs_, Error_>(
849
901
  state: Active<Item_, UserArgs_, Error_>,
850
902
  ): number => state.prevStack.length + 1;
@@ -959,7 +1011,9 @@ export const canPrev = <Item_, UserArgs_, Error_>(
959
1011
  Match.exhaustive,
960
1012
  );
961
1013
 
962
- /** Pattern-match exhaustively on the machine's AsyncData-style state. */
1014
+ /**
1015
+ * Pattern-match exhaustively on the machine's AsyncData-style state.
1016
+ */
963
1017
  export const match: {
964
1018
  <Item_, Error_, A, B, C, D, E, F>(handlers: {
965
1019
  readonly onIdle: (idle: Idle) => A;