@teamkeel/functions-runtime 0.396.8 → 0.397.0-next.1
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/package.json +1 -1
- package/src/TimePeriod.js +89 -0
- package/src/TimePeriod.test.js +148 -0
- package/src/applyWhereConditions.js +17 -0
- package/src/index.d.ts +33 -0
package/package.json
CHANGED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
class TimePeriod {
|
|
2
|
+
constructor(period = "", value = 0, offset = 0, complete = false) {
|
|
3
|
+
this.period = period;
|
|
4
|
+
this.value = value;
|
|
5
|
+
this.offset = offset;
|
|
6
|
+
this.complete = complete;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
static fromExpression(expression) {
|
|
10
|
+
// Regex pattern
|
|
11
|
+
const pattern =
|
|
12
|
+
/^(this|next|last)?\s*(\d+)?\s*(complete)?\s*(second|minute|hour|day|week|month|year|seconds|minutes|hours|days|weeks|months|years)?$/i;
|
|
13
|
+
|
|
14
|
+
const shorthandPattern = /^(now|today|tomorrow|yesterday)$/i;
|
|
15
|
+
|
|
16
|
+
const shorthandMatch = shorthandPattern.exec(expression.trim());
|
|
17
|
+
if (shorthandMatch) {
|
|
18
|
+
const shorthand = shorthandMatch[1].toLowerCase();
|
|
19
|
+
switch (shorthand) {
|
|
20
|
+
case "now":
|
|
21
|
+
return new TimePeriod();
|
|
22
|
+
case "today":
|
|
23
|
+
return TimePeriod.fromExpression("this day");
|
|
24
|
+
case "tomorrow":
|
|
25
|
+
return TimePeriod.fromExpression("next complete day");
|
|
26
|
+
case "yesterday":
|
|
27
|
+
return TimePeriod.fromExpression("last complete day");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const match = pattern.exec(expression.trim());
|
|
32
|
+
if (!match) {
|
|
33
|
+
throw new Error("Invalid time period expression");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const [, direction, rawValue, isComplete, rawPeriod] = match;
|
|
37
|
+
|
|
38
|
+
let period = rawPeriod ? rawPeriod.toLowerCase().replace(/s$/, "") : "";
|
|
39
|
+
let value = rawValue ? parseInt(rawValue, 10) : 1;
|
|
40
|
+
let complete = Boolean(isComplete);
|
|
41
|
+
let offset = 0;
|
|
42
|
+
|
|
43
|
+
switch (direction?.toLowerCase()) {
|
|
44
|
+
case "this":
|
|
45
|
+
offset = 0;
|
|
46
|
+
complete = true;
|
|
47
|
+
break;
|
|
48
|
+
case "next":
|
|
49
|
+
offset = complete ? 1 : 0;
|
|
50
|
+
break;
|
|
51
|
+
case "last":
|
|
52
|
+
offset = -value;
|
|
53
|
+
break;
|
|
54
|
+
default:
|
|
55
|
+
throw new Error(
|
|
56
|
+
"Time period expression must start with this, next, or last"
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return new TimePeriod(period, value, offset, complete);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
periodStartSQL() {
|
|
64
|
+
let sql = "NOW()";
|
|
65
|
+
if (this.offset !== 0) {
|
|
66
|
+
sql = `${sql} + INTERVAL '${this.offset} ${this.period}'`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (this.complete) {
|
|
70
|
+
sql = `DATE_TRUNC('${this.period}', ${sql})`;
|
|
71
|
+
} else {
|
|
72
|
+
sql = `(${sql})`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return sql;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
periodEndSQL() {
|
|
79
|
+
let sql = this.periodStartSQL();
|
|
80
|
+
if (this.value != 0) {
|
|
81
|
+
sql = `(${sql} + INTERVAL '${this.value} ${this.period}')`;
|
|
82
|
+
}
|
|
83
|
+
return sql;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = {
|
|
88
|
+
TimePeriod,
|
|
89
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { test, expect } from "vitest";
|
|
2
|
+
const { TimePeriod } = require("./TimePeriod");
|
|
3
|
+
|
|
4
|
+
test("shorthands test", async () => {
|
|
5
|
+
const today = TimePeriod.fromExpression("today");
|
|
6
|
+
expect(today).toEqual({
|
|
7
|
+
period: "day",
|
|
8
|
+
value: 1,
|
|
9
|
+
complete: true,
|
|
10
|
+
offset: 0,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const tomorrow = TimePeriod.fromExpression("tomorrow");
|
|
14
|
+
expect(tomorrow).toEqual({
|
|
15
|
+
period: "day",
|
|
16
|
+
value: 1,
|
|
17
|
+
complete: true,
|
|
18
|
+
offset: 1,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const yesterday = TimePeriod.fromExpression("yesterday");
|
|
22
|
+
expect(yesterday).toEqual({
|
|
23
|
+
period: "day",
|
|
24
|
+
value: 1,
|
|
25
|
+
complete: true,
|
|
26
|
+
offset: -1,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const now = TimePeriod.fromExpression("now");
|
|
30
|
+
expect(now).toEqual({
|
|
31
|
+
period: "",
|
|
32
|
+
value: 0,
|
|
33
|
+
complete: false,
|
|
34
|
+
offset: 0,
|
|
35
|
+
});
|
|
36
|
+
expect(now.periodStartSQL()).toEqual("(NOW())");
|
|
37
|
+
expect(now.periodEndSQL()).toEqual("(NOW())");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("next test", async () => {
|
|
41
|
+
let period = TimePeriod.fromExpression("next day");
|
|
42
|
+
expect(period).toEqual({
|
|
43
|
+
period: "day",
|
|
44
|
+
value: 1,
|
|
45
|
+
complete: false,
|
|
46
|
+
offset: 0,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
period = TimePeriod.fromExpression("next complete day");
|
|
50
|
+
expect(period).toEqual({
|
|
51
|
+
period: "day",
|
|
52
|
+
value: 1,
|
|
53
|
+
complete: true,
|
|
54
|
+
offset: 1,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
period = TimePeriod.fromExpression("next 5 complete day");
|
|
58
|
+
expect(period).toEqual({
|
|
59
|
+
period: "day",
|
|
60
|
+
value: 5,
|
|
61
|
+
complete: true,
|
|
62
|
+
offset: 1,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
period = TimePeriod.fromExpression("next 5 months");
|
|
66
|
+
expect(period).toEqual({
|
|
67
|
+
period: "month",
|
|
68
|
+
value: 5,
|
|
69
|
+
complete: false,
|
|
70
|
+
offset: 0,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
period = TimePeriod.fromExpression("next 2 complete years");
|
|
74
|
+
expect(period).toEqual({
|
|
75
|
+
period: "year",
|
|
76
|
+
value: 2,
|
|
77
|
+
complete: true,
|
|
78
|
+
offset: 1,
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("last test", async () => {
|
|
83
|
+
let period = TimePeriod.fromExpression("last day");
|
|
84
|
+
expect(period).toEqual({
|
|
85
|
+
period: "day",
|
|
86
|
+
value: 1,
|
|
87
|
+
complete: false,
|
|
88
|
+
offset: -1,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
period = TimePeriod.fromExpression("last complete day");
|
|
92
|
+
expect(period).toEqual({
|
|
93
|
+
period: "day",
|
|
94
|
+
value: 1,
|
|
95
|
+
complete: true,
|
|
96
|
+
offset: -1,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
period = TimePeriod.fromExpression("last 5 complete day");
|
|
100
|
+
expect(period).toEqual({
|
|
101
|
+
period: "day",
|
|
102
|
+
value: 5,
|
|
103
|
+
complete: true,
|
|
104
|
+
offset: -5,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
period = TimePeriod.fromExpression("last 5 months");
|
|
108
|
+
expect(period).toEqual({
|
|
109
|
+
period: "month",
|
|
110
|
+
value: 5,
|
|
111
|
+
complete: false,
|
|
112
|
+
offset: -5,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
period = TimePeriod.fromExpression("last 2 complete years");
|
|
116
|
+
expect(period).toEqual({
|
|
117
|
+
period: "year",
|
|
118
|
+
value: 2,
|
|
119
|
+
complete: true,
|
|
120
|
+
offset: -2,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
period = TimePeriod.fromExpression("last complete year");
|
|
124
|
+
expect(period).toEqual({
|
|
125
|
+
period: "year",
|
|
126
|
+
value: 1,
|
|
127
|
+
complete: true,
|
|
128
|
+
offset: -1,
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("errors test", async () => {
|
|
133
|
+
expect(() => TimePeriod.fromExpression("last test day")).toThrowError(
|
|
134
|
+
"Invalid time period expression"
|
|
135
|
+
);
|
|
136
|
+
expect(() => TimePeriod.fromExpression("today now")).toThrowError(
|
|
137
|
+
"Invalid time period expression"
|
|
138
|
+
);
|
|
139
|
+
expect(() => TimePeriod.fromExpression("last -5 days")).toThrowError(
|
|
140
|
+
"Invalid time period expression"
|
|
141
|
+
);
|
|
142
|
+
expect(() => TimePeriod.fromExpression("5 mont")).toThrowError(
|
|
143
|
+
"Invalid time period expression"
|
|
144
|
+
);
|
|
145
|
+
expect(() => TimePeriod.fromExpression("5 days")).toThrowError(
|
|
146
|
+
"Time period expression must start with this, next, or last"
|
|
147
|
+
);
|
|
148
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { sql, Kysely } = require("kysely");
|
|
2
2
|
const { snakeCase } = require("./casing");
|
|
3
|
+
const { TimePeriod } = require("./TimePeriod");
|
|
3
4
|
|
|
4
5
|
const opMapping = {
|
|
5
6
|
startsWith: { op: "like", value: (v) => `${v}%` },
|
|
@@ -16,6 +17,22 @@ const opMapping = {
|
|
|
16
17
|
onOrAfter: { op: ">=" },
|
|
17
18
|
equals: { op: sql`is not distinct from` },
|
|
18
19
|
notEquals: { op: sql`is distinct from` },
|
|
20
|
+
equalsRelative: {
|
|
21
|
+
op: sql`BETWEEN`,
|
|
22
|
+
value: (v) =>
|
|
23
|
+
sql`${sql.raw(
|
|
24
|
+
TimePeriod.fromExpression(v).periodStartSQL()
|
|
25
|
+
)} AND ${sql.raw(TimePeriod.fromExpression(v).periodEndSQL())}`,
|
|
26
|
+
},
|
|
27
|
+
beforeRelative: {
|
|
28
|
+
op: "<",
|
|
29
|
+
value: (v) =>
|
|
30
|
+
sql`${sql.raw(TimePeriod.fromExpression(v).periodStartSQL())}`,
|
|
31
|
+
},
|
|
32
|
+
afterRelative: {
|
|
33
|
+
op: ">=",
|
|
34
|
+
value: (v) => sql`${sql.raw(TimePeriod.fromExpression(v).periodEndSQL())}`,
|
|
35
|
+
},
|
|
19
36
|
any: {
|
|
20
37
|
isArrayQuery: true,
|
|
21
38
|
greaterThan: { op: ">" },
|
package/src/index.d.ts
CHANGED
|
@@ -29,9 +29,12 @@ export type NumberWhereCondition = {
|
|
|
29
29
|
|
|
30
30
|
export type DateWhereCondition = {
|
|
31
31
|
equals?: Date | string | null;
|
|
32
|
+
equalsRelative?: RelativeDateString | null;
|
|
32
33
|
before?: Date | string | null;
|
|
34
|
+
beforeRelative?: RelativeDateString | null;
|
|
33
35
|
onOrBefore?: Date | string | null;
|
|
34
36
|
after?: Date | string | null;
|
|
37
|
+
afterRelative?: RelativeDateString | null;
|
|
35
38
|
onOrAfter?: Date | string | null;
|
|
36
39
|
};
|
|
37
40
|
|
|
@@ -46,6 +49,9 @@ export type DateQueryInput = {
|
|
|
46
49
|
export type TimestampQueryInput = {
|
|
47
50
|
before: string | null;
|
|
48
51
|
after: string | null;
|
|
52
|
+
equalsRelative?: RelativeDateString | null;
|
|
53
|
+
beforeRelative?: RelativeDateString | null;
|
|
54
|
+
afterRelative?: RelativeDateString | null;
|
|
49
55
|
};
|
|
50
56
|
|
|
51
57
|
export type StringArrayWhereCondition = {
|
|
@@ -239,3 +245,30 @@ export type FunctionConfig = {
|
|
|
239
245
|
export type FuncWithConfig<T> = T & {
|
|
240
246
|
config: FunctionConfig;
|
|
241
247
|
};
|
|
248
|
+
|
|
249
|
+
type unit =
|
|
250
|
+
| "year"
|
|
251
|
+
| "years"
|
|
252
|
+
| "month"
|
|
253
|
+
| "months"
|
|
254
|
+
| "day"
|
|
255
|
+
| "days"
|
|
256
|
+
| "hour"
|
|
257
|
+
| "hours"
|
|
258
|
+
| "minute"
|
|
259
|
+
| "minutes"
|
|
260
|
+
| "second"
|
|
261
|
+
| "seconds";
|
|
262
|
+
type direction = "next" | "last";
|
|
263
|
+
type completed = "complete";
|
|
264
|
+
type value = number;
|
|
265
|
+
|
|
266
|
+
export type RelativeDateString =
|
|
267
|
+
| "now"
|
|
268
|
+
| "today"
|
|
269
|
+
| "tomorrow"
|
|
270
|
+
| "yesterday"
|
|
271
|
+
| `this ${unit}`
|
|
272
|
+
| `${direction} ${unit}`
|
|
273
|
+
| `${direction} ${value} ${unit}`
|
|
274
|
+
| `${direction} ${value} ${completed} ${unit}`;
|