Skip to content

Commit b815755

Browse files
committed
More TypeScript improvements
1 parent e405cc8 commit b815755

File tree

6 files changed

+285
-8
lines changed

6 files changed

+285
-8
lines changed

bunfig.toml

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
[test]
22
coverage = true
3+
coverageThreshold = 1

tests/appliesTests.ts renamed to src/appliesTests.ts

+2
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,8 @@ export default [
411411
{ integers: [1, 2, 3] },
412412
[1, 3],
413413
],
414+
// Invalid filter
415+
[{ filter: [{ var: "integers" }, true] }, { integers: 123 }, []],
414416

415417
[
416418
{ map: [{ var: "integers" }, { "*": [{ var: "" }, 2] }] },

tests/logic.test.ts renamed to src/logic.test.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, spyOn, test } from "bun:test";
2-
import jsonLogic from "../src/logic";
2+
import jsonLogic from "./logic.js";
33
import appliesTests from "./appliesTests.js";
44
import rule_likeTests from "./rule_likeTests.js";
55

@@ -32,11 +32,17 @@ describe("rule_like()", () => {
3232
}
3333
});
3434

35+
describe("uses_data()", () => {
36+
test(`works`, () => {
37+
expect(jsonLogic.uses_data({ a: [1, {var: "b"}] })).toEqual(['b']);
38+
});
39+
});
40+
3541
test("Bad operator", () => {
3642
expect(() => jsonLogic.apply({ fubar: [] })).toThrow(
3743
/Unrecognized operation/
3844
);
39-
expect(() => jsonLogic.apply({ 'fubar.rabuf': [] })).toThrow(
45+
expect(() => jsonLogic.apply({ "fubar.rabuf": [] })).toThrow(
4046
/Unrecognized operation/
4147
);
4248
});

src/logic.ts

+16-6
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const arrayUnique = (array: any[]) => {
1919

2020
const operations: Record<
2121
ProbablyBasicReservedOperations,
22-
(...args: any[]) => any
22+
((...args: any[]) => any) | Record<string, (...args: any[]) => any>
2323
> = {
2424
"==": (a, b) => a == b,
2525
"===": (a, b) => a === b,
@@ -265,14 +265,20 @@ const apply = (logic: Record<string, any>, data: any): any => {
265265
}
266266
return false; // None were truthy
267267
} else if (op === "var") {
268-
return operations.var(data, ...values.map((val: any) => apply(val, data)));
268+
// TODO: Remove `as any`
269+
return (operations.var as any)(
270+
data,
271+
...values.map((val: any) => apply(val, data))
272+
);
269273
} else if (op === "missing") {
270-
return operations.missing(
274+
// TODO: Remove `as any`
275+
return (operations.missing as any)(
271276
data,
272277
...values.map((val: any) => apply(val, data))
273278
);
274279
} else if (op === "missing_some") {
275-
return operations.missing_some(
280+
// TODO: Remove `as any`
281+
return (operations.missing_some as any)(
276282
data,
277283
...values.map((val: any) => apply(val, data))
278284
);
@@ -284,7 +290,8 @@ const apply = (logic: Record<string, any>, data: any): any => {
284290
// Structured commands like % or > can name formal arguments while flexible commands (like missing or merge) can operate on the pseudo-array arguments
285291
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments
286292
if (operations.hasOwnProperty(op) && typeof operations[op] === "function") {
287-
return operations[op](...valuesApplied);
293+
// TODO: Remove `as any`
294+
return (operations[op] as any)(...valuesApplied);
288295
} else if (op.indexOf(".") > 0) {
289296
// Contains a dot, and not in the 0th position
290297
const sub_ops = `${op}`.split(".");
@@ -331,7 +338,10 @@ const uses_data = (logic: any) => {
331338
return arrayUnique(collection);
332339
};
333340

334-
const add_operation = (name: string, code: (...args: any[]) => any) => {
341+
const add_operation = (
342+
name: string,
343+
code: ((...args: any[]) => any) | Record<string, (...args: any[]) => any>
344+
) => {
335345
operations[name] = code;
336346
};
337347

tests/rule_likeTests.ts renamed to src/rule_likeTests.ts

+5
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ export default [
4646
[["falafel"], ["string"], true],
4747
[[1, "falafel", []], ["number", "string", "array"], true],
4848

49+
// Mismatched rule/pattern
50+
[{ "*": [0.01, { var: "goods" }] }, { if: ["number", "@"] }, false],
51+
[["some", "array"], ["some array"], false],
52+
["not an array", ["an array"], false],
53+
4954
// Taxes, rules of different specificity
5055
[{ "*": [0.01, { var: "goods" }] }, { "*": ["number", "@"] }, true],
5156
[{ "*": [0.01, { var: "goods" }] }, { "*": ["number", { "@": "@" }] }, true],

src/types.ts

+253
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,256 @@ export type ProbablyBasicReservedOperations =
5050
| (string & {});
5151

5252
export type ProbablyReservedOperations = ReservedOperations | (string & {});
53+
54+
type RenameToIn<T> = {
55+
[K in keyof T as K extends `in${Uppercase<string>}${Lowercase<string>}`
56+
? `in`
57+
: K]: T[K];
58+
};
59+
60+
type MAXIMUM_ALLOWED_BOUNDARY = 80;
61+
/**
62+
* This is a utility type used below for the "if" operation.
63+
* Original: https://stackoverflow.com/a/68373774/765987
64+
*/
65+
type Mapped<
66+
Tuple extends unknown[],
67+
Result extends unknown[] = [],
68+
Count extends readonly number[] = []
69+
> = Count["length"] extends MAXIMUM_ALLOWED_BOUNDARY
70+
? Result
71+
: Tuple extends []
72+
? []
73+
: Result extends []
74+
? Mapped<Tuple, Tuple, [...Count, 1]>
75+
: Mapped<Tuple, Result | [...Result, ...Tuple], [...Count, 1]>;
76+
/**
77+
* Used for the "if" operation, which takes an array of odd length
78+
* and a minimum of three (3) elements.
79+
*/
80+
type AnyArrayOfOddLengthMin3 = [any, ...Mapped<[any, any]>];
81+
82+
/**
83+
* This can be an object with any key except the reserved keys.
84+
* TODO: Find a way to limit this type to exactly one (1) key, since
85+
* json-logic-js enforces it. See:
86+
* https://github.com/jwadhams/json-logic-js/blob/2.0.2/logic.js#L180
87+
*/
88+
export type AdditionalOperation = Partial<Record<ReservedOperations, never>> & {
89+
[k: string]: any;
90+
};
91+
92+
export interface AllReservedOperationsInterface<
93+
AddOps extends AdditionalOperation = never
94+
> {
95+
var:
96+
| RulesLogic<AddOps>
97+
| [RulesLogic<AddOps>]
98+
| [RulesLogic<AddOps>, any]
99+
| [RulesLogic<AddOps>, any];
100+
missing: RulesLogic<AddOps> | any[];
101+
missing_some: [RulesLogic<AddOps>, RulesLogic<AddOps> | any[]];
102+
if: AnyArrayOfOddLengthMin3;
103+
"==": [any, any];
104+
"===": [any, any];
105+
"!=": [any, any];
106+
"!==": [any, any];
107+
"!": any;
108+
"!!": any;
109+
or: Array<RulesLogic<AddOps>>;
110+
and: Array<RulesLogic<AddOps>>;
111+
">": [RulesLogic<AddOps>, RulesLogic<AddOps>];
112+
">=": [RulesLogic<AddOps>, RulesLogic<AddOps>];
113+
"<":
114+
| [RulesLogic<AddOps>, RulesLogic<AddOps>]
115+
| [RulesLogic<AddOps>, RulesLogic<AddOps>, RulesLogic<AddOps>];
116+
"<=":
117+
| [RulesLogic<AddOps>, RulesLogic<AddOps>]
118+
| [RulesLogic<AddOps>, RulesLogic<AddOps>, RulesLogic<AddOps>];
119+
max: Array<RulesLogic<AddOps>>;
120+
min: Array<RulesLogic<AddOps>>;
121+
"+": Array<RulesLogic<AddOps>> | RulesLogic<AddOps>;
122+
"-": Array<RulesLogic<AddOps>> | RulesLogic<AddOps>;
123+
"*": Array<RulesLogic<AddOps>> | RulesLogic<AddOps>;
124+
"/": Array<RulesLogic<AddOps>> | RulesLogic<AddOps>;
125+
"%": [RulesLogic<AddOps>, RulesLogic<AddOps>];
126+
map: [RulesLogic<AddOps>, RulesLogic<AddOps>];
127+
filter: [RulesLogic<AddOps>, RulesLogic<AddOps>];
128+
reduce: [RulesLogic<AddOps>, RulesLogic<AddOps>, RulesLogic<AddOps>];
129+
all:
130+
| [Array<RulesLogic<AddOps>>, RulesLogic<AddOps>]
131+
| [RulesLogic<AddOps>, RulesLogic<AddOps>];
132+
none:
133+
| [Array<RulesLogic<AddOps>>, RulesLogic<AddOps>]
134+
| [RulesLogic<AddOps>, RulesLogic<AddOps>];
135+
some:
136+
| [Array<RulesLogic<AddOps>>, RulesLogic<AddOps>]
137+
| [RulesLogic<AddOps>, RulesLogic<AddOps>];
138+
merge: Array<Array<RulesLogic<AddOps>> | RulesLogic<AddOps>>;
139+
inArray: [RulesLogic<AddOps>, Array<RulesLogic<AddOps>>];
140+
inString: [RulesLogic<AddOps>, RulesLogic<AddOps>];
141+
cat: Array<RulesLogic<AddOps>>;
142+
substr:
143+
| [RulesLogic<AddOps>, RulesLogic<AddOps>]
144+
| [RulesLogic<AddOps>, RulesLogic<AddOps>, RulesLogic<AddOps>];
145+
log: RulesLogic<AddOps>;
146+
}
147+
148+
export type JsonLogicVar<AddOps extends AdditionalOperation = never> = Pick<
149+
AllReservedOperationsInterface<AddOps>,
150+
"var"
151+
>;
152+
export type JsonLogicMissing<AddOps extends AdditionalOperation = never> = Pick<
153+
AllReservedOperationsInterface<AddOps>,
154+
"missing"
155+
>;
156+
export type JsonLogicMissingSome<AddOps extends AdditionalOperation = never> =
157+
Pick<AllReservedOperationsInterface<AddOps>, "missing_some">;
158+
export type JsonLogicIf = Pick<AllReservedOperationsInterface, "if">;
159+
export type JsonLogicEqual = Pick<AllReservedOperationsInterface, "==">;
160+
export type JsonLogicStrictEqual = Pick<AllReservedOperationsInterface, "===">;
161+
export type JsonLogicNotEqual = Pick<AllReservedOperationsInterface, "!=">;
162+
export type JsonLogicStrictNotEqual = Pick<
163+
AllReservedOperationsInterface,
164+
"!=="
165+
>;
166+
export type JsonLogicNegation = Pick<AllReservedOperationsInterface, "!">;
167+
export type JsonLogicDoubleNegation = Pick<
168+
AllReservedOperationsInterface,
169+
"!!"
170+
>;
171+
export type JsonLogicOr<AddOps extends AdditionalOperation = never> = Pick<
172+
AllReservedOperationsInterface<AddOps>,
173+
"or"
174+
>;
175+
export type JsonLogicAnd<AddOps extends AdditionalOperation = never> = Pick<
176+
AllReservedOperationsInterface<AddOps>,
177+
"and"
178+
>;
179+
export type JsonLogicGreaterThan<AddOps extends AdditionalOperation = never> =
180+
Pick<AllReservedOperationsInterface<AddOps>, ">">;
181+
export type JsonLogicGreaterThanOrEqual<
182+
AddOps extends AdditionalOperation = never
183+
> = Pick<AllReservedOperationsInterface<AddOps>, ">=">;
184+
export type JsonLogicLessThan<AddOps extends AdditionalOperation = never> =
185+
Pick<AllReservedOperationsInterface<AddOps>, "<">;
186+
export type JsonLogicLessThanOrEqual<
187+
AddOps extends AdditionalOperation = never
188+
> = Pick<AllReservedOperationsInterface<AddOps>, "<=">;
189+
export type JsonLogicMax<AddOps extends AdditionalOperation = never> = Pick<
190+
AllReservedOperationsInterface<AddOps>,
191+
"max"
192+
>;
193+
export type JsonLogicMin<AddOps extends AdditionalOperation = never> = Pick<
194+
AllReservedOperationsInterface<AddOps>,
195+
"min"
196+
>;
197+
export type JsonLogicSum<AddOps extends AdditionalOperation = never> = Pick<
198+
AllReservedOperationsInterface<AddOps>,
199+
"+"
200+
>;
201+
export type JsonLogicDifference<AddOps extends AdditionalOperation = never> =
202+
Pick<AllReservedOperationsInterface<AddOps>, "-">;
203+
export type JsonLogicProduct<AddOps extends AdditionalOperation = never> = Pick<
204+
AllReservedOperationsInterface<AddOps>,
205+
"*"
206+
>;
207+
export type JsonLogicQuotient<AddOps extends AdditionalOperation = never> =
208+
Pick<AllReservedOperationsInterface<AddOps>, "/">;
209+
export type JsonLogicRemainder<AddOps extends AdditionalOperation = never> =
210+
Pick<AllReservedOperationsInterface<AddOps>, "%">;
211+
export type JsonLogicMap<AddOps extends AdditionalOperation = never> = Pick<
212+
AllReservedOperationsInterface<AddOps>,
213+
"map"
214+
>;
215+
export type JsonLogicFilter<AddOps extends AdditionalOperation = never> = Pick<
216+
AllReservedOperationsInterface<AddOps>,
217+
"filter"
218+
>;
219+
export type JsonLogicReduce<AddOps extends AdditionalOperation = never> = Pick<
220+
AllReservedOperationsInterface<AddOps>,
221+
"reduce"
222+
>;
223+
export type JsonLogicAll<AddOps extends AdditionalOperation = never> = Pick<
224+
AllReservedOperationsInterface<AddOps>,
225+
"all"
226+
>;
227+
export type JsonLogicNone<AddOps extends AdditionalOperation = never> = Pick<
228+
AllReservedOperationsInterface<AddOps>,
229+
"none"
230+
>;
231+
export type JsonLogicSome<AddOps extends AdditionalOperation = never> = Pick<
232+
AllReservedOperationsInterface<AddOps>,
233+
"some"
234+
>;
235+
export type JsonLogicMerge<AddOps extends AdditionalOperation = never> = Pick<
236+
AllReservedOperationsInterface<AddOps>,
237+
"merge"
238+
>;
239+
export type JsonLogicInArray<AddOps extends AdditionalOperation = never> =
240+
RenameToIn<Pick<AllReservedOperationsInterface<AddOps>, "inArray">>;
241+
export type JsonLogicInString<AddOps extends AdditionalOperation = never> =
242+
RenameToIn<Pick<AllReservedOperationsInterface<AddOps>, "inString">>;
243+
export type JsonLogicCat<AddOps extends AdditionalOperation = never> = Pick<
244+
AllReservedOperationsInterface<AddOps>,
245+
"cat"
246+
>;
247+
export type JsonLogicSubstr<AddOps extends AdditionalOperation = never> = Pick<
248+
AllReservedOperationsInterface<AddOps>,
249+
"substr"
250+
>;
251+
export type JsonLogicLog<AddOps extends AdditionalOperation = never> = Pick<
252+
AllReservedOperationsInterface<AddOps>,
253+
"log"
254+
>;
255+
256+
export type RulesLogic<AddOps extends AdditionalOperation = never> =
257+
| boolean
258+
| string
259+
| number
260+
| JsonLogic<AddOps>;
261+
262+
export type JsonLogic<AddOps extends AdditionalOperation = never> =
263+
// Accessing Data - https://jsonlogic.com/operations.html#accessing-data
264+
| JsonLogicVar<AddOps>
265+
| JsonLogicMissing<AddOps>
266+
| JsonLogicMissingSome<AddOps>
267+
// Logic and Boolean Operations - https://jsonlogic.com/operations.html#logic-and-boolean-operations
268+
| JsonLogicIf
269+
| JsonLogicEqual
270+
| JsonLogicStrictEqual
271+
| JsonLogicNotEqual
272+
| JsonLogicStrictNotEqual
273+
| JsonLogicNegation
274+
| JsonLogicDoubleNegation
275+
| JsonLogicOr<AddOps>
276+
| JsonLogicAnd<AddOps>
277+
// Numeric Operations - https://jsonlogic.com/operations.html#numeric-operations
278+
| JsonLogicGreaterThan<AddOps>
279+
| JsonLogicGreaterThanOrEqual<AddOps>
280+
| JsonLogicLessThan<AddOps>
281+
| JsonLogicLessThanOrEqual<AddOps>
282+
| JsonLogicMax<AddOps>
283+
| JsonLogicMin<AddOps>
284+
| JsonLogicSum<AddOps>
285+
| JsonLogicDifference<AddOps>
286+
| JsonLogicProduct<AddOps>
287+
| JsonLogicQuotient<AddOps>
288+
| JsonLogicRemainder<AddOps>
289+
// Array Operations - https://jsonlogic.com/operations.html#array-operations
290+
| JsonLogicMap<AddOps>
291+
| JsonLogicFilter<AddOps>
292+
| JsonLogicReduce<AddOps>
293+
| JsonLogicAll<AddOps>
294+
| JsonLogicNone<AddOps>
295+
| JsonLogicSome<AddOps>
296+
| JsonLogicMerge<AddOps>
297+
| JsonLogicInArray<AddOps>
298+
// String Operations - https://jsonlogic.com/operations.html#string-operations
299+
| JsonLogicInString<AddOps>
300+
| JsonLogicCat<AddOps>
301+
| JsonLogicSubstr<AddOps>
302+
// Miscellaneous - https://jsonlogic.com/operations.html#miscellaneous
303+
| JsonLogicLog<AddOps>
304+
// Adding Operations (https://jsonlogic.com/add_operation.html)
305+
| AddOps;

0 commit comments

Comments
 (0)