@confect/foldkit 10.0.0-next.19
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.
- package/CHANGELOG.md +61 -0
- package/LICENSE +7 -0
- package/README.md +11 -0
- package/dist/Client.d.ts +69 -0
- package/dist/Client.d.ts.map +1 -0
- package/dist/Client.js +44 -0
- package/dist/Client.js.map +1 -0
- package/dist/Command.d.ts +158 -0
- package/dist/Command.d.ts.map +1 -0
- package/dist/Command.js +111 -0
- package/dist/Command.js.map +1 -0
- package/dist/PaginatedQuery.d.ts +227 -0
- package/dist/PaginatedQuery.d.ts.map +1 -0
- package/dist/PaginatedQuery.js +351 -0
- package/dist/PaginatedQuery.js.map +1 -0
- package/dist/Subscription.d.ts +132 -0
- package/dist/Subscription.d.ts.map +1 -0
- package/dist/Subscription.js +119 -0
- package/dist/Subscription.js.map +1 -0
- package/dist/WebSocketClient.d.ts +16 -0
- package/dist/WebSocketClient.d.ts.map +1 -0
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/package.json +71 -0
- package/src/Client.ts +53 -0
- package/src/Command.ts +452 -0
- package/src/PaginatedQuery.ts +1001 -0
- package/src/Subscription.ts +338 -0
- package/src/index.ts +4 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PaginatedQuery.js","names":["Client.WebSocketClientError"],"sources":["../src/PaginatedQuery.ts"],"sourcesContent":["import * as MiddlewareSpec from \"@confect/core/MiddlewareSpec\";\nimport * as PaginationError from \"@confect/core/PaginationError\";\nimport * as Ref from \"@confect/core/Ref\";\nimport * as Data from \"effect/Data\";\nimport * as Function from \"effect/Function\";\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 SchemaGetter from \"effect/SchemaGetter\";\nimport * as SchemaIssue from \"effect/SchemaIssue\";\nimport * as Client from \"./Client\";\n\n/** Identifies the cursor range fetched for one page. */\nexport interface PageDescriptor {\n readonly cursor: string | null;\n readonly endCursor: Option.Option<string>;\n}\n\nexport const PageDescriptor = Schema.Struct({\n cursor: Schema.Union([Schema.String, Schema.Null]),\n endCursor: Schema.Option(Schema.String),\n});\n\n/** Options that remain fixed for one pagination session. */\nexport interface Options {\n readonly initialNumItems: number;\n readonly maximumRowsRead?: number;\n readonly maximumBytesRead?: number;\n}\n\nconst PositiveInt = Schema.Int.check(Schema.isGreaterThan(0));\nconst NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));\n\nexport const Options = Schema.Struct({\n initialNumItems: PositiveInt,\n maximumRowsRead: Schema.optionalKey(PositiveInt),\n maximumBytesRead: Schema.optionalKey(PositiveInt),\n});\n\n/** Why the machine is fetching its current target. */\nexport type Direction =\n | \"Initial\"\n | \"Next\"\n | \"Previous\"\n | \"First\"\n | \"Split\"\n | \"Reset\";\n\n/** A complete page that can remain on screen while another page is fetched. */\nexport interface Page<Item_> {\n readonly descriptor: PageDescriptor;\n readonly number: number;\n readonly items: ReadonlyArray<Item_>;\n readonly continueCursor: string;\n readonly isDone: boolean;\n}\n\nexport type Loading = Data.TaggedEnum<{\n Loading: { readonly direction: Direction };\n}>;\n\nexport type Refreshing<Item_> = Data.TaggedEnum<{\n Refreshing: { readonly data: Page<Item_>; readonly direction: Direction };\n}>;\n\nexport type Success<Item_> = Data.TaggedEnum<{\n Success: { readonly data: Page<Item_> };\n}>;\n\nexport type Failure<Error_> = Data.TaggedEnum<{\n Failure: { readonly error: Error_ };\n}>;\n\nexport type Stale<Item_, Error_> = Data.TaggedEnum<{\n Stale: { readonly data: Page<Item_>; readonly error: Error_ };\n}>;\n\nexport type Phase<Item_, Error_> =\n | Loading\n | Refreshing<Item_>\n | Success<Item_>\n | Failure<Error_>\n | Stale<Item_, Error_>;\n\ninterface PhaseDefinition extends Data.TaggedEnum.WithGenerics<2> {\n readonly taggedEnum: Phase<this[\"A\"], this[\"B\"]>;\n}\n\nconst Phase = Data.taggedEnum<PhaseDefinition>();\n\n/** A closed machine that retains its generation for stale-result rejection. */\nexport type Idle = Data.TaggedEnum<{\n Idle: { readonly generation: number };\n}>;\n\n/** A live cursor-pagination session. */\nexport type Active<Item_, UserArgs_, Error_> = Data.TaggedEnum<{\n Active: {\n readonly generation: number;\n readonly args: UserArgs_;\n readonly options: Options;\n readonly paginationId: Option.Option<number>;\n readonly requestId: number;\n readonly current: PageDescriptor;\n readonly prevStack: ReadonlyArray<PageDescriptor>;\n readonly phase: Phase<Item_, Error_>;\n };\n}>;\n\n/** A schema-backed cursor-pagination machine. */\nexport type State<Item_, UserArgs_, Error_> =\n | Idle\n | Active<Item_, UserArgs_, Error_>;\n\ninterface StateDefinition extends Data.TaggedEnum.WithGenerics<3> {\n readonly taggedEnum: State<this[\"A\"], this[\"B\"], this[\"C\"]>;\n}\n\nconst State = Data.taggedEnum<StateDefinition>();\n\n/** The logical request from which a subscription allocates a session id. */\nexport interface SubscriptionRequest<UserArgs_> {\n readonly generation: number;\n readonly args: UserArgs_;\n readonly options: Options;\n readonly paginationId: Option.Option<number>;\n readonly requestId: number;\n readonly descriptor: PageDescriptor;\n}\n\n/** The complete identity and arguments of a subscribed page request. */\nexport interface Request<UserArgs_> {\n readonly generation: number;\n readonly args: UserArgs_;\n readonly options: Options;\n readonly paginationId: number;\n readonly requestId: number;\n readonly descriptor: PageDescriptor;\n}\n\n/** The successful payload returned by a Convex paginated query. */\nexport interface PageResult<Item_> {\n readonly page: ReadonlyArray<Item_>;\n readonly isDone: boolean;\n readonly continueCursor: string;\n readonly splitCursor?: string | null;\n readonly pageStatus?: \"SplitRecommended\" | \"SplitRequired\" | null;\n}\n\n/**\n * One correlated subscription outcome. Both success and failure travel\n * through this value so `settle` can mirror `AsyncData.settle`.\n */\nexport interface Settlement<Item_, UserArgs_, Error_> {\n readonly request: Request<UserArgs_>;\n readonly result: Result.Result<PageResult<Item_>, Error_>;\n}\n\nexport type Item<Query extends Ref.AnyConfectPublicPaginatedQuery> =\n Ref.Returns<Query>[\"page\"][number];\n\nexport type UserArgs<Query extends Ref.AnyConfectPublicPaginatedQuery> = Omit<\n Ref.Args<Query>,\n \"paginationOpts\"\n>;\n\ntype TaggedFunctionError<Error_> = Data.TaggedEnum<{\n FunctionError: { readonly error: Error_ };\n}>;\n\ninterface FunctionErrorDefinition extends Data.TaggedEnum.WithGenerics<1> {\n readonly taggedEnum: TaggedFunctionError<this[\"A\"]>;\n}\n\n/** Constructs a declared function or middleware failure envelope. */\nexport const FunctionError =\n Data.taggedEnum<FunctionErrorDefinition>().FunctionError;\n\n/** A declared function or middleware failure, kept distinct from client errors. */\nexport type FunctionError<Query extends Ref.AnyConfectPublicPaginatedQuery> = [\n Ref.Error<Query>,\n] extends [never]\n ? never\n : TaggedFunctionError<Ref.Error<Query>>;\n\n/** Every failure a paginated query subscription can settle with. */\nexport type Error<Query extends Ref.AnyConfectPublicPaginatedQuery> =\n | FunctionError<Query>\n | PaginationError.InvalidCursor\n | Client.WebSocketClientError\n | Schema.SchemaError;\n\nexport { InvalidCursor } from \"@confect/core/PaginationError\";\n\ntype MachineState<Query extends Ref.AnyConfectPublicPaginatedQuery> = State<\n Item<Query>,\n UserArgs<Query>,\n Error<Query>\n>;\n\ntype MachineActive<Query extends Ref.AnyConfectPublicPaginatedQuery> = Active<\n Item<Query>,\n UserArgs<Query>,\n Error<Query>\n>;\n\n/** The schema-and-constructor bundle returned by `make`. */\nexport interface PaginatedQuery<\n Query extends Ref.AnyConfectPublicPaginatedQuery,\n> {\n readonly ref: Query;\n readonly schema: Schema.Codec<MachineState<Query>, unknown>;\n readonly idle: Idle;\n readonly subscriptionRequestSchema: Schema.Codec<\n SubscriptionRequest<UserArgs<Query>>,\n unknown\n >;\n readonly requestSchema: Schema.Codec<Request<UserArgs<Query>>, unknown>;\n readonly settlement: Schema.Codec<\n Settlement<Item<Query>, UserArgs<Query>, Error<Query>>,\n unknown\n >;\n readonly init: keyof UserArgs<Query> extends never\n ? (state: Idle, options: Options) => MachineActive<Query>\n : (\n state: Idle,\n args: UserArgs<Query>,\n options: Options,\n ) => MachineActive<Query>;\n readonly reinitialize: keyof UserArgs<Query> extends never\n ? (state: MachineActive<Query>, options?: Options) => MachineActive<Query>\n : (\n state: MachineActive<Query>,\n args: UserArgs<Query>,\n options?: Options,\n ) => MachineActive<Query>;\n}\n\nexport type Any = PaginatedQuery<Ref.AnyConfectPublicPaginatedQuery>;\n\nconst firstDescriptor = (): PageDescriptor => ({\n cursor: null,\n endCursor: Option.none(),\n});\n\nconst decodeOptions = Schema.decodeUnknownSync(Options);\n\nconst initialState = <Item_, UserArgs_, Error_>(\n generation: number,\n args: UserArgs_,\n options: Options,\n): Active<Item_, UserArgs_, Error_> =>\n State.Active<Item_, UserArgs_, Error_>({\n generation,\n args,\n options: decodeOptions(options),\n paginationId: Option.none(),\n requestId: 1,\n current: firstDescriptor(),\n prevStack: [],\n phase: Phase.Loading<Item_, Error_>({ direction: \"Initial\" }),\n });\n\nconst missingPaginatedProvenanceError = (ref: Ref.AnyConfect) =>\n new globalThis.Error(\n `Paginated query ref \"${Ref.getConvexFunctionName(ref)}\" was not built ` +\n \"with `FunctionSpec.publicPaginatedQuery`. `PaginatedQuery.make` \" +\n \"requires the user-args and item schemas that constructor stores.\",\n );\n\nconst paginatedKindOrThrow = (ref: Ref.AnyConfectPublicPaginatedQuery) =>\n Match.value(ref.kind).pipe(\n Match.tag(\"Paginated\", (kind) => kind),\n Match.tag(\"Standard\", () => {\n throw missingPaginatedProvenanceError(ref);\n }),\n Match.exhaustive,\n );\n\nconst SchemaErrorJson = Schema.TaggedStruct(\"SchemaError\", {\n message: Schema.String,\n});\n\nconst SerializableSchemaError = Schema.instanceOf(Schema.SchemaError, {\n toCodecJson: () =>\n Schema.link<Schema.SchemaError>()(SchemaErrorJson, {\n decode: SchemaGetter.transform(\n ({ message }) =>\n new Schema.SchemaError(new SchemaIssue.InvalidValue({ message })),\n ),\n encode: SchemaGetter.transform((error) =>\n SchemaErrorJson.make({ message: error.message }),\n ),\n }),\n});\n\n/** Builds a page machine from a paginated ref. */\nexport const make = <Query extends Ref.AnyConfectPublicPaginatedQuery>(\n ref: Query,\n): PaginatedQuery<Query> => {\n const kind = paginatedKindOrThrow(ref);\n const hasUserArgs = globalThis.Object.keys(kind.userArgs.fields).length > 0;\n const functionErrorSchemas = [\n ...(\"error\" in ref && ref.error !== undefined ? [ref.error] : []),\n ...MiddlewareSpec.errorSchemas(ref.middlewareSpecs),\n ];\n const errorSchema = Schema.Union([\n PaginationError.InvalidCursor,\n Client.WebSocketClientError,\n SerializableSchemaError,\n ...(functionErrorSchemas.length === 0\n ? []\n : [\n Schema.TaggedStruct(\"FunctionError\", {\n error:\n functionErrorSchemas.length === 1\n ? functionErrorSchemas[0]!\n : Schema.Union(functionErrorSchemas),\n }),\n ]),\n ]) as Schema.Codec<Error<Query>, unknown>;\n const directions = Schema.Literals([\n \"Initial\",\n \"Next\",\n \"Previous\",\n \"First\",\n \"Split\",\n \"Reset\",\n ]);\n const page = Schema.Struct({\n descriptor: PageDescriptor,\n number: PositiveInt,\n items: kind.page,\n continueCursor: Schema.String,\n isDone: Schema.Boolean,\n });\n const phase = Schema.TaggedUnion({\n Loading: { direction: directions },\n Refreshing: { data: page, direction: directions },\n Success: { data: page },\n Failure: { error: errorSchema },\n Stale: { data: page, error: errorSchema },\n });\n const subscriptionRequestSchema: PaginatedQuery<Query>[\"subscriptionRequestSchema\"] =\n Schema.Struct({\n generation: PositiveInt,\n args: kind.userArgs,\n options: Options,\n paginationId: Schema.Option(PositiveInt),\n requestId: PositiveInt,\n descriptor: PageDescriptor,\n });\n const requestSchema: PaginatedQuery<Query>[\"requestSchema\"] = Schema.Struct({\n generation: PositiveInt,\n args: kind.userArgs,\n options: Options,\n paginationId: PositiveInt,\n requestId: PositiveInt,\n descriptor: PageDescriptor,\n });\n const pageResult = Schema.Struct({\n page: kind.page,\n isDone: Schema.Boolean,\n continueCursor: Schema.String,\n splitCursor: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])),\n pageStatus: Schema.optionalKey(\n Schema.Union([\n Schema.Literal(\"SplitRecommended\"),\n Schema.Literal(\"SplitRequired\"),\n Schema.Null,\n ]),\n ),\n });\n const schema: PaginatedQuery<Query>[\"schema\"] = Schema.TaggedUnion({\n Idle: { generation: NonNegativeInt },\n Active: {\n generation: PositiveInt,\n args: kind.userArgs,\n options: Options,\n paginationId: Schema.Option(PositiveInt),\n requestId: PositiveInt,\n current: PageDescriptor,\n prevStack: Schema.Array(PageDescriptor),\n phase,\n },\n });\n const settlement: PaginatedQuery<Query>[\"settlement\"] = Schema.Struct({\n request: requestSchema,\n result: Schema.Result(pageResult, errorSchema),\n });\n\n const init: PaginatedQuery<Query>[\"init\"] = (\n state: Idle,\n argsOrOptions: UserArgs<Query> | Options,\n options?: Options,\n ): MachineActive<Query> =>\n Match.value(options).pipe(\n Match.withReturnType<MachineActive<Query>>(),\n Match.when(undefined, () =>\n initialState(\n state.generation + 1,\n {} as UserArgs<Query>,\n argsOrOptions as Options,\n ),\n ),\n Match.when(Match.defined, (definedOptions) =>\n initialState(\n state.generation + 1,\n argsOrOptions as UserArgs<Query>,\n definedOptions,\n ),\n ),\n Match.exhaustive,\n );\n\n const reinitialize: PaginatedQuery<Query>[\"reinitialize\"] = (\n state: MachineActive<Query>,\n argsOrOptions?: UserArgs<Query> | Options,\n options?: Options,\n ): MachineActive<Query> =>\n Match.value(options).pipe(\n Match.withReturnType<MachineActive<Query>>(),\n Match.when(Match.defined, (definedOptions) =>\n initialState(\n state.generation + 1,\n argsOrOptions as UserArgs<Query>,\n definedOptions,\n ),\n ),\n Match.when(undefined, () =>\n Match.value(argsOrOptions).pipe(\n Match.withReturnType<MachineActive<Query>>(),\n Match.when(undefined, () =>\n initialState(state.generation + 1, state.args, state.options),\n ),\n Match.when(Match.defined, (definedArgsOrOptions) =>\n Match.value(hasUserArgs).pipe(\n Match.withReturnType<MachineActive<Query>>(),\n Match.when(true, () =>\n initialState(\n state.generation + 1,\n definedArgsOrOptions as UserArgs<Query>,\n state.options,\n ),\n ),\n Match.when(false, () =>\n initialState(\n state.generation + 1,\n state.args,\n definedArgsOrOptions as Options,\n ),\n ),\n Match.exhaustive,\n ),\n ),\n Match.exhaustive,\n ),\n ),\n Match.exhaustive,\n );\n\n return {\n ref,\n schema,\n idle: State.Idle({ generation: 0 }),\n subscriptionRequestSchema,\n requestSchema,\n settlement,\n init,\n reinitialize,\n };\n};\n\nconst descriptorEquals = (a: PageDescriptor, b: PageDescriptor): boolean =>\n a.cursor === b.cursor &&\n Option.match(a.endCursor, {\n onNone: () => Option.isNone(b.endCursor),\n onSome: (aEnd) =>\n Option.match(b.endCursor, {\n onNone: () => false,\n onSome: (bEnd) => aEnd === bEnd,\n }),\n });\n\n/** Returns the logical request currently driving the subscription. */\nexport const getSubscriptionRequest = <Item_, UserArgs_, Error_>(\n state: Active<Item_, UserArgs_, Error_>,\n): SubscriptionRequest<UserArgs_> => ({\n generation: state.generation,\n args: state.args,\n options: state.options,\n paginationId: state.paginationId,\n requestId: state.requestId,\n descriptor: state.current,\n});\n\n/** Attaches the client-allocated pagination id to a logical request. */\nexport const allocateRequest = <UserArgs_>(\n request: SubscriptionRequest<UserArgs_>,\n paginationId: number,\n): Request<UserArgs_> => ({\n ...request,\n paginationId,\n});\n\nconst phaseData = <Item_, Error_>(\n phase: Phase<Item_, Error_>,\n): Option.Option<Page<Item_>> =>\n Match.value(phase).pipe(\n Match.withReturnType<Option.Option<Page<Item_>>>(),\n Match.tag(\"Refreshing\", \"Success\", \"Stale\", (dataPhase) =>\n Option.some(dataPhase.data),\n ),\n Match.tag(\"Loading\", \"Failure\", () => Option.none()),\n Match.exhaustive,\n );\n\nconst pendingPhase = <Item_>(\n data: Option.Option<Page<Item_>>,\n direction: Direction,\n): Loading | Refreshing<Item_> =>\n Option.match(data, {\n onNone: () => Phase.Loading<Item_, never>({ direction }),\n onSome: (page) => Phase.Refreshing<Item_, never>({ data: page, direction }),\n });\n\n/** Close the subscription while retaining its generation tombstone. */\nexport const close = <Item_, UserArgs_, Error_>(\n state: Active<Item_, UserArgs_, Error_>,\n): Idle => State.Idle({ generation: state.generation });\n\n/** Start a fresh pagination session at page one while retaining visible data. */\nexport const reset = <Item_, UserArgs_, Error_>(\n state: Active<Item_, UserArgs_, Error_>,\n): Active<Item_, UserArgs_, Error_> => ({\n ...state,\n generation: state.generation + 1,\n paginationId: Option.none(),\n requestId: 1,\n current: firstDescriptor(),\n prevStack: [],\n phase: pendingPhase(phaseData(state.phase), \"Reset\"),\n});\n\n/** Navigate to the next page, retaining the current page while it loads. */\nexport const next = <Item_, UserArgs_, Error_>(\n state: Active<Item_, UserArgs_, Error_>,\n): Option.Option<Active<Item_, UserArgs_, Error_>> =>\n Match.value(state.phase).pipe(\n Match.withReturnType<Option.Option<Active<Item_, UserArgs_, Error_>>>(),\n Match.tag(\"Success\", ({ data }) =>\n Match.value(data.isDone).pipe(\n Match.withReturnType<Option.Option<Active<Item_, UserArgs_, Error_>>>(),\n Match.when(true, () => Option.none()),\n Match.when(false, () =>\n Option.some({\n ...state,\n requestId: state.requestId + 1,\n current: {\n cursor: Option.getOrElse(\n data.descriptor.endCursor,\n () => data.continueCursor,\n ),\n endCursor: Option.none(),\n },\n prevStack: [...state.prevStack, data.descriptor],\n phase: Phase.Refreshing<Item_, Error_>({\n data,\n direction: \"Next\",\n }),\n }),\n ),\n Match.exhaustive,\n ),\n ),\n Match.tag(\"Loading\", \"Refreshing\", \"Failure\", \"Stale\", () => Option.none()),\n Match.exhaustive,\n );\n\n/** Navigate to the previous page, retaining the current page while it loads. */\nexport const prev = <Item_, UserArgs_, Error_>(\n state: Active<Item_, UserArgs_, Error_>,\n): Option.Option<Active<Item_, UserArgs_, Error_>> =>\n Match.value(state.phase).pipe(\n Match.withReturnType<Option.Option<Active<Item_, UserArgs_, Error_>>>(),\n Match.tag(\"Success\", ({ data }) => {\n const current = state.prevStack.at(-1);\n return Match.value(current).pipe(\n Match.withReturnType<Option.Option<Active<Item_, UserArgs_, Error_>>>(),\n Match.when(undefined, () => Option.none()),\n Match.when(Match.defined, (previous) =>\n Option.some({\n ...state,\n requestId: state.requestId + 1,\n current: previous,\n prevStack: state.prevStack.slice(0, -1),\n phase: Phase.Refreshing<Item_, Error_>({\n data,\n direction: \"Previous\",\n }),\n }),\n ),\n Match.exhaustive,\n );\n }),\n Match.tag(\"Loading\", \"Refreshing\", \"Failure\", \"Stale\", () => Option.none()),\n Match.exhaustive,\n );\n\n/** Navigate to page one without invalidating the current pagination session. */\nexport const first = <Item_, UserArgs_, Error_>(\n state: Active<Item_, UserArgs_, Error_>,\n): Option.Option<Active<Item_, UserArgs_, Error_>> =>\n Match.value(\n state.prevStack.length === 0 &&\n descriptorEquals(state.current, firstDescriptor()),\n ).pipe(\n Match.withReturnType<Option.Option<Active<Item_, UserArgs_, Error_>>>(),\n Match.when(true, () => Option.none()),\n Match.when(false, () =>\n Option.some({\n ...state,\n requestId: state.requestId + 1,\n current: firstDescriptor(),\n prevStack: [],\n phase: pendingPhase(phaseData(state.phase), \"First\"),\n }),\n ),\n Match.exhaustive,\n );\n\n/** Whether a request still names the machine's live pagination session/page. */\nexport const isCurrentRequest = <Item_, UserArgs_, Error_>(\n state: State<Item_, UserArgs_, Error_>,\n candidate: Request<UserArgs_>,\n): boolean =>\n Match.value(state).pipe(\n Match.tag(\"Idle\", () => false),\n Match.tag(\"Active\", (active) =>\n Match.value(active.paginationId).pipe(\n Match.tag(\"None\", () =>\n Match.value(\n active.generation === candidate.generation &&\n active.requestId === candidate.requestId &&\n descriptorEquals(active.current, candidate.descriptor),\n ).pipe(\n Match.when(true, () => true),\n Match.when(false, () => false),\n Match.exhaustive,\n ),\n ),\n Match.tag(\n \"Some\",\n ({ value }) =>\n active.generation === candidate.generation &&\n value === candidate.paginationId &&\n active.requestId === candidate.requestId &&\n descriptorEquals(active.current, candidate.descriptor),\n ),\n Match.exhaustive,\n ),\n ),\n Match.exhaustive,\n );\n\nconst pageFromResult = <Item_, UserArgs_, Error_>(\n state: Active<Item_, UserArgs_, Error_>,\n result: PageResult<Item_>,\n): Page<Item_> => ({\n descriptor: state.current,\n number: state.prevStack.length + 1,\n items: result.page,\n continueCursor: result.continueCursor,\n isDone: result.isDone,\n});\n\nconst settleSuccess = <Item_, UserArgs_, Error_>(\n state: Active<Item_, UserArgs_, Error_>,\n result: PageResult<Item_>,\n): Active<Item_, UserArgs_, Error_> => {\n const splitCursor = result.splitCursor;\n const splitSignaled = Match.value(result.pageStatus).pipe(\n Match.whenOr(\"SplitRecommended\", \"SplitRequired\", () => true),\n Match.whenOr(null, undefined, () => false),\n Match.exhaustive,\n );\n const shouldSplit =\n typeof splitCursor === \"string\" &&\n (splitSignaled || result.page.length > 2 * state.options.initialNumItems);\n const shouldRetreat =\n result.page.length === 0 && result.isDone && state.prevStack.length > 0;\n\n return Match.value(shouldSplit).pipe(\n Match.withReturnType<Active<Item_, UserArgs_, Error_>>(),\n Match.when(true, () => {\n const deliveredPage = pageFromResult(state, result);\n const previous = Match.value(result.pageStatus).pipe(\n Match.withReturnType<Option.Option<Page<Item_>>>(),\n Match.when(\"SplitRequired\", () => phaseData(state.phase)),\n Match.whenOr(\"SplitRecommended\", null, undefined, () =>\n Option.some(deliveredPage),\n ),\n Match.exhaustive,\n );\n return {\n ...state,\n requestId: state.requestId + 1,\n current: {\n cursor: state.current.cursor,\n endCursor: Option.fromNullishOr(splitCursor),\n },\n phase: pendingPhase(previous, \"Split\"),\n };\n }),\n Match.when(false, () =>\n Match.value(shouldRetreat).pipe(\n Match.withReturnType<Active<Item_, UserArgs_, Error_>>(),\n Match.when(true, () => ({\n ...state,\n requestId: state.requestId + 1,\n current: state.prevStack[state.prevStack.length - 1],\n prevStack: state.prevStack.slice(0, -1),\n phase: pendingPhase(phaseData(state.phase), \"Previous\"),\n })),\n Match.when(false, () => ({\n ...state,\n phase: Phase.Success<Item_, Error_>({\n data: pageFromResult(state, result),\n }),\n })),\n Match.exhaustive,\n ),\n ),\n Match.exhaustive,\n );\n};\n\nconst settleFailure = <Item_, UserArgs_, Error_>(\n state: Active<Item_, UserArgs_, Error_>,\n error: Error_,\n): Active<Item_, UserArgs_, Error_> => ({\n ...state,\n phase: Option.match(phaseData(state.phase), {\n onNone: () => Phase.Failure<Item_, Error_>({ error }),\n onSome: (data) => Phase.Stale<Item_, Error_>({ error, data }),\n }),\n});\n\n/**\n * Fold a correlated `Result` into the machine. Like `AsyncData.settle`, a\n * success becomes `Success`; a failure becomes `Stale` when a page is held,\n * otherwise `Failure`. Outcomes from superseded requests and outcomes that\n * arrive after the subscription closed are ignored.\n */\nexport const settle: {\n <Item_, UserArgs_, Error_>(\n settlement: Settlement<Item_, UserArgs_, Error_>,\n ): (\n state: State<Item_, UserArgs_, Error_>,\n ) => State<Item_, UserArgs_, Error_>;\n <Item_, UserArgs_, Error_>(\n state: State<Item_, UserArgs_, Error_>,\n settlement: Settlement<Item_, UserArgs_, Error_>,\n ): State<Item_, UserArgs_, Error_>;\n} = Function.dual(\n 2,\n <Item_, UserArgs_, Error_>(\n state: State<Item_, UserArgs_, Error_>,\n settlement: Settlement<Item_, UserArgs_, Error_>,\n ): State<Item_, UserArgs_, Error_> =>\n Match.value(state).pipe(\n Match.withReturnType<State<Item_, UserArgs_, Error_>>(),\n Match.tag(\"Idle\", () => state),\n Match.tag(\"Active\", (active) =>\n Match.value(isCurrentRequest(active, settlement.request)).pipe(\n Match.when(false, () => active),\n Match.when(true, () => {\n const allocated = {\n ...active,\n paginationId: Option.some(settlement.request.paginationId),\n };\n return Result.match(settlement.result, {\n onSuccess: (result) => settleSuccess(allocated, result),\n onFailure: (error) =>\n Match.value(error).pipe(\n Match.when(\n Match.instanceOf(PaginationError.InvalidCursor),\n () => reset(allocated),\n ),\n Match.orElse((other) => settleFailure(allocated, other)),\n ),\n });\n }),\n Match.exhaustive,\n ),\n ),\n Match.exhaustive,\n ),\n);\n\n/** The complete page currently available to render. */\nexport const getPage = <Item_, UserArgs_, Error_>(\n state: State<Item_, UserArgs_, Error_>,\n): Option.Option<Page<Item_>> =>\n Match.value(state).pipe(\n Match.tag(\"Idle\", () => Option.none()),\n Match.tag(\"Active\", ({ phase }) => phaseData(phase)),\n Match.exhaustive,\n );\n\n/** The items currently available to render. */\nexport const getItems = <Item_, UserArgs_, Error_>(\n state: State<Item_, UserArgs_, Error_>,\n): Option.Option<ReadonlyArray<Item_>> =>\n Option.map(getPage(state), (page) => page.items);\n\n/** The error from the most recent failed request, if any. */\nexport const getError = <Item_, UserArgs_, Error_>(\n state: State<Item_, UserArgs_, Error_>,\n): Option.Option<Error_> =>\n Match.value(state).pipe(\n Match.withReturnType<Option.Option<Error_>>(),\n Match.tag(\"Idle\", () => Option.none()),\n Match.tag(\"Active\", ({ phase }) =>\n Match.value(phase).pipe(\n Match.withReturnType<Option.Option<Error_>>(),\n Match.tag(\"Failure\", \"Stale\", ({ error }) => Option.some(error)),\n Match.tag(\"Loading\", \"Refreshing\", \"Success\", () => Option.none()),\n Match.exhaustive,\n ),\n ),\n Match.exhaustive,\n );\n\n/** The 1-indexed page number currently targeted by the subscription. */\nexport const targetPageNumber = <Item_, UserArgs_, Error_>(\n state: Active<Item_, UserArgs_, Error_>,\n): number => state.prevStack.length + 1;\n\nexport const hasPage = <Item_, UserArgs_, Error_>(\n state: State<Item_, UserArgs_, Error_>,\n): boolean => Option.isSome(getPage(state));\n\nexport const hasError = <Item_, UserArgs_, Error_>(\n state: State<Item_, UserArgs_, Error_>,\n): boolean => Option.isSome(getError(state));\n\nexport const isFirst = <Item_, UserArgs_, Error_>(\n state: State<Item_, UserArgs_, Error_>,\n): boolean =>\n Match.value(state).pipe(\n Match.tag(\"Idle\", () => false),\n Match.tag(\"Active\", ({ prevStack }) => prevStack.length === 0),\n Match.exhaustive,\n );\n\nexport const isLast = <Item_, UserArgs_, Error_>(\n state: State<Item_, UserArgs_, Error_>,\n): boolean =>\n Match.value(state).pipe(\n Match.tag(\"Idle\", () => false),\n Match.tag(\"Active\", ({ phase }) =>\n Match.value(phase).pipe(\n Match.tag(\"Success\", ({ data }) => data.isDone),\n Match.tag(\"Loading\", \"Refreshing\", \"Failure\", \"Stale\", () => false),\n Match.exhaustive,\n ),\n ),\n Match.exhaustive,\n );\n\ntype WithPhase<State_, Phase_> = Omit<State_, \"phase\"> & {\n readonly phase: Phase_;\n};\n\nexport const isIdle = <Item_, UserArgs_, Error_>(\n state: State<Item_, UserArgs_, Error_>,\n): state is Idle => state._tag === \"Idle\";\n\nexport const isLoading = <Item_, UserArgs_, Error_>(\n state: State<Item_, UserArgs_, Error_>,\n): state is WithPhase<Active<Item_, UserArgs_, Error_>, Loading> =>\n state._tag === \"Active\" && state.phase._tag === \"Loading\";\n\nexport const isRefreshing = <Item_, UserArgs_, Error_>(\n state: State<Item_, UserArgs_, Error_>,\n): state is WithPhase<Active<Item_, UserArgs_, Error_>, Refreshing<Item_>> =>\n state._tag === \"Active\" && state.phase._tag === \"Refreshing\";\n\nexport const isSuccess = <Item_, UserArgs_, Error_>(\n state: State<Item_, UserArgs_, Error_>,\n): state is WithPhase<Active<Item_, UserArgs_, Error_>, Success<Item_>> =>\n state._tag === \"Active\" && state.phase._tag === \"Success\";\n\nexport const isFailure = <Item_, UserArgs_, Error_>(\n state: State<Item_, UserArgs_, Error_>,\n): state is WithPhase<Active<Item_, UserArgs_, Error_>, Failure<Error_>> =>\n state._tag === \"Active\" && state.phase._tag === \"Failure\";\n\nexport const isStale = <Item_, UserArgs_, Error_>(\n state: State<Item_, UserArgs_, Error_>,\n): state is WithPhase<Active<Item_, UserArgs_, Error_>, Stale<Item_, Error_>> =>\n state._tag === \"Active\" && state.phase._tag === \"Stale\";\n\nexport const isPending = <Item_, UserArgs_, Error_>(\n state: State<Item_, UserArgs_, Error_>,\n): boolean =>\n Match.value(state).pipe(\n Match.tag(\"Idle\", () => false),\n Match.tag(\"Active\", ({ phase }) =>\n Match.value(phase).pipe(\n Match.tag(\"Loading\", \"Refreshing\", () => true),\n Match.tag(\"Success\", \"Failure\", \"Stale\", () => false),\n Match.exhaustive,\n ),\n ),\n Match.exhaustive,\n );\n\nexport const canNext = <Item_, UserArgs_, Error_>(\n state: State<Item_, UserArgs_, Error_>,\n): boolean =>\n Match.value(state).pipe(\n Match.tag(\"Idle\", () => false),\n Match.tag(\"Active\", ({ phase }) =>\n Match.value(phase).pipe(\n Match.tag(\"Success\", ({ data }) => !data.isDone),\n Match.tag(\"Loading\", \"Refreshing\", \"Failure\", \"Stale\", () => false),\n Match.exhaustive,\n ),\n ),\n Match.exhaustive,\n );\n\nexport const canPrev = <Item_, UserArgs_, Error_>(\n state: State<Item_, UserArgs_, Error_>,\n): boolean =>\n Match.value(state).pipe(\n Match.tag(\"Idle\", () => false),\n Match.tag(\"Active\", ({ phase, prevStack }) =>\n Match.value(phase).pipe(\n Match.tag(\"Success\", () => prevStack.length > 0),\n Match.tag(\"Loading\", \"Refreshing\", \"Failure\", \"Stale\", () => false),\n Match.exhaustive,\n ),\n ),\n Match.exhaustive,\n );\n\n/** Pattern-match exhaustively on the machine's AsyncData-style state. */\nexport const match: {\n <Item_, Error_, A, B, C, D, E, F>(handlers: {\n readonly onIdle: (idle: Idle) => A;\n readonly onLoading: (loading: Loading) => B;\n readonly onRefreshing: (refreshing: Refreshing<Item_>) => C;\n readonly onFailure: (failure: Failure<Error_>) => D;\n readonly onStale: (stale: Stale<Item_, Error_>) => E;\n readonly onSuccess: (success: Success<Item_>) => F;\n }): <UserArgs_>(\n state: State<Item_, UserArgs_, Error_>,\n ) => A | B | C | D | E | F;\n <Item_, UserArgs_, Error_, A, B, C, D, E, F>(\n state: State<Item_, UserArgs_, Error_>,\n handlers: {\n readonly onIdle: (idle: Idle) => A;\n readonly onLoading: (loading: Loading) => B;\n readonly onRefreshing: (refreshing: Refreshing<Item_>) => C;\n readonly onFailure: (failure: Failure<Error_>) => D;\n readonly onStale: (stale: Stale<Item_, Error_>) => E;\n readonly onSuccess: (success: Success<Item_>) => F;\n },\n ): A | B | C | D | E | F;\n} = Function.dual(\n 2,\n <Item_, UserArgs_, Error_, A, B, C, D, E, F>(\n state: State<Item_, UserArgs_, Error_>,\n handlers: {\n readonly onIdle: (idle: Idle) => A;\n readonly onLoading: (loading: Loading) => B;\n readonly onRefreshing: (refreshing: Refreshing<Item_>) => C;\n readonly onFailure: (failure: Failure<Error_>) => D;\n readonly onStale: (stale: Stale<Item_, Error_>) => E;\n readonly onSuccess: (success: Success<Item_>) => F;\n },\n ): A | B | C | D | E | F =>\n Match.value(state).pipe(\n Match.tag(\"Idle\", handlers.onIdle),\n Match.tag(\"Active\", ({ phase }) =>\n Match.value(phase).pipe(\n Match.tag(\"Loading\", handlers.onLoading),\n Match.tag(\"Refreshing\", handlers.onRefreshing),\n Match.tag(\"Failure\", handlers.onFailure),\n Match.tag(\"Stale\", handlers.onStale),\n Match.tag(\"Success\", handlers.onSuccess),\n Match.exhaustive,\n ),\n ),\n Match.exhaustive,\n ) as A | B | C | D | E | F,\n);\n"],"mappings":"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"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type * as Ref from "@confect/core/Ref";
|
|
2
|
+
import type * as Equivalence from "effect/Equivalence";
|
|
3
|
+
import * as Option from "effect/Option";
|
|
4
|
+
import * as Schema from "effect/Schema";
|
|
5
|
+
import * as Stream from "effect/Stream";
|
|
6
|
+
import type * as FoldkitSubscription from "foldkit/subscription";
|
|
7
|
+
import * as PaginatedQuery from "./PaginatedQuery";
|
|
8
|
+
import * as Client from "./Client";
|
|
9
|
+
/**
|
|
10
|
+
* Everything a reactive query subscription against `Query` can fail with: the
|
|
11
|
+
* ref's typed error (if it declares an `error` schema), a transport-level
|
|
12
|
+
* `WebSocketClientError`, or a `SchemaError` from encoding args or decoding
|
|
13
|
+
* returns.
|
|
14
|
+
*/
|
|
15
|
+
export type Error<Query extends Ref.AnyConfectPublicQuery> = Ref.Error<Query> | Client.WebSocketClientError | Schema.SchemaError;
|
|
16
|
+
/**
|
|
17
|
+
* The dependency record of a `reactiveQuery` entry. `None` means the
|
|
18
|
+
* subscription is closed; a change from one `Some` to another tears the
|
|
19
|
+
* server subscription down and reopens it with the new args.
|
|
20
|
+
*/
|
|
21
|
+
export interface Dependencies<Query extends Ref.AnyConfectPublicQuery> {
|
|
22
|
+
readonly args: Option.Option<Ref.Args<Query>>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Maps a reactive query's emissions into the app's Messages. Every failure —
|
|
26
|
+
* the ref's typed error, a transport error, or a codec error — arrives via
|
|
27
|
+
* `onError`, so the resulting stream's error channel is `never`, as Foldkit
|
|
28
|
+
* requires.
|
|
29
|
+
*/
|
|
30
|
+
export interface Handlers<Query extends Ref.AnyConfectPublicQuery, SuccessMessage, ErrorMessage> {
|
|
31
|
+
readonly onSuccess: (returns: Ref.Returns<Query>) => SuccessMessage;
|
|
32
|
+
readonly onError: (error: Error<Query>) => ErrorMessage;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The `args` extractor is required whenever the query declares args; queries
|
|
36
|
+
* without args may omit it, in which case the subscription is always open.
|
|
37
|
+
*/
|
|
38
|
+
type ArgsConfig<Query extends Ref.AnyConfectPublicQuery, Model> = {
|
|
39
|
+
readonly args?: (model: Model) => Option.Option<Ref.Args<Query>>;
|
|
40
|
+
} & (keyof Ref.Args<Query> extends never ? unknown : {
|
|
41
|
+
readonly args: (model: Model) => Option.Option<Ref.Args<Query>>;
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* A `dependenciesToStream` body for a hand-written subscription entry: runs
|
|
45
|
+
* the ref as a reactive query against the `Client` resource and maps
|
|
46
|
+
* every emission and every failure into a Message. Query failures are values,
|
|
47
|
+
* so the subscription remains live and can later emit a successful result.
|
|
48
|
+
*
|
|
49
|
+
* Prefer `reactiveQuery`, which builds the whole entry; reach for this when
|
|
50
|
+
* you need control over the entry's dependencies (custom gating, extra
|
|
51
|
+
* dependencies).
|
|
52
|
+
*/
|
|
53
|
+
export declare const reactiveQueryStream: <Query extends Ref.AnyConfectPublicQuery, SuccessMessage, ErrorMessage>(ref: Query, handlers: Handlers<Query, SuccessMessage, ErrorMessage>) => (...args: Ref.OptionalArgs<Query>) => Stream.Stream<SuccessMessage | ErrorMessage, never, Client.Client>;
|
|
54
|
+
/**
|
|
55
|
+
* A complete Foldkit subscription entry for a Confect reactive query. The
|
|
56
|
+
* leading thunk fixes the `Model` type (TypeScript cannot partially infer
|
|
57
|
+
* type arguments), so the `args` extractor's parameter is already typed.
|
|
58
|
+
* Pass the result as an entry value to `Subscription.make`:
|
|
59
|
+
*
|
|
60
|
+
* ```ts
|
|
61
|
+
* import * as Confect from "@confect/foldkit"
|
|
62
|
+
* import * as Subscription from "foldkit/subscription"
|
|
63
|
+
*
|
|
64
|
+
* const subscriptions = Subscription.make<
|
|
65
|
+
* Model,
|
|
66
|
+
* Message,
|
|
67
|
+
* Confect.Client.Client
|
|
68
|
+
* >()(() => ({
|
|
69
|
+
* note: Confect.Subscription.reactiveQuery<Model>()(refs.public.notes.get, {
|
|
70
|
+
* args: (model) => Option.map(model.noteId, (noteId) => ({ noteId })),
|
|
71
|
+
* onSuccess: (note) => SucceededGetNote({ note }),
|
|
72
|
+
* onError: (error) => FailedGetNote({ message: String(error) }),
|
|
73
|
+
* }),
|
|
74
|
+
* }))
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* The entry's dependencies are the query args wrapped in `Option`: `None`
|
|
78
|
+
* closes the subscription, a change from one `Some` to another unsubscribes
|
|
79
|
+
* and resubscribes with the new args, and structurally equal args leave the
|
|
80
|
+
* subscription running (equivalence is derived from the ref's args schema).
|
|
81
|
+
* Queries without args may omit `args`, leaving the subscription always open.
|
|
82
|
+
*/
|
|
83
|
+
export declare const reactiveQuery: <Model>() => <Query extends Ref.AnyConfectPublicQuery, SuccessMessage, ErrorMessage>(ref: Query, config: Handlers<Query, SuccessMessage, ErrorMessage> & ArgsConfig<Query, Model>) => FoldkitSubscription.EntryWithoutKeepAlive<Model, SuccessMessage | ErrorMessage, Dependencies<Query>, Client.Client>;
|
|
84
|
+
/**
|
|
85
|
+
* A complete Foldkit subscription entry that keeps exactly one live reactive
|
|
86
|
+
* page subscription in sync with a `PaginatedQuery` machine in the Model.
|
|
87
|
+
* The machine state is the single source of truth: navigation and
|
|
88
|
+
* split-pinning change the derived args, while `Idle` closes the subscription.
|
|
89
|
+
* Pagination ids are allocated here and installed by the first settlement.
|
|
90
|
+
*
|
|
91
|
+
* The leading thunk fixes the `Model` type (TypeScript cannot partially
|
|
92
|
+
* infer type arguments), so the `state` extractor's parameter is already
|
|
93
|
+
* typed:
|
|
94
|
+
*
|
|
95
|
+
* ```ts
|
|
96
|
+
* import * as Confect from "@confect/foldkit"
|
|
97
|
+
* import * as Subscription from "foldkit/subscription"
|
|
98
|
+
*
|
|
99
|
+
* const subscriptions = Subscription.make<
|
|
100
|
+
* Model,
|
|
101
|
+
* Message,
|
|
102
|
+
* Confect.Client.Client
|
|
103
|
+
* >()(() => ({
|
|
104
|
+
* notesPage: Confect.Subscription.paginatedQuery<Model>()(Notes, {
|
|
105
|
+
* state: (model) => model.notes,
|
|
106
|
+
* onSettled: (settlement) => SettledGetNotesPage({ settlement }),
|
|
107
|
+
* }),
|
|
108
|
+
* }))
|
|
109
|
+
* ```
|
|
110
|
+
*
|
|
111
|
+
* Pass the settlement to `PaginatedQuery.settle` in `update`. The request
|
|
112
|
+
* identity is included in both success and failure Messages, so superseded
|
|
113
|
+
* outcomes cannot settle a newer page or pagination session. Query failures
|
|
114
|
+
* do not close the Convex subscription; a later result can recover naturally.
|
|
115
|
+
*/
|
|
116
|
+
type DependenciesSchema<Dependencies_> = Schema.Schema<Dependencies_> & {
|
|
117
|
+
readonly fields: Schema.Struct.Fields;
|
|
118
|
+
};
|
|
119
|
+
type EntryWithKeepAlive<Model, Message, Dependencies_, Services> = {
|
|
120
|
+
readonly dependenciesSchema: DependenciesSchema<Dependencies_>;
|
|
121
|
+
readonly modelToDependencies: (model: Model) => Dependencies_;
|
|
122
|
+
readonly keepAliveEquivalence: Equivalence.Equivalence<Dependencies_>;
|
|
123
|
+
readonly dependenciesToStream: (dependencies: Dependencies_, readDependencies: () => Dependencies_) => Stream.Stream<Message, never, Services>;
|
|
124
|
+
};
|
|
125
|
+
export declare const paginatedQuery: <Model>() => <PaginatedQueryRef extends Ref.AnyConfectPublicPaginatedQuery, SettledMessage>(machine: PaginatedQuery.PaginatedQuery<PaginatedQueryRef>, config: {
|
|
126
|
+
readonly state: (model: Model) => PaginatedQuery.State<PaginatedQuery.Item<PaginatedQueryRef>, PaginatedQuery.UserArgs<PaginatedQueryRef>, PaginatedQuery.Error<PaginatedQueryRef>>;
|
|
127
|
+
readonly onSettled: (settlement: PaginatedQuery.Settlement<PaginatedQuery.Item<PaginatedQueryRef>, PaginatedQuery.UserArgs<PaginatedQueryRef>, PaginatedQuery.Error<PaginatedQueryRef>>) => SettledMessage;
|
|
128
|
+
}) => EntryWithKeepAlive<Model, SettledMessage, {
|
|
129
|
+
readonly request: Option.Option<PaginatedQuery.SubscriptionRequest<PaginatedQuery.UserArgs<PaginatedQueryRef>>>;
|
|
130
|
+
}, Client.Client>;
|
|
131
|
+
export {};
|
|
132
|
+
//# sourceMappingURL=Subscription.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Subscription.d.ts","sourceRoot":"","sources":["../src/Subscription.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
2
|
+
import { Client, WebSocketClientError } from "./Client.js";
|
|
3
|
+
import { FunctionError, allocateRequest, getSubscriptionRequest } from "./PaginatedQuery.js";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Option from "effect/Option";
|
|
6
|
+
import * as Match from "effect/Match";
|
|
7
|
+
import * as PaginationError from "@confect/core/PaginationError";
|
|
8
|
+
import * as Result from "effect/Result";
|
|
9
|
+
import * as Schema from "effect/Schema";
|
|
10
|
+
import * as Stream from "effect/Stream";
|
|
11
|
+
//#region src/Subscription.ts
|
|
12
|
+
var Subscription_exports = /* @__PURE__ */ __exportAll({
|
|
13
|
+
paginatedQuery: () => paginatedQuery,
|
|
14
|
+
reactiveQuery: () => reactiveQuery,
|
|
15
|
+
reactiveQueryStream: () => reactiveQueryStream
|
|
16
|
+
});
|
|
17
|
+
const paginatedError = (error) => Match.value(error).pipe(Match.when(Schema.isSchemaError, (schemaError) => schemaError), Match.when(Match.instanceOf(WebSocketClientError), (clientError) => PaginationError.fromConvexQueryError(clientError.cause).pipe(Option.getOrElse(() => clientError))), Match.when(Match.any, (functionError) => PaginationError.fromConvexErrorData(functionError).pipe(Option.getOrElse(() => FunctionError({ error: functionError })))), Match.exhaustive);
|
|
18
|
+
/**
|
|
19
|
+
* A `dependenciesToStream` body for a hand-written subscription entry: runs
|
|
20
|
+
* the ref as a reactive query against the `Client` resource and maps
|
|
21
|
+
* every emission and every failure into a Message. Query failures are values,
|
|
22
|
+
* so the subscription remains live and can later emit a successful result.
|
|
23
|
+
*
|
|
24
|
+
* Prefer `reactiveQuery`, which builds the whole entry; reach for this when
|
|
25
|
+
* you need control over the entry's dependencies (custom gating, extra
|
|
26
|
+
* dependencies).
|
|
27
|
+
*/
|
|
28
|
+
const reactiveQueryStream = (ref, handlers) => (...args) => Client.pipe(Effect.map((client) => client.reactiveQueryResult(ref, ...args)), Stream.unwrap, Stream.map(Result.match({
|
|
29
|
+
onSuccess: handlers.onSuccess,
|
|
30
|
+
onFailure: handlers.onError
|
|
31
|
+
})));
|
|
32
|
+
/**
|
|
33
|
+
* A complete Foldkit subscription entry for a Confect reactive query. The
|
|
34
|
+
* leading thunk fixes the `Model` type (TypeScript cannot partially infer
|
|
35
|
+
* type arguments), so the `args` extractor's parameter is already typed.
|
|
36
|
+
* Pass the result as an entry value to `Subscription.make`:
|
|
37
|
+
*
|
|
38
|
+
* ```ts
|
|
39
|
+
* import * as Confect from "@confect/foldkit"
|
|
40
|
+
* import * as Subscription from "foldkit/subscription"
|
|
41
|
+
*
|
|
42
|
+
* const subscriptions = Subscription.make<
|
|
43
|
+
* Model,
|
|
44
|
+
* Message,
|
|
45
|
+
* Confect.Client.Client
|
|
46
|
+
* >()(() => ({
|
|
47
|
+
* note: Confect.Subscription.reactiveQuery<Model>()(refs.public.notes.get, {
|
|
48
|
+
* args: (model) => Option.map(model.noteId, (noteId) => ({ noteId })),
|
|
49
|
+
* onSuccess: (note) => SucceededGetNote({ note }),
|
|
50
|
+
* onError: (error) => FailedGetNote({ message: String(error) }),
|
|
51
|
+
* }),
|
|
52
|
+
* }))
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* The entry's dependencies are the query args wrapped in `Option`: `None`
|
|
56
|
+
* closes the subscription, a change from one `Some` to another unsubscribes
|
|
57
|
+
* and resubscribes with the new args, and structurally equal args leave the
|
|
58
|
+
* subscription running (equivalence is derived from the ref's args schema).
|
|
59
|
+
* Queries without args may omit `args`, leaving the subscription always open.
|
|
60
|
+
*/
|
|
61
|
+
const reactiveQuery = () => (ref, config) => {
|
|
62
|
+
const modelToArgs = config.args;
|
|
63
|
+
return {
|
|
64
|
+
dependenciesSchema: Schema.Struct({ args: Schema.Option(ref.args) }),
|
|
65
|
+
modelToDependencies: (model) => ({ args: modelToArgs === void 0 ? Option.some({}) : modelToArgs(model) }),
|
|
66
|
+
dependenciesToStream: ({ args }) => Option.match(args, {
|
|
67
|
+
onNone: () => Stream.empty,
|
|
68
|
+
onSome: (someArgs) => reactiveQueryStream(ref, config)(someArgs)
|
|
69
|
+
})
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
const paginatedQuery = () => (machine, config) => {
|
|
73
|
+
const ref = machine.ref;
|
|
74
|
+
const composedArgsSchema = ref.args;
|
|
75
|
+
const requestEquivalence = Schema.toEquivalence(machine.subscriptionRequestSchema);
|
|
76
|
+
const optionalRequestEquivalence = Option.makeEquivalence((left, right) => requestEquivalence({
|
|
77
|
+
...left,
|
|
78
|
+
paginationId: Option.none()
|
|
79
|
+
}, {
|
|
80
|
+
...right,
|
|
81
|
+
paginationId: Option.none()
|
|
82
|
+
}));
|
|
83
|
+
return {
|
|
84
|
+
dependenciesSchema: Schema.Struct({ request: Schema.Option(machine.subscriptionRequestSchema) }),
|
|
85
|
+
modelToDependencies: (model) => ({ request: Match.value(config.state(model)).pipe(Match.tag("Idle", () => Option.none()), Match.tag("Active", (state) => Option.some(getSubscriptionRequest(state))), Match.exhaustive) }),
|
|
86
|
+
keepAliveEquivalence: (left, right) => optionalRequestEquivalence(left.request, right.request),
|
|
87
|
+
dependenciesToStream: ({ request }) => Option.match(request, {
|
|
88
|
+
onNone: () => Stream.empty,
|
|
89
|
+
onSome: (subscriptionRequest) => Stream.unwrap(Effect.gen(function* () {
|
|
90
|
+
const client = yield* Client;
|
|
91
|
+
const paginationId = yield* client.resolvePaginationId(subscriptionRequest.paginationId);
|
|
92
|
+
const allocatedRequest = allocateRequest(subscriptionRequest, paginationId);
|
|
93
|
+
const { descriptor, options } = allocatedRequest;
|
|
94
|
+
const composedArgs = composedArgsSchema.make({
|
|
95
|
+
...allocatedRequest.args,
|
|
96
|
+
paginationOpts: {
|
|
97
|
+
numItems: options.initialNumItems,
|
|
98
|
+
cursor: descriptor.cursor,
|
|
99
|
+
id: paginationId,
|
|
100
|
+
...Match.value(options.maximumRowsRead).pipe(Match.when(void 0, () => ({})), Match.when(Match.defined, (maximumRowsRead) => ({ maximumRowsRead })), Match.exhaustive),
|
|
101
|
+
...Match.value(options.maximumBytesRead).pipe(Match.when(void 0, () => ({})), Match.when(Match.defined, (maximumBytesRead) => ({ maximumBytesRead })), Match.exhaustive),
|
|
102
|
+
...Option.match(descriptor.endCursor, {
|
|
103
|
+
onNone: () => ({}),
|
|
104
|
+
onSome: (endCursor) => ({ endCursor })
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
return client.reactiveQueryResult(ref, composedArgs).pipe(Stream.map((result) => config.onSettled({
|
|
109
|
+
request: allocatedRequest,
|
|
110
|
+
result: Result.mapError(result, paginatedError)
|
|
111
|
+
})));
|
|
112
|
+
}))
|
|
113
|
+
})
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
//#endregion
|
|
117
|
+
export { Subscription_exports, paginatedQuery, reactiveQuery, reactiveQueryStream };
|
|
118
|
+
|
|
119
|
+
//# sourceMappingURL=Subscription.js.map
|
|
@@ -0,0 +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 —\n * 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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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { layer, WebSocketClient, WebSocketClientError } from "@confect/js/WebSocketClient";
|
|
2
|
+
/**
|
|
3
|
+
* The Confect WebSocket client, re-exported from `@confect/js` for use as a
|
|
4
|
+
* Foldkit resource. Pass `layer(address)` to `Runtime.makeApplication`'s
|
|
5
|
+
* `resources` so the client lives for the lifetime of the app and the socket
|
|
6
|
+
* closes at teardown:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* Runtime.makeApplication({
|
|
10
|
+
* // ...
|
|
11
|
+
* resources: WebSocketClient.layer(import.meta.env.VITE_CONVEX_URL),
|
|
12
|
+
* })
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export { layer, WebSocketClient, WebSocketClientError };
|
|
16
|
+
//# sourceMappingURL=WebSocketClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebSocketClient.d.ts","sourceRoot":"","sources":["../src/WebSocketClient.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EACL,eAAe,EACf,oBAAoB,EACrB,MAAM,6BAA6B,CAAC;AAErC;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,oBAAoB,EAAE,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __exportAll = (all, no_symbols) => {
|
|
4
|
+
let target = {};
|
|
5
|
+
for (var name in all) __defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true
|
|
8
|
+
});
|
|
9
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
10
|
+
return target;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { __exportAll };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AACnC,OAAO,KAAK,OAAO,MAAM,WAAW,CAAC;AACrC,OAAO,KAAK,cAAc,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,YAAY,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Client_exports } from "./Client.js";
|
|
2
|
+
import { Command_exports } from "./Command.js";
|
|
3
|
+
import { PaginatedQuery_exports } from "./PaginatedQuery.js";
|
|
4
|
+
import { Subscription_exports } from "./Subscription.js";
|
|
5
|
+
export { Client_exports as Client, Command_exports as Command, PaginatedQuery_exports as PaginatedQuery, Subscription_exports as Subscription };
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@confect/foldkit",
|
|
3
|
+
"description": "Client-side bindings for Foldkit apps",
|
|
4
|
+
"version": "10.0.0-next.19",
|
|
5
|
+
"author": "RJ Dellecese",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/rjdellecese/confect/issues"
|
|
8
|
+
},
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"@effect/vitest": "4.0.0-rc.112",
|
|
11
|
+
"@types/node": "22.20.1",
|
|
12
|
+
"convex": "1.44.0",
|
|
13
|
+
"effect": "4.0.0-rc.112",
|
|
14
|
+
"foldkit": "0.148.1",
|
|
15
|
+
"tsdown": "0.22.14",
|
|
16
|
+
"typescript": "7.0.2",
|
|
17
|
+
"unrun": "0.3.1",
|
|
18
|
+
"vitest": "4.1.10"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=22"
|
|
22
|
+
},
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"default": "./dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./*": {
|
|
29
|
+
"types": "./dist/*.d.ts",
|
|
30
|
+
"default": "./dist/*.js"
|
|
31
|
+
},
|
|
32
|
+
"./package.json": "./package.json"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"CHANGELOG.md",
|
|
36
|
+
"LICENSE",
|
|
37
|
+
"README.md",
|
|
38
|
+
"dist",
|
|
39
|
+
"!dist/*.tsbuildinfo",
|
|
40
|
+
"package.json",
|
|
41
|
+
"src"
|
|
42
|
+
],
|
|
43
|
+
"homepage": "https://confect.dev",
|
|
44
|
+
"keywords": [
|
|
45
|
+
"convex",
|
|
46
|
+
"effect",
|
|
47
|
+
"foldkit"
|
|
48
|
+
],
|
|
49
|
+
"license": "ISC",
|
|
50
|
+
"main": "./dist/index.js",
|
|
51
|
+
"module": "./dist/index.js",
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"effect": "^4.0.0-rc.112",
|
|
54
|
+
"foldkit": "^0.148.0",
|
|
55
|
+
"@confect/core": "^10.0.0-next.19",
|
|
56
|
+
"@confect/js": "^10.0.0-next.19"
|
|
57
|
+
},
|
|
58
|
+
"repository": {
|
|
59
|
+
"type": "git",
|
|
60
|
+
"url": "git+https://github.com/rjdellecese/confect.git"
|
|
61
|
+
},
|
|
62
|
+
"sideEffects": false,
|
|
63
|
+
"type": "module",
|
|
64
|
+
"types": "./dist/index.d.ts",
|
|
65
|
+
"scripts": {
|
|
66
|
+
"clean": "shx rm -rf dist coverage node_modules",
|
|
67
|
+
"dev": "tsdown --watch --no-clean --config-loader unrun",
|
|
68
|
+
"test": "vitest run",
|
|
69
|
+
"typecheck": "tsc -b tsconfig.src.json"
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/Client.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as WebSocketClient from "@confect/js/WebSocketClient";
|
|
2
|
+
import type { ConvexClient } from "convex/browser";
|
|
3
|
+
import * as Context from "effect/Context";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Layer from "effect/Layer";
|
|
6
|
+
import * as Option from "effect/Option";
|
|
7
|
+
import * as Ref from "effect/Ref";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Builds the Foldkit-facing Confect client around one WebSocket client. The
|
|
11
|
+
* pagination id allocator shares the WebSocket client's lifetime because its
|
|
12
|
+
* ids are cache-busters for that client's query cache.
|
|
13
|
+
*/
|
|
14
|
+
export const make = (webSocketClient: WebSocketClient.WebSocketClient) =>
|
|
15
|
+
Effect.gen(function* () {
|
|
16
|
+
const paginationId = yield* Ref.make(0);
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
...webSocketClient,
|
|
20
|
+
resolvePaginationId: (restored: Option.Option<number>) =>
|
|
21
|
+
Ref.modify(paginationId, (current) =>
|
|
22
|
+
Option.match(restored, {
|
|
23
|
+
onNone: () => [current + 1, current + 1],
|
|
24
|
+
onSome: (id) => [id, Math.max(current, id)],
|
|
25
|
+
}),
|
|
26
|
+
),
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The application-scoped Confect client used by Foldkit Commands and
|
|
32
|
+
* Subscriptions. It combines the WebSocket API with the pagination-session id
|
|
33
|
+
* allocator required by Convex's query cache.
|
|
34
|
+
*/
|
|
35
|
+
export const Client = Context.Service<Effect.Success<ReturnType<typeof make>>>(
|
|
36
|
+
"@confect/foldkit/Client",
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
export type Client = typeof Client.Identifier;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Constructs one scoped client for a Foldkit application's `resources`.
|
|
43
|
+
*/
|
|
44
|
+
export const layer = (
|
|
45
|
+
address: string,
|
|
46
|
+
options?: ConstructorParameters<typeof ConvexClient>[1],
|
|
47
|
+
) =>
|
|
48
|
+
Layer.effect(
|
|
49
|
+
Client,
|
|
50
|
+
Effect.flatMap(WebSocketClient.WebSocketClient, make),
|
|
51
|
+
).pipe(Layer.provide(WebSocketClient.layer(address, options)));
|
|
52
|
+
|
|
53
|
+
export { WebSocketClientError } from "@confect/js/WebSocketClient";
|