Skip to content

Commit f89a663

Browse files
ci: Fix builds (#2)
1 parent 1c5b04a commit f89a663

File tree

8 files changed

+386
-272
lines changed

8 files changed

+386
-272
lines changed

.github/workflows/CI.yml

Lines changed: 90 additions & 236 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
# cel-typescript
22

33
A TypeScript binding for the Common Expression Language (CEL) using
4-
[cel-rust](https://github.com/clarkmcc/cel-rust). This project provides a
4+
[cel-rust][cel-rust]. This project provides a
55
Node.js native module that allows you to use CEL in your TypeScript/JavaScript
66
projects.
77

8+
[cel-spec]: https://github.com/google/cel-spec
9+
[cel-rust]: https://github.com/clarkmcc/cel-rust
10+
811
## What is CEL?
912

10-
[Common Expression Language (CEL)](https://github.com/google/cel-spec) is an
13+
[Common Expression Language (CEL)][cel-spec] is an
1114
expression language created by Google that implements common semantics for
1215
expression evaluation. It's a simple language for expressing boolean conditions,
1316
calculations, and variable substitutions. CEL is used in various Google products
@@ -20,18 +23,7 @@ business rule evaluation.
2023
npm install @kevinmichaelchen/cel-typescript
2124
```
2225

23-
**Requirements:**
24-
25-
- Node.js 18 or later
26-
27-
This package includes pre-compiled native binaries for multiple platforms:
28-
29-
- macOS (x64, arm64)
30-
- Linux (x64, arm64)
31-
- Windows (x64)
32-
33-
The appropriate binary for your platform will be automatically loaded at
34-
runtime.
26+
Node.js 18 or later is required.
3527

3628
## Usage
3729

@@ -169,16 +161,20 @@ pre-compiled native binaries for all supported platforms:
169161
However, when you install this package, npm will only extract the `.node` file
170162
for your platform. For example:
171163

172-
- On an M1/M2 Mac, only `cel-typescript.darwin-arm64.node` (~7.4 MB) is used
164+
- On Apple Silicon, only `cel-typescript.darwin-arm64.node` (~7.4 MB) is used
173165
- On Windows, only `cel-typescript.win32-x64.node` is used
174166
- On Linux, only `cel-typescript.linux-x64.node` or
175167
`cel-typescript.linux-arm64.node` is used
176168

177-
This is a common pattern for packages with native bindings. For comparison:
169+
This is sometimes a pattern for packages with native bindings. For comparison:
170+
171+
- [`better-sqlite3`][better-sqlite3]: 10.2 MB unpacked
172+
- [`canvas`][canvas]: 408 kB unpacked
173+
- [`sharp`][sharp]: 522 kB unpacked
178174

179-
- `sharp` (image processing): 39.7 MB unpacked
180-
- `better-sqlite3`: 12.8 MB unpacked
181-
- `canvas`: 8.9 MB unpacked
175+
[better-sqlite3]: https://www.npmjs.com/package/better-sqlite3
176+
[canvas]: https://www.npmjs.com/package/canvas
177+
[sharp]: https://www.npmjs.com/package/sharp
182178

183179
#### A Note on Tree-Shaking
184180

__tests__/cel.test.ts

Lines changed: 262 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,15 +152,274 @@ describe("Performance measurements", () => {
152152
// Allow more variation due to system noise, optimization differences, and convenience overhead
153153
const tolerance = 1.0; // Allow 100% variation
154154
const expectedEvaluateTime = compileTime + executeTime;
155-
155+
156156
// Log the actual ratios to help with debugging
157157
const ratio = evaluateTime / expectedEvaluateTime;
158-
console.log(`One-step evaluation was ${ratio.toFixed(2)}x the separate steps`);
159-
158+
console.log(
159+
`One-step evaluation was ${ratio.toFixed(2)}x the separate steps`,
160+
);
161+
160162
// Only fail if the difference is extreme
161163
expect(evaluateTime).toBeGreaterThan(
162164
expectedEvaluateTime * (1 - tolerance),
163165
);
164166
expect(evaluateTime).toBeLessThan(expectedEvaluateTime * (2 + tolerance));
165167
});
166168
});
169+
170+
describe("Timestamp Functions", () => {
171+
it("should correctly parse and compare timestamps", async () => {
172+
const expr = `[
173+
// Test 1: Compare UTC timestamp with itself
174+
timestamp('2025-04-28T12:00:00Z') == timestamp('2025-04-28T12:00:00Z'),
175+
// Test 2: Compare UTC timestamp with equivalent EDT timestamp
176+
timestamp('2025-04-28T12:00:00Z') == timestamp('2025-04-28T08:00:00-04:00'),
177+
// Test 3: Compare timestamps one day apart
178+
timestamp('2025-04-28T12:00:00Z') < timestamp('2025-04-29T12:00:00Z'),
179+
// Test 4: Get components from UTC timestamp
180+
timestamp('2025-04-28T12:00:00Z').getMonth(),
181+
timestamp('2025-04-28T12:00:00Z').getDayOfMonth(),
182+
timestamp('2025-04-28T12:00:00Z').getHours(),
183+
timestamp('2025-04-28T12:00:00Z').getDayOfWeek() // 1==Monday
184+
]`;
185+
const result = await evaluate(expr, {});
186+
expect(result).toEqual([true, true, true, 3, 27, 12, 1]);
187+
});
188+
});
189+
190+
describe("Travel Reservation Rules", () => {
191+
describe("Premium Discount Eligibility", () => {
192+
// Tests a complex pricing rule that considers:
193+
// - Total package cost across flight, hotel, and car
194+
// - Customer loyalty tier
195+
// - Seasonal booking (summer months)
196+
const expr = `
197+
// Calculate total package cost
198+
double(reservation.flight.price) +
199+
double(reservation.hotel.nightlyRate) * int(reservation.hotel.nights) +
200+
double(reservation.car.dailyRate) * int(reservation.car.days) >= 2000.0 &&
201+
// Check loyalty tier
202+
reservation.customer.loyaltyTier in ['GOLD', 'PLATINUM'] &&
203+
// Check if booking is in summer months (0-based: 5=June, 6=July, 7=August)
204+
timestamp(reservation.bookingDate).getMonth() in [5, 6, 7]
205+
`;
206+
let program: CelProgram;
207+
208+
beforeEach(async () => {
209+
program = await CelProgram.compile(expr);
210+
});
211+
212+
it("should qualify for premium discount with valid summer booking", async () => {
213+
const result = await program.execute({
214+
reservation: {
215+
flight: { price: 1000.0 },
216+
hotel: { nightlyRate: 200.0, nights: 4 },
217+
car: { dailyRate: 100.0, days: 4 },
218+
customer: { loyaltyTier: "PLATINUM" },
219+
bookingDate: "2025-07-15T00:00:00Z",
220+
},
221+
});
222+
expect(result).toBe(true);
223+
});
224+
225+
it("should not qualify outside summer months", async () => {
226+
const result = await program.execute({
227+
reservation: {
228+
flight: { price: 1000.0 },
229+
hotel: { nightlyRate: 200.0, nights: 4 },
230+
car: { dailyRate: 100.0, days: 4 },
231+
customer: { loyaltyTier: "PLATINUM" },
232+
bookingDate: "2025-12-15T00:00:00Z",
233+
},
234+
});
235+
expect(result).toBe(false);
236+
program = await CelProgram.compile(expr);
237+
});
238+
});
239+
240+
describe("Multi-condition Booking Validation", () => {
241+
// Tests complex booking validation rules that ensure:
242+
// - All required components are present
243+
// - Logical time sequence of events
244+
// - Location consistency
245+
// - Capacity constraints
246+
const expr = `
247+
has(reservation.flight) &&
248+
timestamp(reservation.flight.departureTime) < timestamp(reservation.hotel.checkIn) &&
249+
timestamp(reservation.hotel.checkIn) < timestamp(reservation.hotel.checkOut) &&
250+
(timestamp(reservation.hotel.checkOut) - timestamp(reservation.hotel.checkIn)) > duration("1h") &&
251+
timestamp(reservation.hotel.checkOut) < timestamp(reservation.flight.returnTime) &&
252+
(reservation.car.pickupLocation == reservation.flight.arrivalAirport ||
253+
reservation.car.pickupLocation == reservation.hotel.address.city) &&
254+
size(reservation.travelers) <= reservation.hotel.maxOccupancy &&
255+
size(reservation.travelers) <= reservation.car.capacity
256+
`;
257+
let program: CelProgram;
258+
259+
beforeEach(async () => {
260+
program = await CelProgram.compile(expr);
261+
});
262+
263+
it("should validate a well-formed booking", async () => {
264+
const result = await program.execute({
265+
reservation: {
266+
flight: {
267+
departureTime: "2025-05-01T10:00:00Z",
268+
returnTime: "2025-05-05T15:00:00Z",
269+
arrivalAirport: "LAX",
270+
},
271+
hotel: {
272+
checkIn: "2025-05-01T15:00:00Z",
273+
checkOut: "2025-05-05T11:00:00Z",
274+
maxOccupancy: 4,
275+
address: { city: "Los Angeles" },
276+
},
277+
car: {
278+
pickupLocation: "LAX",
279+
capacity: 5,
280+
},
281+
travelers: ["person1", "person2", "person3"],
282+
},
283+
});
284+
expect(result).toBe(true);
285+
});
286+
287+
it("should reject invalid time sequence", async () => {
288+
const result = await program.execute({
289+
reservation: {
290+
flight: {
291+
departureTime: "2025-05-01T16:00:00Z", // Later than check-in
292+
returnTime: "2025-05-05T15:00:00Z",
293+
arrivalAirport: "LAX",
294+
},
295+
hotel: {
296+
checkIn: "2025-05-01T15:00:00Z",
297+
checkOut: "2025-05-05T11:00:00Z",
298+
maxOccupancy: 4,
299+
address: { city: "Los Angeles" },
300+
},
301+
car: {
302+
pickupLocation: "LAX",
303+
capacity: 5,
304+
},
305+
travelers: ["person1", "person2", "person3"],
306+
},
307+
});
308+
expect(result).toBe(false);
309+
});
310+
});
311+
312+
describe.skip("Dynamic Pricing with Seasonal Adjustments", () => {
313+
// Tests complex pricing calculations including:
314+
// - Base rates for all components
315+
// - Seasonal multipliers
316+
// - Loyalty discounts
317+
const expr = `
318+
let basePrice = double(reservation.flight.price) +
319+
double(reservation.hotel.nightlyRate) * int(reservation.hotel.nights) +
320+
double(reservation.car.dailyRate) * int(reservation.car.days);
321+
let seasonalPrice = basePrice * (timestamp(reservation.hotel.checkIn).getMonth() in [11, 0, 1] ? 1.25 : 1.0);
322+
seasonalPrice * (1.0 - {'BRONZE': 0.05, 'SILVER': 0.10, 'GOLD': 0.15, 'PLATINUM': 0.20}[reservation.customer.loyaltyTier])
323+
`;
324+
let program: CelProgram;
325+
326+
beforeEach(async () => {
327+
program = await CelProgram.compile(expr);
328+
});
329+
330+
it("should calculate winter pricing with loyalty discount", async () => {
331+
const result = await program.execute({
332+
reservation: {
333+
flight: { price: 1000.0 },
334+
hotel: {
335+
nightlyRate: 200.0,
336+
nights: 4,
337+
checkIn: "2025-01-15T15:00:00Z", // January
338+
},
339+
car: { dailyRate: 100.0, days: 4 },
340+
customer: { loyaltyTier: "GOLD" },
341+
},
342+
});
343+
// Base: 1000 + (200 * 4) + (100 * 4) = 2200
344+
// Winter multiplier: 2200 * 1.25 = 2750
345+
// Gold discount (15%): 2750 * 0.85 = 2337.5
346+
expect(result).toBe(2337.5);
347+
});
348+
349+
it("should calculate summer pricing with loyalty discount", async () => {
350+
const result = await program.execute({
351+
reservation: {
352+
flight: { price: 1000.0 },
353+
hotel: {
354+
nightlyRate: 200.0,
355+
nights: 4,
356+
checkIn: "2025-07-15T15:00:00Z", // July
357+
},
358+
car: { dailyRate: 100.0, days: 4 },
359+
customer: { loyaltyTier: "PLATINUM" },
360+
},
361+
});
362+
// Base: 1000 + (200 * 4) + (100 * 4) = 2200
363+
// No seasonal multiplier
364+
// Platinum discount (20%): 2200 * 0.80 = 1760
365+
expect(result).toBe(1760.0);
366+
});
367+
});
368+
369+
describe("Room Upgrade Eligibility", () => {
370+
// Tests complex upgrade eligibility rules considering:
371+
// - Customer loyalty tier
372+
// - Stay duration
373+
// - Hotel occupancy
374+
// - Existing offers
375+
// - Total spend
376+
// - Current booking class
377+
const expr = `
378+
reservation.customer.loyaltyTier in ['GOLD', 'PLATINUM'] &&
379+
reservation.hotel.nights >= 3 &&
380+
reservation.hotel.occupancyRate < 0.80 &&
381+
!(reservation.specialOffers.exists(o, o.type == 'ROOM_UPGRADE')) &&
382+
reservation.totalSpend > 5000.0 &&
383+
[reservation.flight.class, reservation.hotel.roomType].all(t, t != 'ECONOMY')
384+
`;
385+
let program: CelProgram;
386+
387+
beforeEach(async () => {
388+
program = await CelProgram.compile(expr);
389+
});
390+
391+
it("should qualify eligible platinum member for upgrade", async () => {
392+
const result = await program.execute({
393+
reservation: {
394+
customer: { loyaltyTier: "PLATINUM" },
395+
hotel: {
396+
nights: 4,
397+
occupancyRate: 0.7,
398+
roomType: "DELUXE",
399+
},
400+
flight: { class: "BUSINESS" },
401+
specialOffers: [],
402+
totalSpend: 6000.0,
403+
},
404+
});
405+
expect(result).toBe(true);
406+
});
407+
408+
it("should reject upgrade when conditions not met", async () => {
409+
const result = await program.execute({
410+
reservation: {
411+
customer: { loyaltyTier: "PLATINUM" },
412+
hotel: {
413+
nights: 4,
414+
occupancyRate: 0.85, // Too high occupancy
415+
roomType: "DELUXE",
416+
},
417+
flight: { class: "BUSINESS" },
418+
specialOffers: [],
419+
totalSpend: 6000.0,
420+
},
421+
});
422+
expect(result).toBe(false);
423+
});
424+
});
425+
});

biome.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"enabled": true,
1212
"indentStyle": "space",
1313
"indentWidth": 2,
14-
"lineWidth": 80
14+
"lineWidth": 80,
15+
"ignore": ["index.js"]
1516
},
1617
"linter": {
1718
"enabled": true,

package.json

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@kevinmichaelchen/cel-typescript",
3-
"version": "0.0.8",
3+
"version": "0.0.9",
44
"type": "module",
55
"description": "TypeScript bindings for the Common Expression Language (CEL) using cel-rust",
66
"repository": {
@@ -13,12 +13,7 @@
1313
"url": "https://github.com/kevinmichaelchen/cel-typescript/issues"
1414
},
1515
"homepage": "https://github.com/kevinmichaelchen/cel-typescript#readme",
16-
"files": [
17-
"dist/**/*",
18-
"*.node",
19-
"index.js",
20-
"index.d.ts"
21-
],
16+
"files": ["dist/**/*", "*.node", "index.js", "index.d.ts"],
2217
"keywords": [
2318
"cel",
2419
"common-expression-language",
@@ -44,13 +39,12 @@
4439
"napi": {
4540
"name": "cel-typescript",
4641
"triples": {
47-
"defaults": true,
42+
"defaults": false,
4843
"additional": [
4944
"aarch64-apple-darwin",
50-
"aarch64-unknown-linux-gnu",
51-
"aarch64-pc-windows-msvc",
52-
"x86_64-unknown-linux-gnu",
5345
"x86_64-apple-darwin",
46+
"x86_64-unknown-linux-gnu",
47+
"aarch64-unknown-linux-gnu",
5448
"x86_64-pc-windows-msvc"
5549
]
5650
}

0 commit comments

Comments
 (0)