@temporal-cortex/truth-engine 0.1.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/README.md ADDED
@@ -0,0 +1,172 @@
1
+ # @temporal-cortex/truth-engine
2
+
3
+ WASM-powered RRULE expansion, conflict detection, free/busy computation, and multi-calendar availability merging for Node.js.
4
+
5
+ This package wraps the Rust `truth-engine` library via WebAssembly, providing near-native performance for deterministic calendar operations in JavaScript/TypeScript environments.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @temporal-cortex/truth-engine
11
+ # or
12
+ pnpm add @temporal-cortex/truth-engine
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ### Expand Recurrence Rules
18
+
19
+ ```typescript
20
+ import { expandRRule } from "@temporal-cortex/truth-engine";
21
+
22
+ // "Every Tuesday at 2pm Pacific" for 4 weeks
23
+ const events = expandRRule(
24
+ "FREQ=WEEKLY;COUNT=4;BYDAY=TU",
25
+ "2026-02-17T14:00:00",
26
+ 60, // 60-minute duration
27
+ "America/Los_Angeles", // IANA timezone (DST-aware)
28
+ );
29
+ // events = [{ start: "2026-02-17T22:00:00+00:00", end: "2026-02-17T23:00:00+00:00" }, ...]
30
+ ```
31
+
32
+ ### Merge Multi-Calendar Availability
33
+
34
+ ```typescript
35
+ import { mergeAvailability } from "@temporal-cortex/truth-engine";
36
+
37
+ const availability = mergeAvailability(
38
+ [
39
+ { stream_id: "google", events: [{ start: "2026-03-16T09:00:00Z", end: "2026-03-16T10:00:00Z" }] },
40
+ { stream_id: "outlook", events: [{ start: "2026-03-16T14:00:00Z", end: "2026-03-16T15:00:00Z" }] },
41
+ ],
42
+ "2026-03-16T08:00:00Z",
43
+ "2026-03-16T17:00:00Z",
44
+ true, // opaque mode — hides which calendar each block came from
45
+ );
46
+ // availability.busy = [{ start, end, source_count: 0 }, ...]
47
+ // availability.free = [{ start, end, duration_minutes }, ...]
48
+ ```
49
+
50
+ ### Find Conflicts
51
+
52
+ ```typescript
53
+ import { findConflicts } from "@temporal-cortex/truth-engine";
54
+
55
+ const teamA = [{ start: "2026-02-17T14:00:00Z", end: "2026-02-17T15:00:00Z" }];
56
+ const teamB = [{ start: "2026-02-17T14:30:00Z", end: "2026-02-17T15:30:00Z" }];
57
+
58
+ const conflicts = findConflicts(teamA, teamB);
59
+ // [{ event_a: {...}, event_b: {...}, overlap_minutes: 30 }]
60
+ ```
61
+
62
+ ### Find Free Slots
63
+
64
+ ```typescript
65
+ import { findFreeSlots } from "@temporal-cortex/truth-engine";
66
+
67
+ const busyEvents = [
68
+ { start: "2026-02-17T09:00:00Z", end: "2026-02-17T10:00:00Z" },
69
+ { start: "2026-02-17T11:00:00Z", end: "2026-02-17T12:00:00Z" },
70
+ ];
71
+
72
+ const slots = findFreeSlots(busyEvents, "2026-02-17T08:00:00", "2026-02-17T13:00:00");
73
+ // [{ start, end, duration_minutes: 60 }, { ... }, { ... }]
74
+ ```
75
+
76
+ ### Find First Available Slot Across Calendars
77
+
78
+ ```typescript
79
+ import { findFirstFreeAcross } from "@temporal-cortex/truth-engine";
80
+
81
+ const slot = findFirstFreeAcross(
82
+ [
83
+ { stream_id: "google", events: [{ start: "2026-03-16T09:00:00Z", end: "2026-03-16T10:00:00Z" }] },
84
+ { stream_id: "outlook", events: [{ start: "2026-03-16T10:00:00Z", end: "2026-03-16T11:00:00Z" }] },
85
+ ],
86
+ "2026-03-16T08:00:00Z",
87
+ "2026-03-16T17:00:00Z",
88
+ 30, // minimum 30-minute slot
89
+ );
90
+ // { start, end, duration_minutes }
91
+ ```
92
+
93
+ ## API
94
+
95
+ ### `expandRRule(rrule, dtstart, durationMinutes, timezone, until?, maxCount?): TimeRange[]`
96
+
97
+ Expand an RFC 5545 RRULE into concrete event instances. Supports FREQ, BYDAY, BYSETPOS, BYMONTHDAY, COUNT, UNTIL, EXDATE. DST-aware — events at 14:00 Pacific stay at 14:00 Pacific across transitions.
98
+
99
+ ### `findConflicts(eventsA, eventsB): Conflict[]`
100
+
101
+ Find all pairwise overlaps between two event lists. Adjacent events (end === start) are not conflicts.
102
+
103
+ ### `findFreeSlots(events, windowStart, windowEnd): FreeSlot[]`
104
+
105
+ Find free time slots within a window, given a list of busy events.
106
+
107
+ ### `mergeAvailability(streams, windowStart, windowEnd, opaque?): UnifiedAvailability`
108
+
109
+ Merge N event streams into a unified busy/free view. In opaque mode (default), source counts are hidden for privacy.
110
+
111
+ ### `findFirstFreeAcross(streams, windowStart, windowEnd, minDurationMinutes): FreeSlot | null`
112
+
113
+ Find the first free slot of at least `minDurationMinutes` across all merged streams. Returns `null` if no qualifying slot exists.
114
+
115
+ ## Types
116
+
117
+ ```typescript
118
+ interface TimeRange { start: string; end: string }
119
+ interface Conflict { event_a: TimeRange; event_b: TimeRange; overlap_minutes: number }
120
+ interface FreeSlot { start: string; end: string; duration_minutes: number }
121
+ interface EventStream { stream_id: string; events: TimeRange[] }
122
+ interface BusyBlock { start: string; end: string; source_count: number }
123
+ interface UnifiedAvailability { busy: BusyBlock[]; free: FreeSlot[]; window_start: string; window_end: string; privacy: string }
124
+ ```
125
+
126
+ ## Build from Source
127
+
128
+ This package requires the WASM artifacts to be built from the Rust crate first:
129
+
130
+ ```bash
131
+ # From the monorepo root:
132
+
133
+ # 1. Build the WASM binary
134
+ cargo build -p truth-engine-wasm --target wasm32-unknown-unknown --release
135
+
136
+ # 2. Generate Node.js bindings
137
+ wasm-bindgen --target nodejs \
138
+ --out-dir packages/truth-engine-js/wasm/ \
139
+ target/wasm32-unknown-unknown/release/truth_engine_wasm.wasm
140
+
141
+ # 3. Rename for ESM/CJS compatibility
142
+ mv packages/truth-engine-js/wasm/truth_engine_wasm.js packages/truth-engine-js/wasm/truth_engine_wasm.cjs
143
+
144
+ # 4. Build TypeScript
145
+ pnpm --filter @temporal-cortex/truth-engine build
146
+
147
+ # 5. Run tests
148
+ pnpm --filter @temporal-cortex/truth-engine test
149
+ ```
150
+
151
+ ## Architecture
152
+
153
+ ```
154
+ src/index.ts ← Public API, loads WASM via createRequire
155
+ wasm/truth_engine_wasm.cjs ← wasm-bindgen generated CommonJS bindings
156
+ wasm/truth_engine_wasm.wasm ← Compiled WASM binary from truth-engine (Rust)
157
+ wasm/truth_engine_wasm.d.ts ← TypeScript type declarations for WASM exports
158
+ ```
159
+
160
+ The package uses `createRequire(import.meta.url)` to load the CommonJS WASM bindings from an ESM context. This bridges the module system mismatch since `wasm-bindgen --target nodejs` generates CommonJS but the package uses `"type": "module"`.
161
+
162
+ ## Testing
163
+
164
+ 13 tests covering RRULE expansion (8), conflict detection (3), and free/busy computation (2):
165
+
166
+ ```bash
167
+ pnpm --filter @temporal-cortex/truth-engine test
168
+ ```
169
+
170
+ ## License
171
+
172
+ MIT OR Apache-2.0
@@ -0,0 +1,83 @@
1
+ export interface TimeRange {
2
+ start: string;
3
+ end: string;
4
+ }
5
+ export interface Conflict {
6
+ event_a: TimeRange;
7
+ event_b: TimeRange;
8
+ overlap_minutes: number;
9
+ }
10
+ export interface FreeSlot {
11
+ start: string;
12
+ end: string;
13
+ duration_minutes: number;
14
+ }
15
+ /**
16
+ * Expand an RFC 5545 RRULE into concrete event instances.
17
+ *
18
+ * @param rrule - RFC 5545 recurrence rule (e.g., "FREQ=DAILY;COUNT=3")
19
+ * @param dtstart - Local datetime for the first occurrence (e.g., "2026-02-17T14:00:00")
20
+ * @param durationMinutes - Duration of each instance in minutes
21
+ * @param timezone - IANA timezone (e.g., "America/Los_Angeles")
22
+ * @param until - Optional end boundary (local datetime string)
23
+ * @param maxCount - Optional maximum number of instances to generate
24
+ * @returns Array of {start, end} objects with RFC 3339 datetime strings
25
+ */
26
+ export declare function expandRRule(rrule: string, dtstart: string, durationMinutes: number, timezone: string, until?: string, maxCount?: number): TimeRange[];
27
+ /**
28
+ * Find all pairwise conflicts (overlapping time ranges) between two event lists.
29
+ *
30
+ * @param eventsA - First list of events
31
+ * @param eventsB - Second list of events
32
+ * @returns Array of conflict objects with event_a, event_b, and overlap_minutes
33
+ */
34
+ export declare function findConflicts(eventsA: TimeRange[], eventsB: TimeRange[]): Conflict[];
35
+ /**
36
+ * Find free time slots within a given window, given a list of busy events.
37
+ *
38
+ * @param events - List of busy events
39
+ * @param windowStart - Start of the search window (ISO 8601 datetime)
40
+ * @param windowEnd - End of the search window (ISO 8601 datetime)
41
+ * @returns Array of free slot objects with start, end, and duration_minutes
42
+ */
43
+ export declare function findFreeSlots(events: TimeRange[], windowStart: string, windowEnd: string): FreeSlot[];
44
+ export interface EventStream {
45
+ stream_id: string;
46
+ events: TimeRange[];
47
+ }
48
+ export interface BusyBlock {
49
+ start: string;
50
+ end: string;
51
+ source_count: number;
52
+ }
53
+ export interface UnifiedAvailability {
54
+ busy: BusyBlock[];
55
+ free: FreeSlot[];
56
+ window_start: string;
57
+ window_end: string;
58
+ privacy: string;
59
+ }
60
+ /**
61
+ * Merge N event streams into unified availability within a time window.
62
+ *
63
+ * This is the core of the "Unified Availability Graph" — it computes a
64
+ * single source of truth for a user's availability across all their calendars.
65
+ *
66
+ * @param streams - Array of event streams (from different calendars/providers)
67
+ * @param windowStart - Start of the analysis window (ISO 8601 datetime)
68
+ * @param windowEnd - End of the analysis window (ISO 8601 datetime)
69
+ * @param opaque - If true, hide source counts in busy blocks (privacy mode). Default: true.
70
+ * @returns Unified availability with busy blocks and free slots
71
+ */
72
+ export declare function mergeAvailability(streams: EventStream[], windowStart: string, windowEnd: string, opaque?: boolean): UnifiedAvailability;
73
+ /**
74
+ * Find the first free slot of at least `minDurationMinutes` across N merged
75
+ * event streams.
76
+ *
77
+ * @param streams - Array of event streams
78
+ * @param windowStart - Start of the search window (ISO 8601 datetime)
79
+ * @param windowEnd - End of the search window (ISO 8601 datetime)
80
+ * @param minDurationMinutes - Minimum slot duration in minutes
81
+ * @returns The first qualifying free slot, or null if none found
82
+ */
83
+ export declare function findFirstFreeAcross(streams: EventStream[], windowStart: string, windowEnd: string, minDurationMinutes: number): FreeSlot | null;
package/dist/index.js ADDED
@@ -0,0 +1,88 @@
1
+ // @temporal-cortex/truth-engine — WASM-powered RRULE expansion, conflict
2
+ // detection, and free/busy computation for Node.js.
3
+ //
4
+ // This module loads the WASM binary compiled from the truth-engine-wasm Rust
5
+ // crate. The WASM bindings are generated by wasm-bindgen as CommonJS (.cjs),
6
+ // but this package uses ESM ("type": "module" in package.json). We bridge the
7
+ // two module systems using Node's createRequire().
8
+ //
9
+ // Build chain:
10
+ // truth-engine (Rust) -> truth-engine-wasm (wasm-bindgen) -> truth-engine-js (this wrapper)
11
+ import { createRequire } from "module";
12
+ const require = createRequire(import.meta.url);
13
+ // Load the WASM-generated Node.js bindings (.cjs for CommonJS/ESM compat)
14
+ const wasm = require("../wasm/truth_engine_wasm.cjs");
15
+ // ---------------------------------------------------------------------------
16
+ // Public API
17
+ // ---------------------------------------------------------------------------
18
+ /**
19
+ * Expand an RFC 5545 RRULE into concrete event instances.
20
+ *
21
+ * @param rrule - RFC 5545 recurrence rule (e.g., "FREQ=DAILY;COUNT=3")
22
+ * @param dtstart - Local datetime for the first occurrence (e.g., "2026-02-17T14:00:00")
23
+ * @param durationMinutes - Duration of each instance in minutes
24
+ * @param timezone - IANA timezone (e.g., "America/Los_Angeles")
25
+ * @param until - Optional end boundary (local datetime string)
26
+ * @param maxCount - Optional maximum number of instances to generate
27
+ * @returns Array of {start, end} objects with RFC 3339 datetime strings
28
+ */
29
+ export function expandRRule(rrule, dtstart, durationMinutes, timezone, until, maxCount) {
30
+ const json = wasm.expandRRule(rrule, dtstart, durationMinutes, timezone, until ?? undefined, maxCount ?? undefined);
31
+ return JSON.parse(json);
32
+ }
33
+ /**
34
+ * Find all pairwise conflicts (overlapping time ranges) between two event lists.
35
+ *
36
+ * @param eventsA - First list of events
37
+ * @param eventsB - Second list of events
38
+ * @returns Array of conflict objects with event_a, event_b, and overlap_minutes
39
+ */
40
+ export function findConflicts(eventsA, eventsB) {
41
+ const json = wasm.findConflicts(JSON.stringify(eventsA), JSON.stringify(eventsB));
42
+ return JSON.parse(json);
43
+ }
44
+ /**
45
+ * Find free time slots within a given window, given a list of busy events.
46
+ *
47
+ * @param events - List of busy events
48
+ * @param windowStart - Start of the search window (ISO 8601 datetime)
49
+ * @param windowEnd - End of the search window (ISO 8601 datetime)
50
+ * @returns Array of free slot objects with start, end, and duration_minutes
51
+ */
52
+ export function findFreeSlots(events, windowStart, windowEnd) {
53
+ const json = wasm.findFreeSlots(JSON.stringify(events), windowStart, windowEnd);
54
+ return JSON.parse(json);
55
+ }
56
+ // ---------------------------------------------------------------------------
57
+ // Multi-stream availability API
58
+ // ---------------------------------------------------------------------------
59
+ /**
60
+ * Merge N event streams into unified availability within a time window.
61
+ *
62
+ * This is the core of the "Unified Availability Graph" — it computes a
63
+ * single source of truth for a user's availability across all their calendars.
64
+ *
65
+ * @param streams - Array of event streams (from different calendars/providers)
66
+ * @param windowStart - Start of the analysis window (ISO 8601 datetime)
67
+ * @param windowEnd - End of the analysis window (ISO 8601 datetime)
68
+ * @param opaque - If true, hide source counts in busy blocks (privacy mode). Default: true.
69
+ * @returns Unified availability with busy blocks and free slots
70
+ */
71
+ export function mergeAvailability(streams, windowStart, windowEnd, opaque = true) {
72
+ const json = wasm.mergeAvailability(JSON.stringify(streams), windowStart, windowEnd, opaque);
73
+ return JSON.parse(json);
74
+ }
75
+ /**
76
+ * Find the first free slot of at least `minDurationMinutes` across N merged
77
+ * event streams.
78
+ *
79
+ * @param streams - Array of event streams
80
+ * @param windowStart - Start of the search window (ISO 8601 datetime)
81
+ * @param windowEnd - End of the search window (ISO 8601 datetime)
82
+ * @param minDurationMinutes - Minimum slot duration in minutes
83
+ * @returns The first qualifying free slot, or null if none found
84
+ */
85
+ export function findFirstFreeAcross(streams, windowStart, windowEnd, minDurationMinutes) {
86
+ const json = wasm.findFirstFreeAcross(JSON.stringify(streams), windowStart, windowEnd, minDurationMinutes);
87
+ return JSON.parse(json);
88
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@temporal-cortex/truth-engine",
3
+ "version": "0.1.0",
4
+ "description": "Truth Engine — deterministic RRULE expansion, conflict detection, free/busy for AI calendar agents",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "wasm"
11
+ ],
12
+ "keywords": [
13
+ "rrule",
14
+ "calendar",
15
+ "icalendar",
16
+ "recurrence",
17
+ "wasm"
18
+ ],
19
+ "license": "MIT OR Apache-2.0",
20
+ "author": "Billy Lui",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/billylui/temporal-cortex-core.git",
24
+ "directory": "packages/truth-engine-js"
25
+ },
26
+ "homepage": "https://github.com/billylui/temporal-cortex-core",
27
+ "bugs": {
28
+ "url": "https://github.com/billylui/temporal-cortex-core/issues"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^20",
32
+ "typescript": "^5",
33
+ "vitest": "^3"
34
+ },
35
+ "scripts": {
36
+ "build": "tsc",
37
+ "test": "vitest run",
38
+ "test:watch": "vitest"
39
+ }
40
+ }
@@ -0,0 +1,383 @@
1
+ /* @ts-self-types="./truth_engine_wasm.d.ts" */
2
+
3
+ /**
4
+ * Expand an RRULE string into concrete datetime instances.
5
+ *
6
+ * Returns a JSON string containing an array of `{start, end}` objects with
7
+ * RFC 3339 datetime strings.
8
+ *
9
+ * # Arguments
10
+ * - `rrule` -- RFC 5545 RRULE string (e.g., "FREQ=WEEKLY;BYDAY=TU,TH")
11
+ * - `dtstart` -- Local datetime string (e.g., "2026-02-17T14:00:00")
12
+ * - `duration_minutes` -- Duration of each instance in minutes
13
+ * - `timezone` -- IANA timezone (e.g., "America/Los_Angeles")
14
+ * - `until` -- Optional end boundary for expansion (local datetime string)
15
+ * - `max_count` -- Optional maximum number of instances
16
+ * @param {string} rrule
17
+ * @param {string} dtstart
18
+ * @param {number} duration_minutes
19
+ * @param {string} timezone
20
+ * @param {string | null} [until]
21
+ * @param {number | null} [max_count]
22
+ * @returns {string}
23
+ */
24
+ function expandRRule(rrule, dtstart, duration_minutes, timezone, until, max_count) {
25
+ let deferred6_0;
26
+ let deferred6_1;
27
+ try {
28
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
29
+ const ptr0 = passStringToWasm0(rrule, wasm.__wbindgen_export, wasm.__wbindgen_export2);
30
+ const len0 = WASM_VECTOR_LEN;
31
+ const ptr1 = passStringToWasm0(dtstart, wasm.__wbindgen_export, wasm.__wbindgen_export2);
32
+ const len1 = WASM_VECTOR_LEN;
33
+ const ptr2 = passStringToWasm0(timezone, wasm.__wbindgen_export, wasm.__wbindgen_export2);
34
+ const len2 = WASM_VECTOR_LEN;
35
+ var ptr3 = isLikeNone(until) ? 0 : passStringToWasm0(until, wasm.__wbindgen_export, wasm.__wbindgen_export2);
36
+ var len3 = WASM_VECTOR_LEN;
37
+ wasm.expandRRule(retptr, ptr0, len0, ptr1, len1, duration_minutes, ptr2, len2, ptr3, len3, isLikeNone(max_count) ? 0x100000001 : (max_count) >>> 0);
38
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
39
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
40
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
41
+ var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
42
+ var ptr5 = r0;
43
+ var len5 = r1;
44
+ if (r3) {
45
+ ptr5 = 0; len5 = 0;
46
+ throw takeObject(r2);
47
+ }
48
+ deferred6_0 = ptr5;
49
+ deferred6_1 = len5;
50
+ return getStringFromWasm0(ptr5, len5);
51
+ } finally {
52
+ wasm.__wbindgen_add_to_stack_pointer(16);
53
+ wasm.__wbindgen_export3(deferred6_0, deferred6_1, 1);
54
+ }
55
+ }
56
+ exports.expandRRule = expandRRule;
57
+
58
+ /**
59
+ * Find all pairwise conflicts (overlapping time ranges) between two event lists.
60
+ *
61
+ * Both arguments must be JSON arrays of `{start, end}` objects with ISO 8601
62
+ * datetime strings. Returns a JSON string containing an array of conflict objects,
63
+ * each with `event_a`, `event_b`, and `overlap_minutes`.
64
+ * @param {string} events_a_json
65
+ * @param {string} events_b_json
66
+ * @returns {string}
67
+ */
68
+ function findConflicts(events_a_json, events_b_json) {
69
+ let deferred4_0;
70
+ let deferred4_1;
71
+ try {
72
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
73
+ const ptr0 = passStringToWasm0(events_a_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
74
+ const len0 = WASM_VECTOR_LEN;
75
+ const ptr1 = passStringToWasm0(events_b_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
76
+ const len1 = WASM_VECTOR_LEN;
77
+ wasm.findConflicts(retptr, ptr0, len0, ptr1, len1);
78
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
79
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
80
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
81
+ var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
82
+ var ptr3 = r0;
83
+ var len3 = r1;
84
+ if (r3) {
85
+ ptr3 = 0; len3 = 0;
86
+ throw takeObject(r2);
87
+ }
88
+ deferred4_0 = ptr3;
89
+ deferred4_1 = len3;
90
+ return getStringFromWasm0(ptr3, len3);
91
+ } finally {
92
+ wasm.__wbindgen_add_to_stack_pointer(16);
93
+ wasm.__wbindgen_export3(deferred4_0, deferred4_1, 1);
94
+ }
95
+ }
96
+ exports.findConflicts = findConflicts;
97
+
98
+ /**
99
+ * Find the first free slot of at least `min_duration_minutes` across N merged
100
+ * event streams.
101
+ *
102
+ * `streams_json` must be a JSON array of `{stream_id, events: [{start, end}]}`.
103
+ * Returns a JSON string with `{start, end, duration_minutes}` or `null`.
104
+ * @param {string} streams_json
105
+ * @param {string} window_start
106
+ * @param {string} window_end
107
+ * @param {bigint} min_duration_minutes
108
+ * @returns {string}
109
+ */
110
+ function findFirstFreeAcross(streams_json, window_start, window_end, min_duration_minutes) {
111
+ let deferred5_0;
112
+ let deferred5_1;
113
+ try {
114
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
115
+ const ptr0 = passStringToWasm0(streams_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
116
+ const len0 = WASM_VECTOR_LEN;
117
+ const ptr1 = passStringToWasm0(window_start, wasm.__wbindgen_export, wasm.__wbindgen_export2);
118
+ const len1 = WASM_VECTOR_LEN;
119
+ const ptr2 = passStringToWasm0(window_end, wasm.__wbindgen_export, wasm.__wbindgen_export2);
120
+ const len2 = WASM_VECTOR_LEN;
121
+ wasm.findFirstFreeAcross(retptr, ptr0, len0, ptr1, len1, ptr2, len2, min_duration_minutes);
122
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
123
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
124
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
125
+ var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
126
+ var ptr4 = r0;
127
+ var len4 = r1;
128
+ if (r3) {
129
+ ptr4 = 0; len4 = 0;
130
+ throw takeObject(r2);
131
+ }
132
+ deferred5_0 = ptr4;
133
+ deferred5_1 = len4;
134
+ return getStringFromWasm0(ptr4, len4);
135
+ } finally {
136
+ wasm.__wbindgen_add_to_stack_pointer(16);
137
+ wasm.__wbindgen_export3(deferred5_0, deferred5_1, 1);
138
+ }
139
+ }
140
+ exports.findFirstFreeAcross = findFirstFreeAcross;
141
+
142
+ /**
143
+ * Find free time slots within a given time window, given a list of busy events.
144
+ *
145
+ * `events_json` must be a JSON array of `{start, end}` objects. `window_start`
146
+ * and `window_end` are ISO 8601 datetime strings. Returns a JSON string containing
147
+ * an array of `{start, end, duration_minutes}` objects.
148
+ * @param {string} events_json
149
+ * @param {string} window_start
150
+ * @param {string} window_end
151
+ * @returns {string}
152
+ */
153
+ function findFreeSlots(events_json, window_start, window_end) {
154
+ let deferred5_0;
155
+ let deferred5_1;
156
+ try {
157
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
158
+ const ptr0 = passStringToWasm0(events_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
159
+ const len0 = WASM_VECTOR_LEN;
160
+ const ptr1 = passStringToWasm0(window_start, wasm.__wbindgen_export, wasm.__wbindgen_export2);
161
+ const len1 = WASM_VECTOR_LEN;
162
+ const ptr2 = passStringToWasm0(window_end, wasm.__wbindgen_export, wasm.__wbindgen_export2);
163
+ const len2 = WASM_VECTOR_LEN;
164
+ wasm.findFreeSlots(retptr, ptr0, len0, ptr1, len1, ptr2, len2);
165
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
166
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
167
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
168
+ var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
169
+ var ptr4 = r0;
170
+ var len4 = r1;
171
+ if (r3) {
172
+ ptr4 = 0; len4 = 0;
173
+ throw takeObject(r2);
174
+ }
175
+ deferred5_0 = ptr4;
176
+ deferred5_1 = len4;
177
+ return getStringFromWasm0(ptr4, len4);
178
+ } finally {
179
+ wasm.__wbindgen_add_to_stack_pointer(16);
180
+ wasm.__wbindgen_export3(deferred5_0, deferred5_1, 1);
181
+ }
182
+ }
183
+ exports.findFreeSlots = findFreeSlots;
184
+
185
+ /**
186
+ * Merge N event streams into unified availability within a time window.
187
+ *
188
+ * `streams_json` must be a JSON array of `{stream_id, events: [{start, end}]}`.
189
+ * `window_start` and `window_end` are ISO 8601 datetime strings.
190
+ * `opaque` controls privacy: true = hide source counts, false = show them.
191
+ *
192
+ * Returns a JSON string with `{busy, free, window_start, window_end, privacy}`.
193
+ * @param {string} streams_json
194
+ * @param {string} window_start
195
+ * @param {string} window_end
196
+ * @param {boolean} opaque
197
+ * @returns {string}
198
+ */
199
+ function mergeAvailability(streams_json, window_start, window_end, opaque) {
200
+ let deferred5_0;
201
+ let deferred5_1;
202
+ try {
203
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
204
+ const ptr0 = passStringToWasm0(streams_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
205
+ const len0 = WASM_VECTOR_LEN;
206
+ const ptr1 = passStringToWasm0(window_start, wasm.__wbindgen_export, wasm.__wbindgen_export2);
207
+ const len1 = WASM_VECTOR_LEN;
208
+ const ptr2 = passStringToWasm0(window_end, wasm.__wbindgen_export, wasm.__wbindgen_export2);
209
+ const len2 = WASM_VECTOR_LEN;
210
+ wasm.mergeAvailability(retptr, ptr0, len0, ptr1, len1, ptr2, len2, opaque);
211
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
212
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
213
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
214
+ var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
215
+ var ptr4 = r0;
216
+ var len4 = r1;
217
+ if (r3) {
218
+ ptr4 = 0; len4 = 0;
219
+ throw takeObject(r2);
220
+ }
221
+ deferred5_0 = ptr4;
222
+ deferred5_1 = len4;
223
+ return getStringFromWasm0(ptr4, len4);
224
+ } finally {
225
+ wasm.__wbindgen_add_to_stack_pointer(16);
226
+ wasm.__wbindgen_export3(deferred5_0, deferred5_1, 1);
227
+ }
228
+ }
229
+ exports.mergeAvailability = mergeAvailability;
230
+
231
+ function __wbg_get_imports() {
232
+ const import0 = {
233
+ __proto__: null,
234
+ __wbg___wbindgen_throw_be289d5034ed271b: function(arg0, arg1) {
235
+ throw new Error(getStringFromWasm0(arg0, arg1));
236
+ },
237
+ __wbg_getTimezoneOffset_81776d10a4ec18a8: function(arg0) {
238
+ const ret = getObject(arg0).getTimezoneOffset();
239
+ return ret;
240
+ },
241
+ __wbg_new_245cd5c49157e602: function(arg0) {
242
+ const ret = new Date(getObject(arg0));
243
+ return addHeapObject(ret);
244
+ },
245
+ __wbg_new_with_year_month_day_hr_min_sec_f82362c71c4dfc23: function(arg0, arg1, arg2, arg3, arg4, arg5) {
246
+ const ret = new Date(arg0 >>> 0, arg1, arg2, arg3, arg4, arg5);
247
+ return addHeapObject(ret);
248
+ },
249
+ __wbindgen_cast_0000000000000001: function(arg0) {
250
+ // Cast intrinsic for `F64 -> Externref`.
251
+ const ret = arg0;
252
+ return addHeapObject(ret);
253
+ },
254
+ __wbindgen_cast_0000000000000002: function(arg0, arg1) {
255
+ // Cast intrinsic for `Ref(String) -> Externref`.
256
+ const ret = getStringFromWasm0(arg0, arg1);
257
+ return addHeapObject(ret);
258
+ },
259
+ __wbindgen_object_drop_ref: function(arg0) {
260
+ takeObject(arg0);
261
+ },
262
+ };
263
+ return {
264
+ __proto__: null,
265
+ "./truth_engine_wasm_bg.js": import0,
266
+ };
267
+ }
268
+
269
+ function addHeapObject(obj) {
270
+ if (heap_next === heap.length) heap.push(heap.length + 1);
271
+ const idx = heap_next;
272
+ heap_next = heap[idx];
273
+
274
+ heap[idx] = obj;
275
+ return idx;
276
+ }
277
+
278
+ function dropObject(idx) {
279
+ if (idx < 132) return;
280
+ heap[idx] = heap_next;
281
+ heap_next = idx;
282
+ }
283
+
284
+ let cachedDataViewMemory0 = null;
285
+ function getDataViewMemory0() {
286
+ if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
287
+ cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
288
+ }
289
+ return cachedDataViewMemory0;
290
+ }
291
+
292
+ function getStringFromWasm0(ptr, len) {
293
+ ptr = ptr >>> 0;
294
+ return decodeText(ptr, len);
295
+ }
296
+
297
+ let cachedUint8ArrayMemory0 = null;
298
+ function getUint8ArrayMemory0() {
299
+ if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
300
+ cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
301
+ }
302
+ return cachedUint8ArrayMemory0;
303
+ }
304
+
305
+ function getObject(idx) { return heap[idx]; }
306
+
307
+ let heap = new Array(128).fill(undefined);
308
+ heap.push(undefined, null, true, false);
309
+
310
+ let heap_next = heap.length;
311
+
312
+ function isLikeNone(x) {
313
+ return x === undefined || x === null;
314
+ }
315
+
316
+ function passStringToWasm0(arg, malloc, realloc) {
317
+ if (realloc === undefined) {
318
+ const buf = cachedTextEncoder.encode(arg);
319
+ const ptr = malloc(buf.length, 1) >>> 0;
320
+ getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
321
+ WASM_VECTOR_LEN = buf.length;
322
+ return ptr;
323
+ }
324
+
325
+ let len = arg.length;
326
+ let ptr = malloc(len, 1) >>> 0;
327
+
328
+ const mem = getUint8ArrayMemory0();
329
+
330
+ let offset = 0;
331
+
332
+ for (; offset < len; offset++) {
333
+ const code = arg.charCodeAt(offset);
334
+ if (code > 0x7F) break;
335
+ mem[ptr + offset] = code;
336
+ }
337
+ if (offset !== len) {
338
+ if (offset !== 0) {
339
+ arg = arg.slice(offset);
340
+ }
341
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
342
+ const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
343
+ const ret = cachedTextEncoder.encodeInto(arg, view);
344
+
345
+ offset += ret.written;
346
+ ptr = realloc(ptr, len, offset, 1) >>> 0;
347
+ }
348
+
349
+ WASM_VECTOR_LEN = offset;
350
+ return ptr;
351
+ }
352
+
353
+ function takeObject(idx) {
354
+ const ret = getObject(idx);
355
+ dropObject(idx);
356
+ return ret;
357
+ }
358
+
359
+ let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
360
+ cachedTextDecoder.decode();
361
+ function decodeText(ptr, len) {
362
+ return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
363
+ }
364
+
365
+ const cachedTextEncoder = new TextEncoder();
366
+
367
+ if (!('encodeInto' in cachedTextEncoder)) {
368
+ cachedTextEncoder.encodeInto = function (arg, view) {
369
+ const buf = cachedTextEncoder.encode(arg);
370
+ view.set(buf);
371
+ return {
372
+ read: arg.length,
373
+ written: buf.length
374
+ };
375
+ };
376
+ }
377
+
378
+ let WASM_VECTOR_LEN = 0;
379
+
380
+ const wasmPath = `${__dirname}/truth_engine_wasm_bg.wasm`;
381
+ const wasmBytes = require('fs').readFileSync(wasmPath);
382
+ const wasmModule = new WebAssembly.Module(wasmBytes);
383
+ const wasm = new WebAssembly.Instance(wasmModule, __wbg_get_imports()).exports;
@@ -0,0 +1,56 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+
4
+ /**
5
+ * Expand an RRULE string into concrete datetime instances.
6
+ *
7
+ * Returns a JSON string containing an array of `{start, end}` objects with
8
+ * RFC 3339 datetime strings.
9
+ *
10
+ * # Arguments
11
+ * - `rrule` -- RFC 5545 RRULE string (e.g., "FREQ=WEEKLY;BYDAY=TU,TH")
12
+ * - `dtstart` -- Local datetime string (e.g., "2026-02-17T14:00:00")
13
+ * - `duration_minutes` -- Duration of each instance in minutes
14
+ * - `timezone` -- IANA timezone (e.g., "America/Los_Angeles")
15
+ * - `until` -- Optional end boundary for expansion (local datetime string)
16
+ * - `max_count` -- Optional maximum number of instances
17
+ */
18
+ export function expandRRule(rrule: string, dtstart: string, duration_minutes: number, timezone: string, until?: string | null, max_count?: number | null): string;
19
+
20
+ /**
21
+ * Find all pairwise conflicts (overlapping time ranges) between two event lists.
22
+ *
23
+ * Both arguments must be JSON arrays of `{start, end}` objects with ISO 8601
24
+ * datetime strings. Returns a JSON string containing an array of conflict objects,
25
+ * each with `event_a`, `event_b`, and `overlap_minutes`.
26
+ */
27
+ export function findConflicts(events_a_json: string, events_b_json: string): string;
28
+
29
+ /**
30
+ * Find the first free slot of at least `min_duration_minutes` across N merged
31
+ * event streams.
32
+ *
33
+ * `streams_json` must be a JSON array of `{stream_id, events: [{start, end}]}`.
34
+ * Returns a JSON string with `{start, end, duration_minutes}` or `null`.
35
+ */
36
+ export function findFirstFreeAcross(streams_json: string, window_start: string, window_end: string, min_duration_minutes: bigint): string;
37
+
38
+ /**
39
+ * Find free time slots within a given time window, given a list of busy events.
40
+ *
41
+ * `events_json` must be a JSON array of `{start, end}` objects. `window_start`
42
+ * and `window_end` are ISO 8601 datetime strings. Returns a JSON string containing
43
+ * an array of `{start, end, duration_minutes}` objects.
44
+ */
45
+ export function findFreeSlots(events_json: string, window_start: string, window_end: string): string;
46
+
47
+ /**
48
+ * Merge N event streams into unified availability within a time window.
49
+ *
50
+ * `streams_json` must be a JSON array of `{stream_id, events: [{start, end}]}`.
51
+ * `window_start` and `window_end` are ISO 8601 datetime strings.
52
+ * `opaque` controls privacy: true = hide source counts, false = show them.
53
+ *
54
+ * Returns a JSON string with `{busy, free, window_start, window_end, privacy}`.
55
+ */
56
+ export function mergeAvailability(streams_json: string, window_start: string, window_end: string, opaque: boolean): string;
Binary file
@@ -0,0 +1,12 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ export const memory: WebAssembly.Memory;
4
+ export const expandRRule: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number) => void;
5
+ export const findConflicts: (a: number, b: number, c: number, d: number, e: number) => void;
6
+ export const findFirstFreeAcross: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: bigint) => void;
7
+ export const findFreeSlots: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
8
+ export const mergeAvailability: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => void;
9
+ export const __wbindgen_add_to_stack_pointer: (a: number) => number;
10
+ export const __wbindgen_export: (a: number, b: number) => number;
11
+ export const __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
12
+ export const __wbindgen_export3: (a: number, b: number, c: number) => void;