funcade 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Maxim Molochkov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,287 @@
1
+ # Funcade
2
+
3
+ Build type-safe fluent object APIs from functional JavaScript libraries.
4
+
5
+ Funcade turns standalone functions into methods over a current value while preserving their
6
+ arguments, compatible overloads, and return types. It is immutable, has no runtime dependencies,
7
+ and does not modify the wrapped library.
8
+
9
+ ## Installation
10
+
11
+ ```sh
12
+ npm install funcade
13
+ ```
14
+
15
+ ## Configuration
16
+
17
+ `funcade` accepts an initializer and a readonly list of method groups:
18
+
19
+ ```ts
20
+ import { add, format, sub, toDate } from 'date-fns'
21
+ import { chain, funcade, result } from 'funcade'
22
+
23
+ const date = funcade({
24
+ init: toDate,
25
+ methods: [chain({ add, sub }), result({ format })],
26
+ })
27
+
28
+ const formatted = date(new Date(2024, 0, 15))
29
+ .add({ days: 4 })
30
+ .sub({ days: 2 })
31
+ .format('yyyy-MM-dd')
32
+
33
+ // formatted: string
34
+ // => '2024-01-17'
35
+ ```
36
+
37
+ - `init` creates the initial current value and determines its type. Its parameters become the
38
+ parameters of the generated `date` factory.
39
+ - `methods` defines the methods shared by every wrapper created by that factory.
40
+ - Method overloads incompatible with the current value are removed from the generated API.
41
+ - A group is rejected if none of a method's overloads are compatible.
42
+
43
+ Calling the generated factory creates the first immutable wrapper. Chain methods create subsequent
44
+ wrappers, while result methods return a value directly.
45
+
46
+ ## Method groups
47
+
48
+ Every function belongs to one of three method groups.
49
+
50
+ ### `chain`
51
+
52
+ Calls the function and wraps its result in a new Funcade API. The original wrapper is not changed.
53
+ A chain method must return a value assignable to the current value type.
54
+
55
+ ```ts
56
+ const numbers = funcade({
57
+ init: (value: number) => value,
58
+ methods: [
59
+ chain({
60
+ add: (value: number, amount: number) => value + amount,
61
+ }),
62
+ ],
63
+ })
64
+
65
+ const original = numbers(2)
66
+ const changed = original.add(3)
67
+
68
+ // original and changed are different wrappers
69
+ ```
70
+
71
+ ### `result`
72
+
73
+ Calls the function and returns its result directly, ending the chain. Result methods may return any
74
+ type.
75
+
76
+ ```ts
77
+ const numbers = funcade({
78
+ init: (value: number) => value,
79
+ methods: [
80
+ result({
81
+ isPositive: (value: number) => value > 0,
82
+ }),
83
+ ],
84
+ })
85
+
86
+ const positive = numbers(2).isPositive()
87
+ // positive: boolean
88
+ ```
89
+
90
+ ### `tap`
91
+
92
+ Calls the function for an observation or side effect and returns the same wrapper.
93
+
94
+ ```ts
95
+ const numbers = funcade({
96
+ init: (value: number) => value,
97
+ methods: [
98
+ tap({
99
+ inspect: (value: number, label: string) => console.log(label, value),
100
+ }),
101
+ result({ read: (value: number) => value }),
102
+ ],
103
+ })
104
+
105
+ const value = numbers(2).inspect('current').read()
106
+ ```
107
+
108
+ Method names must be unique across all groups in one Funcade API.
109
+
110
+ ## Calling strategies
111
+
112
+ A strategy defines how a source function receives the current value and method arguments.
113
+
114
+ ### Data-first
115
+
116
+ The default strategy expects the current value as the first argument:
117
+
118
+ ```ts
119
+ fn(value, ...methodArguments)
120
+ ```
121
+
122
+ This is equivalent to passing `dataFirst` explicitly:
123
+
124
+ ```ts
125
+ chain({ add })
126
+ chain({ add }, dataFirst)
127
+ ```
128
+
129
+ ### Curried FP functions
130
+
131
+ The `fp` strategy first applies the method arguments, then passes the current value to the returned
132
+ function:
133
+
134
+ ```ts
135
+ fn(...methodArguments)(value)
136
+ ```
137
+
138
+ ```ts
139
+ import { add, format } from 'date-fns/fp'
140
+ import { chain, fp, funcade, result } from 'funcade'
141
+
142
+ const date = funcade({
143
+ init: (value: Date) => value,
144
+ methods: [chain({ add }, fp), result({ format }, fp)],
145
+ })
146
+
147
+ date(new Date()).add({ months: 1 }).format('yyyy-MM-dd')
148
+ ```
149
+
150
+ ## Custom strategies
151
+
152
+ Use `defineStrategy` when a library has a different application model. A custom strategy consists
153
+ of two independent parts:
154
+
155
+ - The function passed to `defineStrategy` implements the runtime call.
156
+ - A `StrategySpec` describes the same call to TypeScript. It does not exist at runtime.
157
+
158
+ ### How a strategy specification works
159
+
160
+ `StrategySpec` acts as a type-level function. Funcade evaluates it separately for every source
161
+ function overload and provides two placeholders:
162
+
163
+ - `this['_fn']` is the source function overload currently being converted into a method.
164
+ - `this['_value']` is the value stored in the current Funcade wrapper.
165
+
166
+ The specification derives four public fields from those placeholders:
167
+
168
+ | Field | Meaning |
169
+ | -------- | ------------------------------------------------------------------------------------- |
170
+ | `args` | Arguments exposed by the generated object method. |
171
+ | `result` | Declared return type of the strategy. For `chain`, it must preserve the wrapped type. |
172
+ | `input` | Value type accepted by the source operation. |
173
+ | `value` | Relevant type represented by the current wrapped value. |
174
+
175
+ Funcade considers a method compatible when `value` is assignable to `input`. For a `chain` group,
176
+ it additionally checks that `result` is assignable to the type of `this['_value']`. This is why
177
+ `input` and `value` are separate: the wrapped value may be a container, such as a schema, while the
178
+ operation consumes the type represented by that container.
179
+
180
+ A specification has this general data flow:
181
+
182
+ ```text
183
+ this['_fn'] -> args, input
184
+ this['_value'] -> value
185
+ runtime call -> result
186
+ ```
187
+
188
+ Do not assign `_fn` or `_value` in the specification. They are type-only placeholders supplied by
189
+ Funcade through `this`. The other fields should derive from them instead of referring to one
190
+ concrete function or wrapped value type. If a source function is overloaded, Funcade evaluates the
191
+ specification for each overload independently before assembling the generated method overloads.
192
+
193
+ The runtime callback intentionally accepts broad function, value, and argument types. TypeScript
194
+ cannot prove that its implementation matches the specification, so the specification is a contract
195
+ maintained by the strategy author. In particular, `result` must describe what the callback actually
196
+ returns, and the callback must interpret `args` in the same way as the `args` field.
197
+
198
+ ### Valibot example
199
+
200
+ Valibot actions are not called with a schema. An action is created from the method arguments and
201
+ then composed with the current schema through `v.pipe`:
202
+
203
+ ```ts
204
+ action(...methodArguments) // creates an action
205
+ v.pipe(schema, action) // creates the next schema
206
+ ```
207
+
208
+ The corresponding specification needs to connect three different types: the action constructor,
209
+ the schema object, and the data flowing through that schema. Here is the complete strategy:
210
+
211
+ ```ts
212
+ import * as v from 'valibot'
213
+ import { defineStrategy } from 'funcade'
214
+ import type { StrategySpec } from 'funcade'
215
+
216
+ type InputOf<T> = T extends {
217
+ readonly '~types'?: { readonly input: infer Input } | undefined
218
+ }
219
+ ? Input
220
+ : unknown
221
+
222
+ type OutputOf<T> = T extends {
223
+ readonly '~types'?: { readonly output: infer Output } | undefined
224
+ }
225
+ ? Output
226
+ : T
227
+
228
+ interface ValibotStrategySpec extends StrategySpec {
229
+ readonly args: Parameters<this['_fn']>
230
+ readonly result: this['_value']
231
+ readonly input: InputOf<ReturnType<this['_fn']>>
232
+ readonly value: OutputOf<this['_value']>
233
+ }
234
+
235
+ const valibotStrategy = defineStrategy<ValibotStrategySpec>((action, schema, args) =>
236
+ v.pipe(schema, action(...args)),
237
+ )
238
+ ```
239
+
240
+ Here is how each field maps to Valibot:
241
+
242
+ - `args` preserves all parameters of the action constructor. For example, `v.minLength` becomes
243
+ `.minLength(requirement, message?)`.
244
+ - `result` declares that `v.pipe` returns another schema suitable for the same wrapper API. Keeping
245
+ it as `this['_value']` allows the operation to be used in a `chain` group.
246
+ - `input` extracts the data type accepted by the created action.
247
+ - `value` extracts the output type represented by the current schema.
248
+
249
+ For a string schema and `v.minLength`, both compatibility types are `string`. With `v.minSize`, the
250
+ action input is `SizeInput`, which is incompatible with the schema's `string` output, so Funcade
251
+ rejects the method where the group is declared.
252
+
253
+ For one compatible `v.minLength` overload, Funcade evaluates the specification approximately as
254
+ follows:
255
+
256
+ ```text
257
+ _fn = (requirement: number, message?: ErrorMessage<string>) => MinLengthAction<string, ...>
258
+ _value = StringSchema<...>
259
+
260
+ args = [requirement: number, message?: ErrorMessage<string>]
261
+ result = StringSchema<...>
262
+ input = string
263
+ value = string
264
+ ```
265
+
266
+ The generated method therefore accepts the action constructor's arguments, while compatibility is
267
+ checked against the data flowing through the schema rather than against the schema object itself.
268
+
269
+ The complete API can then use the custom strategy like any built-in strategy:
270
+
271
+ ```ts
272
+ import * as v from 'valibot'
273
+ import { chain, funcade, result } from 'funcade'
274
+
275
+ // valibotStrategy is defined above.
276
+
277
+ const stringSchema = funcade({
278
+ init: () => v.string(),
279
+ methods: [
280
+ chain({ trim: v.trim, minLength: v.minLength }, valibotStrategy),
281
+ result({ parse: v.parse, safeParse: v.safeParse }),
282
+ ],
283
+ })
284
+
285
+ stringSchema().trim().minLength(3).parse(' hello ')
286
+ // => 'hello'
287
+ ```
@@ -0,0 +1,43 @@
1
+ import { BivariantFn, FnMap, IsAny } from "./types.js";
2
+ import { SpecFor, Strategy } from "./strategies.js";
3
+ import { MethodGroup, MethodGroupBase, MethodKind } from "./methods.js";
4
+ type TypeError<T extends readonly unknown[]> = {
5
+ readonly __typeError: T;
6
+ };
7
+ type UnionToIntersection<T> = (T extends unknown ? (value: T) => void : never) extends ((value: infer U) => void) ? U : never;
8
+ type SpecArgs<Spec> = Spec extends {
9
+ readonly args: infer Args extends readonly unknown[];
10
+ } ? Args : never;
11
+ type SpecResult<Spec> = Spec extends {
12
+ readonly result: infer Result;
13
+ } ? Result : never;
14
+ type AnyArray = readonly unknown[];
15
+ type FunctionProperties<T extends object> = { [Key in keyof T]: T[Key]; };
16
+ /** @ts-expect-error TypeScript cannot verify extending the generic object type T. */
17
+ interface AddSignature<T extends object, Args extends AnyArray, Result> extends T {
18
+ (...args: Args): Result;
19
+ }
20
+ type Signature<Args extends AnyArray, Result> = (...args: Args) => Result;
21
+ type MethodResult<Kind extends MethodKind, Spec, Value, Groups extends readonly MethodGroup[]> = Kind extends "result" ? SpecResult<Spec> : FuncadeApi<Value, Groups>;
22
+ type IsAssignable<Value, Input> = IsAny<Value> extends true ? true : Value extends Input ? true : false;
23
+ type IsCompatible<Spec> = Spec extends {
24
+ readonly input: infer Input;
25
+ readonly value: infer Actual;
26
+ } ? IsAssignable<Actual, Input> : true;
27
+ type IsMethodCompatible<Spec, Kind extends MethodKind, Value> = IsCompatible<Spec> extends true ? Kind extends "chain" ? IsAssignable<SpecResult<Spec>, Value> : true : false;
28
+ type AddMethodSignature<Shape extends object, Args extends AnyArray, Result, S extends Strategy, Value, Kind extends MethodKind, Groups extends readonly MethodGroup[], Spec = SpecFor<Signature<Args, Result>, S, Value>> = IsMethodCompatible<Spec, Kind, Value> extends true ? AddSignature<Shape, SpecArgs<Spec>, MethodResult<Kind, Spec, Value, Groups>> : Shape;
29
+ type MapMethodOverloads<Fn extends object, S extends Strategy, Value, Kind extends MethodKind, Groups extends readonly MethodGroup[], Shape extends object, ResultShape extends object> = Shape extends Fn ? ResultShape : Fn extends AddSignature<Shape, infer Args, infer Result> ? MapMethodOverloads<Fn, S, Value, Kind, Groups, AddSignature<Shape, Args, Result>, AddMethodSignature<ResultShape, Args, Result, S, Value, Kind, Groups>> : ResultShape;
30
+ type MethodOverloads<Fn extends object, S extends Strategy, Value, Kind extends MethodKind, Groups extends readonly MethodGroup[]> = MapMethodOverloads<Fn, S, Value, Kind, Groups, FunctionProperties<Fn>, object>;
31
+ type GroupMethods<Value, Groups extends readonly MethodGroup[], Kind extends MethodKind, Methods extends FnMap, S extends Strategy> = { [Key in keyof Methods]: MethodOverloads<Methods[Key], S, Value, Kind, Groups>; };
32
+ type MethodsFromGroup<Value, Groups extends readonly MethodGroup[], Group> = Group extends MethodGroupBase<infer Kind, infer Methods, infer S> ? GroupMethods<Value, Groups, Kind, Methods, S> : unknown;
33
+ type FuncadeApi<Value, Groups extends readonly MethodGroup[]> = UnionToIntersection<MethodsFromGroup<Value, Groups, Groups[number]>>;
34
+ type HasCompatibleOverload<Fn extends object, S extends Strategy, Value, Kind extends MethodKind> = MethodOverloads<Fn, S, Value, Kind, readonly []> extends BivariantFn ? true : false;
35
+ type IncompatibleMethods<Value, Kind extends MethodKind, Methods extends FnMap, S extends Strategy> = { [Key in keyof Methods]: HasCompatibleOverload<Methods[Key], S, Value, Kind> extends true ? never : TypeError<[Key, "method is incompatible with the current value"]>; }[keyof Methods];
36
+ type IncompatibleGroup<Value, Group> = Group extends MethodGroupBase<infer Kind, infer Methods, infer S> ? IncompatibleMethods<Value, Kind, Methods, S> : never;
37
+ type ValidatedGroups<Value, Groups extends readonly MethodGroup[]> = { [Index in keyof Groups]: [IncompatibleGroup<Value, Groups[Index]>] extends [never] ? Groups[Index] : IncompatibleGroup<Value, Groups[Index]>; };
38
+ type FuncadeConfig<Init extends BivariantFn, Groups extends readonly MethodGroup[]> = {
39
+ init: Init;
40
+ methods: Groups & ValidatedGroups<ReturnType<NoInfer<Init>>, NoInfer<Groups>>;
41
+ };
42
+ declare function funcade<const Init extends BivariantFn, const Groups extends readonly MethodGroup[]>(config: FuncadeConfig<Init, Groups>): (...args: Parameters<Init>) => FuncadeApi<ReturnType<Init>, Groups>;
43
+ export { FuncadeApi, FuncadeConfig, TypeError, funcade };
@@ -0,0 +1,29 @@
1
+ const apiValue = Symbol("funcade.apiValue");
2
+ function funcade(config) {
3
+ const prototype = Object.create(null);
4
+ const createApi = (value) => {
5
+ const api = Object.create(prototype);
6
+ api[apiValue] = value;
7
+ return api;
8
+ };
9
+ for (const group of config.methods) {
10
+ const { kind, strategy } = group;
11
+ for (const [name, fn] of Object.entries(group.methods)) {
12
+ if (Object.hasOwn(prototype, name)) throw new Error(`Duplicate objectified API method: "${name}"`);
13
+ let method;
14
+ if (kind === "result") method = function(...args) {
15
+ return strategy(fn, this[apiValue], args);
16
+ };
17
+ else if (kind === "tap") method = function(...args) {
18
+ strategy(fn, this[apiValue], args);
19
+ return this;
20
+ };
21
+ else method = function(...args) {
22
+ return createApi(strategy(fn, this[apiValue], args));
23
+ };
24
+ prototype[name] = method;
25
+ }
26
+ }
27
+ return (...args) => createApi(config.init(...args));
28
+ }
29
+ export { funcade };
@@ -0,0 +1,4 @@
1
+ import { DataFirstStrategy, FpStrategy, Strategy, StrategyApply, StrategySpec, dataFirst, defineStrategy, fp } from "./strategies.js";
2
+ import { ChainGroup, MethodGroup, MethodGroupBase, MethodKind, ResultGroup, TapGroup, chain, result, tap } from "./methods.js";
3
+ import { FuncadeApi, FuncadeConfig, TypeError, funcade } from "./funcade.js";
4
+ export { type ChainGroup, type DataFirstStrategy, type FpStrategy, type FuncadeApi, type FuncadeConfig, type MethodGroup, type MethodGroupBase, type MethodKind, type ResultGroup, type Strategy, type StrategyApply, type StrategySpec, type TapGroup, type TypeError, chain, dataFirst, defineStrategy, fp, funcade, result, tap };
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ import { funcade } from "./funcade.js";
2
+ import { dataFirst, defineStrategy, fp } from "./strategies.js";
3
+ import { chain, result, tap } from "./methods.js";
4
+ export { chain, dataFirst, defineStrategy, fp, funcade, result, tap };
@@ -0,0 +1,16 @@
1
+ import { FnMap } from "./types.js";
2
+ import { DataFirstStrategy, Strategy } from "./strategies.js";
3
+ type MethodKind = "chain" | "result" | "tap";
4
+ type MethodGroupBase<Kind extends MethodKind, Methods extends FnMap = FnMap, S extends Strategy = Strategy> = Readonly<{
5
+ kind: Kind;
6
+ strategy: S;
7
+ methods: Methods;
8
+ }>;
9
+ type ChainGroup<Methods extends FnMap = FnMap, S extends Strategy = Strategy> = MethodGroupBase<"chain", Methods, S>;
10
+ type ResultGroup<Methods extends FnMap = FnMap, S extends Strategy = Strategy> = MethodGroupBase<"result", Methods, S>;
11
+ type TapGroup<Methods extends FnMap = FnMap, S extends Strategy = Strategy> = MethodGroupBase<"tap", Methods, S>;
12
+ type MethodGroup = MethodGroupBase<MethodKind>;
13
+ declare const chain: <const Methods extends FnMap, S extends Strategy = DataFirstStrategy>(methods: Methods, strategy?: S) => ChainGroup<Methods, NoInfer<S>>;
14
+ declare const result: <const Methods extends FnMap, S extends Strategy = DataFirstStrategy>(methods: Methods, strategy?: S) => ResultGroup<Methods, NoInfer<S>>;
15
+ declare const tap: <const Methods extends FnMap, S extends Strategy = DataFirstStrategy>(methods: Methods, strategy?: S) => TapGroup<Methods, NoInfer<S>>;
16
+ export { ChainGroup, MethodGroup, MethodGroupBase, MethodKind, ResultGroup, TapGroup, chain, result, tap };
@@ -0,0 +1,10 @@
1
+ import { dataFirst } from "./strategies.js";
2
+ const createGroup = (kind, methods, strategy) => ({
3
+ kind,
4
+ strategy: strategy ?? dataFirst,
5
+ methods
6
+ });
7
+ const chain = (methods, strategy) => createGroup("chain", methods, strategy);
8
+ const result = (methods, strategy) => createGroup("result", methods, strategy);
9
+ const tap = (methods, strategy) => createGroup("tap", methods, strategy);
10
+ export { chain, result, tap };
@@ -0,0 +1,54 @@
1
+ import { AnyFn, BivariantFn, IsAny } from "./types.js";
2
+ type Tail<T extends readonly unknown[]> = T extends readonly [unknown, ...infer Rest] ? Rest : [];
3
+ type StrategyApply = (fn: AnyFn, value: any, args: readonly unknown[]) => unknown;
4
+ type StrategySpec = Readonly<{
5
+ _fn: BivariantFn;
6
+ _value: unknown;
7
+ args: unknown[];
8
+ result: unknown;
9
+ input: unknown;
10
+ value: unknown;
11
+ }>;
12
+ type FnOf<T> = T extends BivariantFn ? T : never;
13
+ type ApplyStrategySpec<GetSpec extends StrategySpec, Fn, Value> = {
14
+ args: (GetSpec & {
15
+ readonly _fn: Fn;
16
+ readonly _value: Value;
17
+ })["args"];
18
+ result: (GetSpec & {
19
+ readonly _fn: Fn;
20
+ readonly _value: Value;
21
+ })["result"];
22
+ input: (GetSpec & {
23
+ readonly _fn: Fn;
24
+ })["input"];
25
+ value: (GetSpec & {
26
+ readonly _value: Value;
27
+ })["value"];
28
+ };
29
+ type Strategy<GetSpec extends StrategySpec = StrategySpec> = StrategyApply & {
30
+ readonly __spec: GetSpec;
31
+ };
32
+ interface DataFirstSpec extends StrategySpec {
33
+ readonly args: Tail<Parameters<FnOf<this["_fn"]>>>;
34
+ readonly result: ReturnType<FnOf<this["_fn"]>>;
35
+ readonly input: Parameters<FnOf<this["_fn"]>>[0];
36
+ readonly value: this["_value"];
37
+ }
38
+ interface FpSpec extends StrategySpec {
39
+ readonly args: CurriedApplyArgs<FnOf<this["_fn"]>>;
40
+ readonly result: CurriedApplyResult<FnOf<this["_fn"]>>;
41
+ readonly input: CurriedValue<FnOf<this["_fn"]>>;
42
+ readonly value: this["_value"];
43
+ }
44
+ type DataFirstStrategy = Strategy<DataFirstSpec>;
45
+ type FpStrategy = Strategy<FpSpec>;
46
+ type SpecFor<Fn, S extends Strategy, Value> = ApplyStrategySpec<S["__spec"], Fn, Value>;
47
+ type CurriedConsumer<Fn extends BivariantFn> = IsAny<ReturnType<Fn>> extends true ? never : ReturnType<Fn> extends BivariantFn ? ReturnType<Fn> : never;
48
+ type CurriedApplyArgs<Fn extends BivariantFn> = CurriedConsumer<Fn> extends never ? never : Parameters<Fn>;
49
+ type CurriedApplyResult<Fn extends BivariantFn> = ReturnType<CurriedConsumer<Fn>>;
50
+ type CurriedValue<Fn extends BivariantFn> = Parameters<CurriedConsumer<Fn>>[0];
51
+ declare const defineStrategy: <GetSpec extends StrategySpec = StrategySpec>(apply: StrategyApply) => Strategy<GetSpec>;
52
+ declare const dataFirst: Strategy<DataFirstSpec>;
53
+ declare const fp: Strategy<FpSpec>;
54
+ export { DataFirstStrategy, FpStrategy, SpecFor, Strategy, StrategyApply, StrategySpec, dataFirst, defineStrategy, fp };
@@ -0,0 +1,8 @@
1
+ const defineStrategy = (apply) => apply;
2
+ const dataFirst = defineStrategy((fn, value, args) => fn(value, ...args));
3
+ const fp = defineStrategy((fn, value, args) => {
4
+ const partiallyApplied = fn(...args);
5
+ if (typeof partiallyApplied !== "function") throw new TypeError("FP strategy expects a function that returns another function before receiving the current value");
6
+ return partiallyApplied(value);
7
+ });
8
+ export { dataFirst, defineStrategy, fp };
@@ -0,0 +1,7 @@
1
+ type BivariantFn = {
2
+ fn(...args: unknown[]): unknown;
3
+ }["fn"];
4
+ type FnMap = Record<string, BivariantFn>;
5
+ type AnyFn = (...args: any[]) => any;
6
+ type IsAny<T> = 0 extends 1 & T ? true : false;
7
+ export { AnyFn, BivariantFn, FnMap, IsAny };
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "funcade",
3
+ "type": "module",
4
+ "version": "1.0.0",
5
+ "description": "Build type-safe fluent object APIs from functional JavaScript libraries",
6
+ "author": "Maxim Molochkov <klaseca@gmail.com> (https://github.com/klaseca)",
7
+ "keywords": [
8
+ "functional-programming",
9
+ "fp",
10
+ "fluent-api",
11
+ "method-chaining",
12
+ "api-adapter",
13
+ "facade",
14
+ "currying",
15
+ "immutable",
16
+ "wrapper"
17
+ ],
18
+ "license": "MIT",
19
+ "homepage": "https://github.com/klaseca/funcade#readme",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/klaseca/funcade.git"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/klaseca/funcade/issues"
26
+ },
27
+ "devEngines": {
28
+ "runtime": {
29
+ "name": "node",
30
+ "version": ">=24.18"
31
+ },
32
+ "packageManager": {
33
+ "name": "npm",
34
+ "version": ">=11"
35
+ }
36
+ },
37
+ "exports": {
38
+ ".": "./dist/index.js",
39
+ "./package.json": "./package.json"
40
+ },
41
+ "files": [
42
+ "dist"
43
+ ],
44
+ "scripts": {
45
+ "prepare": "husky",
46
+ "build": "tsdown",
47
+ "dev": "tsdown --watch",
48
+ "test": "node --test",
49
+ "test:watch": "node --test --watch",
50
+ "typecheck": "tsc --noEmit",
51
+ "fmt": "prettier -w -u .",
52
+ "prerelease": "npm run typecheck && npm run test",
53
+ "release": "bumpp",
54
+ "prepublishOnly": "npm run build"
55
+ },
56
+ "devDependencies": {
57
+ "@types/node": "^26.1.1",
58
+ "bumpp": "^11.1.0",
59
+ "date-fns": "^4.1.0",
60
+ "expect-type": "^1.4.0",
61
+ "husky": "^9.1.7",
62
+ "lint-staged": "^17.0.8",
63
+ "prettier": "^3.9.5",
64
+ "tsdown": "^0.22.9",
65
+ "typescript": "^7.0.2",
66
+ "valibot": "^1.4.2"
67
+ }
68
+ }