@skygraph/core 0.0.0-placeholder.0 → 0.4.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 +21 -0
- package/dist/calendar.cjs +404 -0
- package/dist/calendar.cjs.map +1 -0
- package/dist/calendar.d.cts +209 -0
- package/dist/calendar.d.ts +209 -0
- package/dist/calendar.js +12 -0
- package/dist/calendar.js.map +1 -0
- package/dist/chunk-5CCD5Q4B.js +36 -0
- package/dist/chunk-5CCD5Q4B.js.map +1 -0
- package/dist/chunk-AB3RLBLW.js +357 -0
- package/dist/chunk-AB3RLBLW.js.map +1 -0
- package/dist/chunk-GEMALROQ.js +258 -0
- package/dist/chunk-GEMALROQ.js.map +1 -0
- package/dist/chunk-IY6LJU3L.js +256 -0
- package/dist/chunk-IY6LJU3L.js.map +1 -0
- package/dist/chunk-KMGRNPLG.js +453 -0
- package/dist/chunk-KMGRNPLG.js.map +1 -0
- package/dist/chunk-KP75DEA4.js +1012 -0
- package/dist/chunk-KP75DEA4.js.map +1 -0
- package/dist/chunk-Y7FTBBYX.js +397 -0
- package/dist/chunk-Y7FTBBYX.js.map +1 -0
- package/dist/chunk-ZWB7JGAJ.js +488 -0
- package/dist/chunk-ZWB7JGAJ.js.map +1 -0
- package/dist/form.cjs +498 -0
- package/dist/form.cjs.map +1 -0
- package/dist/form.d.cts +130 -0
- package/dist/form.d.ts +130 -0
- package/dist/form.js +8 -0
- package/dist/form.js.map +1 -0
- package/dist/graph.cjs +1065 -0
- package/dist/graph.cjs.map +1 -0
- package/dist/graph.d.cts +542 -0
- package/dist/graph.d.ts +542 -0
- package/dist/graph.js +24 -0
- package/dist/graph.js.map +1 -0
- package/dist/index.cjs +3656 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +144 -0
- package/dist/index.d.ts +144 -0
- package/dist/index.js +467 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime-internal.cjs +339 -0
- package/dist/runtime-internal.cjs.map +1 -0
- package/dist/runtime-internal.d.cts +130 -0
- package/dist/runtime-internal.d.ts +130 -0
- package/dist/runtime-internal.js +67 -0
- package/dist/runtime-internal.js.map +1 -0
- package/dist/table.cjs +537 -0
- package/dist/table.cjs.map +1 -0
- package/dist/table.d.cts +215 -0
- package/dist/table.d.ts +215 -0
- package/dist/table.js +16 -0
- package/dist/table.js.map +1 -0
- package/dist/tree.cjs +442 -0
- package/dist/tree.cjs.map +1 -0
- package/dist/tree.d.cts +76 -0
- package/dist/tree.d.ts +76 -0
- package/dist/tree.js +8 -0
- package/dist/tree.js.map +1 -0
- package/dist/types-B-rKQJ4R.d.cts +35 -0
- package/dist/types-B-rKQJ4R.d.ts +35 -0
- package/dist/virtual.cjs +283 -0
- package/dist/virtual.cjs.map +1 -0
- package/dist/virtual.d.cts +96 -0
- package/dist/virtual.d.ts +96 -0
- package/dist/virtual.js +9 -0
- package/dist/virtual.js.map +1 -0
- package/package.json +98 -18
- package/README.md +0 -12
- package/index.js +0 -3
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { C as Core } from './types-B-rKQJ4R.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resource Calendar — engine domain types.
|
|
5
|
+
*
|
|
6
|
+
* The engine works with `number` (epoch ms) timestamps internally so the
|
|
7
|
+
* full state can round-trip through `core.snapshot()` / `core.restore()`
|
|
8
|
+
* without any custom (de)serialization. The React adapter accepts both
|
|
9
|
+
* `Date` and `number` on the public surface and normalizes on input.
|
|
10
|
+
*/
|
|
11
|
+
type CalendarScale = 'day' | 'week' | 'month';
|
|
12
|
+
type AssignmentStatus = 'tentative' | 'confirmed' | 'conflict';
|
|
13
|
+
type ConflictReason = 'overlap' | 'over-capacity' | 'outside-availability';
|
|
14
|
+
type DayOfWeek = 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
15
|
+
interface Resource {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
/** Optional accent colour rendered alongside the resource label. */
|
|
19
|
+
color?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Maximum number of simultaneous assignments tolerated on this resource
|
|
22
|
+
* within the same scale-step. `undefined` ≡ no concurrency limit
|
|
23
|
+
* (overlaps still count as `overlap` conflicts but never as
|
|
24
|
+
* `over-capacity`). When set, every slot whose concurrent count exceeds
|
|
25
|
+
* `capacityPerSlot` produces an `over-capacity` conflict containing
|
|
26
|
+
* exactly the assignments that touch that slot.
|
|
27
|
+
*/
|
|
28
|
+
capacityPerSlot?: number;
|
|
29
|
+
/** Free-form metadata. The engine ignores this and round-trips it as-is. */
|
|
30
|
+
meta?: unknown;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A single booking on the timeline.
|
|
34
|
+
*
|
|
35
|
+
* `start` / `end` are stored as epoch milliseconds. `start` is inclusive,
|
|
36
|
+
* `end` is exclusive — same convention as `GanttTask` and the JS `Date`
|
|
37
|
+
* arithmetic the engine uses internally.
|
|
38
|
+
*/
|
|
39
|
+
interface Assignment {
|
|
40
|
+
id: string;
|
|
41
|
+
resourceId: string;
|
|
42
|
+
start: number;
|
|
43
|
+
end: number;
|
|
44
|
+
title: string;
|
|
45
|
+
/**
|
|
46
|
+
* `'conflict'` is set automatically by the engine after every mutation
|
|
47
|
+
* for assignments that participate in at least one detected conflict;
|
|
48
|
+
* consumers may pass `'tentative'` / `'confirmed'` to label the
|
|
49
|
+
* underlying intent, those values are preserved and only escalated to
|
|
50
|
+
* `'conflict'` when a conflict actually exists.
|
|
51
|
+
*/
|
|
52
|
+
status?: AssignmentStatus;
|
|
53
|
+
meta?: unknown;
|
|
54
|
+
}
|
|
55
|
+
interface Conflict {
|
|
56
|
+
id: string;
|
|
57
|
+
/** Assignments involved, sorted ascending by id for stable equality. */
|
|
58
|
+
assignmentIds: string[];
|
|
59
|
+
reason: ConflictReason;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Availability rule.
|
|
63
|
+
*
|
|
64
|
+
* v0 keeps the rule shape minimal — `dayOfWeek` plus an `HH:MM` window.
|
|
65
|
+
* One rule = one window. Multiple windows on the same day = multiple
|
|
66
|
+
* rules. A resource with NO rules is considered always available.
|
|
67
|
+
*
|
|
68
|
+
* TODO(round-10): exception dates (`exception: { date, kind }`) for
|
|
69
|
+
* one-off blackouts / additions are explicitly out of scope for v0 — the
|
|
70
|
+
* spec calls them out but the use-cases (PTO calendars, holiday packs)
|
|
71
|
+
* are not finalised yet. Add them once a production consumer needs
|
|
72
|
+
* either kind so we don't bake the wrong shape in.
|
|
73
|
+
*/
|
|
74
|
+
interface AvailabilityRule {
|
|
75
|
+
resourceId: string;
|
|
76
|
+
/** 0 = Sunday … 6 = Saturday. Omitted ≡ rule applies every day. */
|
|
77
|
+
dayOfWeek?: DayOfWeek;
|
|
78
|
+
/** `HH:MM` (24h, UTC). Omitted ≡ open from 00:00. */
|
|
79
|
+
from?: string;
|
|
80
|
+
/** `HH:MM` (24h, UTC). Omitted ≡ open until 24:00. */
|
|
81
|
+
to?: string;
|
|
82
|
+
}
|
|
83
|
+
interface CalendarState {
|
|
84
|
+
resources: Resource[];
|
|
85
|
+
assignments: Assignment[];
|
|
86
|
+
conflicts: Conflict[];
|
|
87
|
+
}
|
|
88
|
+
interface DetectConflictsOptions {
|
|
89
|
+
scale?: CalendarScale;
|
|
90
|
+
rules?: readonly AvailabilityRule[];
|
|
91
|
+
}
|
|
92
|
+
interface CalendarEngineOptions {
|
|
93
|
+
/** Optional debug name appended to the generated engine id. */
|
|
94
|
+
name?: string;
|
|
95
|
+
/** Default scale used for capacity slot computation. @default 'day' */
|
|
96
|
+
scale?: CalendarScale;
|
|
97
|
+
/** Initial availability rules. Can be replaced via `setRules` later. */
|
|
98
|
+
rules?: readonly AvailabilityRule[];
|
|
99
|
+
}
|
|
100
|
+
interface CalendarEngine {
|
|
101
|
+
/** Stable engine id. Used as the suffix on the `$calendar.<id>.*` paths. */
|
|
102
|
+
readonly id: string;
|
|
103
|
+
addResource(resource: Resource): void;
|
|
104
|
+
removeResource(id: string): void;
|
|
105
|
+
updateResource(id: string, patch: Partial<Resource>): void;
|
|
106
|
+
getResource(id: string): Resource | undefined;
|
|
107
|
+
getResources(): Resource[];
|
|
108
|
+
addAssignment(assignment: Assignment): void;
|
|
109
|
+
updateAssignment(id: string, patch: Partial<Assignment>): void;
|
|
110
|
+
removeAssignment(id: string): void;
|
|
111
|
+
/** Shifts both `start` and `end` by `deltaMs`. */
|
|
112
|
+
moveAssignment(id: string, deltaMs: number): void;
|
|
113
|
+
/**
|
|
114
|
+
* Resizes one side. `'end'` shifts only the end timestamp;
|
|
115
|
+
* `'start'` shifts only the start. The opposite side stays put.
|
|
116
|
+
* The engine clamps the result so the assignment never inverts —
|
|
117
|
+
* minimum width is `1` ms (the React adapter additionally enforces
|
|
118
|
+
* a per-scale step in its drag handler).
|
|
119
|
+
*/
|
|
120
|
+
resizeAssignment(id: string, side: 'start' | 'end', deltaMs: number): void;
|
|
121
|
+
getAssignment(id: string): Assignment | undefined;
|
|
122
|
+
getAssignments(): Assignment[];
|
|
123
|
+
setRules(rules: readonly AvailabilityRule[]): void;
|
|
124
|
+
getRules(): AvailabilityRule[];
|
|
125
|
+
/** Latest computed conflicts; refreshed automatically on mutations. */
|
|
126
|
+
getConflicts(): Conflict[];
|
|
127
|
+
setScale(scale: CalendarScale): void;
|
|
128
|
+
getScale(): CalendarScale;
|
|
129
|
+
getState(): CalendarState;
|
|
130
|
+
/** Subscribe to ANY engine mutation. Returns an unsubscribe fn. */
|
|
131
|
+
subscribe(cb: () => void): () => void;
|
|
132
|
+
/** Reset the engine — drops resources, assignments, rules, conflicts. */
|
|
133
|
+
clear(): void;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Resource Calendar engine — the sixth member of the
|
|
138
|
+
* form / table / tree / virtual / graph / **calendar** family.
|
|
139
|
+
*
|
|
140
|
+
* The in-memory maps are the source of truth and the public read API;
|
|
141
|
+
* snapshots are mirrored to the Core store under
|
|
142
|
+
* `$calendar.<id>.{resources,assignments,conflicts,view}` so other
|
|
143
|
+
* engines / external observers can react via `core.subscribe(...)`.
|
|
144
|
+
*
|
|
145
|
+
* v0 scope:
|
|
146
|
+
* - addResource / removeResource / updateResource
|
|
147
|
+
* - addAssignment / updateAssignment / removeAssignment
|
|
148
|
+
* - moveAssignment(id, deltaMs) — shifts both ends together
|
|
149
|
+
* - resizeAssignment(id, side, deltaMs) — clamps so width >= 1ms
|
|
150
|
+
* - setRules / setScale — re-compute conflicts on change
|
|
151
|
+
* - detectConflicts under the hood; status of involved assignments
|
|
152
|
+
* is auto-promoted to `'conflict'` so the React adapter can render
|
|
153
|
+
* without re-running the algorithm.
|
|
154
|
+
*
|
|
155
|
+
* NOT in v0 (deferred to round-10):
|
|
156
|
+
* - exception dates on `AvailabilityRule`
|
|
157
|
+
* - full per-engine undo/redo (use `core.snapshot/restore` instead)
|
|
158
|
+
* - aggregate read APIs over a sub-range
|
|
159
|
+
*/
|
|
160
|
+
declare function createCalendar(core: Core, options?: CalendarEngineOptions): CalendarEngine;
|
|
161
|
+
|
|
162
|
+
interface DetectConflictsArgs {
|
|
163
|
+
scale?: CalendarScale;
|
|
164
|
+
rules?: readonly AvailabilityRule[];
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Find all conflicts in `state`.
|
|
168
|
+
*
|
|
169
|
+
* Algorithm — three independent passes, results de-duplicated by
|
|
170
|
+
* `(reason, sorted-ids)` so the same overlapping pair is never reported
|
|
171
|
+
* twice as `overlap`:
|
|
172
|
+
*
|
|
173
|
+
* 1. **overlap** (per resource): sort assignments by `start` and sweep.
|
|
174
|
+
* Keep an active set ordered by `end` ASC; whenever a new
|
|
175
|
+
* assignment opens, every still-active assignment forms a pairwise
|
|
176
|
+
* `overlap` conflict with it. O(n log n + k) where k = total
|
|
177
|
+
* overlapping pairs.
|
|
178
|
+
*
|
|
179
|
+
* 2. **over-capacity** (per resource with `capacityPerSlot`): bucket
|
|
180
|
+
* every assignment into the slots it touches (1 slot = 1 scale
|
|
181
|
+
* step). For each slot whose concurrent count exceeds capacity,
|
|
182
|
+
* emit one `over-capacity` conflict naming exactly the assignments
|
|
183
|
+
* that touch that slot. This is the simple counter approach (no
|
|
184
|
+
* interval tree) — fine up to ~10⁵ assignments per resource.
|
|
185
|
+
*
|
|
186
|
+
* 3. **outside-availability**: per-assignment check via
|
|
187
|
+
* `isAvailable`. Each violation becomes its own one-element
|
|
188
|
+
* conflict.
|
|
189
|
+
*/
|
|
190
|
+
declare function detectConflicts(state: Pick<CalendarState, 'resources' | 'assignments'>, opts?: DetectConflictsArgs): Conflict[];
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* `true` ⇔ the entire `[start, end)` interval falls inside at least one
|
|
194
|
+
* availability window for `resource`. A resource without any matching
|
|
195
|
+
* rule (or without any rules at all) is considered always available —
|
|
196
|
+
* unconfigured calendars stay friendly.
|
|
197
|
+
*
|
|
198
|
+
* Implementation: walk one UTC day at a time and check that the slice of
|
|
199
|
+
* `[start, end)` intersected with that day is fully covered by the union
|
|
200
|
+
* of windows attached to that day. The union is built by sorting rule
|
|
201
|
+
* windows by `from` and merging overlaps in O(k log k) where k = rules
|
|
202
|
+
* for that day (typically 1–3).
|
|
203
|
+
*/
|
|
204
|
+
declare function isAvailable(resource: Resource, range: {
|
|
205
|
+
start: number;
|
|
206
|
+
end: number;
|
|
207
|
+
}, rules: readonly AvailabilityRule[]): boolean;
|
|
208
|
+
|
|
209
|
+
export { type Assignment, type AssignmentStatus, type AvailabilityRule, type CalendarEngine, type CalendarEngineOptions, type CalendarScale, type CalendarState, type Conflict, type ConflictReason, type DayOfWeek, type DetectConflictsOptions, type Resource, createCalendar, detectConflicts, isAvailable };
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { C as Core } from './types-B-rKQJ4R.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resource Calendar — engine domain types.
|
|
5
|
+
*
|
|
6
|
+
* The engine works with `number` (epoch ms) timestamps internally so the
|
|
7
|
+
* full state can round-trip through `core.snapshot()` / `core.restore()`
|
|
8
|
+
* without any custom (de)serialization. The React adapter accepts both
|
|
9
|
+
* `Date` and `number` on the public surface and normalizes on input.
|
|
10
|
+
*/
|
|
11
|
+
type CalendarScale = 'day' | 'week' | 'month';
|
|
12
|
+
type AssignmentStatus = 'tentative' | 'confirmed' | 'conflict';
|
|
13
|
+
type ConflictReason = 'overlap' | 'over-capacity' | 'outside-availability';
|
|
14
|
+
type DayOfWeek = 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
15
|
+
interface Resource {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
/** Optional accent colour rendered alongside the resource label. */
|
|
19
|
+
color?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Maximum number of simultaneous assignments tolerated on this resource
|
|
22
|
+
* within the same scale-step. `undefined` ≡ no concurrency limit
|
|
23
|
+
* (overlaps still count as `overlap` conflicts but never as
|
|
24
|
+
* `over-capacity`). When set, every slot whose concurrent count exceeds
|
|
25
|
+
* `capacityPerSlot` produces an `over-capacity` conflict containing
|
|
26
|
+
* exactly the assignments that touch that slot.
|
|
27
|
+
*/
|
|
28
|
+
capacityPerSlot?: number;
|
|
29
|
+
/** Free-form metadata. The engine ignores this and round-trips it as-is. */
|
|
30
|
+
meta?: unknown;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A single booking on the timeline.
|
|
34
|
+
*
|
|
35
|
+
* `start` / `end` are stored as epoch milliseconds. `start` is inclusive,
|
|
36
|
+
* `end` is exclusive — same convention as `GanttTask` and the JS `Date`
|
|
37
|
+
* arithmetic the engine uses internally.
|
|
38
|
+
*/
|
|
39
|
+
interface Assignment {
|
|
40
|
+
id: string;
|
|
41
|
+
resourceId: string;
|
|
42
|
+
start: number;
|
|
43
|
+
end: number;
|
|
44
|
+
title: string;
|
|
45
|
+
/**
|
|
46
|
+
* `'conflict'` is set automatically by the engine after every mutation
|
|
47
|
+
* for assignments that participate in at least one detected conflict;
|
|
48
|
+
* consumers may pass `'tentative'` / `'confirmed'` to label the
|
|
49
|
+
* underlying intent, those values are preserved and only escalated to
|
|
50
|
+
* `'conflict'` when a conflict actually exists.
|
|
51
|
+
*/
|
|
52
|
+
status?: AssignmentStatus;
|
|
53
|
+
meta?: unknown;
|
|
54
|
+
}
|
|
55
|
+
interface Conflict {
|
|
56
|
+
id: string;
|
|
57
|
+
/** Assignments involved, sorted ascending by id for stable equality. */
|
|
58
|
+
assignmentIds: string[];
|
|
59
|
+
reason: ConflictReason;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Availability rule.
|
|
63
|
+
*
|
|
64
|
+
* v0 keeps the rule shape minimal — `dayOfWeek` plus an `HH:MM` window.
|
|
65
|
+
* One rule = one window. Multiple windows on the same day = multiple
|
|
66
|
+
* rules. A resource with NO rules is considered always available.
|
|
67
|
+
*
|
|
68
|
+
* TODO(round-10): exception dates (`exception: { date, kind }`) for
|
|
69
|
+
* one-off blackouts / additions are explicitly out of scope for v0 — the
|
|
70
|
+
* spec calls them out but the use-cases (PTO calendars, holiday packs)
|
|
71
|
+
* are not finalised yet. Add them once a production consumer needs
|
|
72
|
+
* either kind so we don't bake the wrong shape in.
|
|
73
|
+
*/
|
|
74
|
+
interface AvailabilityRule {
|
|
75
|
+
resourceId: string;
|
|
76
|
+
/** 0 = Sunday … 6 = Saturday. Omitted ≡ rule applies every day. */
|
|
77
|
+
dayOfWeek?: DayOfWeek;
|
|
78
|
+
/** `HH:MM` (24h, UTC). Omitted ≡ open from 00:00. */
|
|
79
|
+
from?: string;
|
|
80
|
+
/** `HH:MM` (24h, UTC). Omitted ≡ open until 24:00. */
|
|
81
|
+
to?: string;
|
|
82
|
+
}
|
|
83
|
+
interface CalendarState {
|
|
84
|
+
resources: Resource[];
|
|
85
|
+
assignments: Assignment[];
|
|
86
|
+
conflicts: Conflict[];
|
|
87
|
+
}
|
|
88
|
+
interface DetectConflictsOptions {
|
|
89
|
+
scale?: CalendarScale;
|
|
90
|
+
rules?: readonly AvailabilityRule[];
|
|
91
|
+
}
|
|
92
|
+
interface CalendarEngineOptions {
|
|
93
|
+
/** Optional debug name appended to the generated engine id. */
|
|
94
|
+
name?: string;
|
|
95
|
+
/** Default scale used for capacity slot computation. @default 'day' */
|
|
96
|
+
scale?: CalendarScale;
|
|
97
|
+
/** Initial availability rules. Can be replaced via `setRules` later. */
|
|
98
|
+
rules?: readonly AvailabilityRule[];
|
|
99
|
+
}
|
|
100
|
+
interface CalendarEngine {
|
|
101
|
+
/** Stable engine id. Used as the suffix on the `$calendar.<id>.*` paths. */
|
|
102
|
+
readonly id: string;
|
|
103
|
+
addResource(resource: Resource): void;
|
|
104
|
+
removeResource(id: string): void;
|
|
105
|
+
updateResource(id: string, patch: Partial<Resource>): void;
|
|
106
|
+
getResource(id: string): Resource | undefined;
|
|
107
|
+
getResources(): Resource[];
|
|
108
|
+
addAssignment(assignment: Assignment): void;
|
|
109
|
+
updateAssignment(id: string, patch: Partial<Assignment>): void;
|
|
110
|
+
removeAssignment(id: string): void;
|
|
111
|
+
/** Shifts both `start` and `end` by `deltaMs`. */
|
|
112
|
+
moveAssignment(id: string, deltaMs: number): void;
|
|
113
|
+
/**
|
|
114
|
+
* Resizes one side. `'end'` shifts only the end timestamp;
|
|
115
|
+
* `'start'` shifts only the start. The opposite side stays put.
|
|
116
|
+
* The engine clamps the result so the assignment never inverts —
|
|
117
|
+
* minimum width is `1` ms (the React adapter additionally enforces
|
|
118
|
+
* a per-scale step in its drag handler).
|
|
119
|
+
*/
|
|
120
|
+
resizeAssignment(id: string, side: 'start' | 'end', deltaMs: number): void;
|
|
121
|
+
getAssignment(id: string): Assignment | undefined;
|
|
122
|
+
getAssignments(): Assignment[];
|
|
123
|
+
setRules(rules: readonly AvailabilityRule[]): void;
|
|
124
|
+
getRules(): AvailabilityRule[];
|
|
125
|
+
/** Latest computed conflicts; refreshed automatically on mutations. */
|
|
126
|
+
getConflicts(): Conflict[];
|
|
127
|
+
setScale(scale: CalendarScale): void;
|
|
128
|
+
getScale(): CalendarScale;
|
|
129
|
+
getState(): CalendarState;
|
|
130
|
+
/** Subscribe to ANY engine mutation. Returns an unsubscribe fn. */
|
|
131
|
+
subscribe(cb: () => void): () => void;
|
|
132
|
+
/** Reset the engine — drops resources, assignments, rules, conflicts. */
|
|
133
|
+
clear(): void;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Resource Calendar engine — the sixth member of the
|
|
138
|
+
* form / table / tree / virtual / graph / **calendar** family.
|
|
139
|
+
*
|
|
140
|
+
* The in-memory maps are the source of truth and the public read API;
|
|
141
|
+
* snapshots are mirrored to the Core store under
|
|
142
|
+
* `$calendar.<id>.{resources,assignments,conflicts,view}` so other
|
|
143
|
+
* engines / external observers can react via `core.subscribe(...)`.
|
|
144
|
+
*
|
|
145
|
+
* v0 scope:
|
|
146
|
+
* - addResource / removeResource / updateResource
|
|
147
|
+
* - addAssignment / updateAssignment / removeAssignment
|
|
148
|
+
* - moveAssignment(id, deltaMs) — shifts both ends together
|
|
149
|
+
* - resizeAssignment(id, side, deltaMs) — clamps so width >= 1ms
|
|
150
|
+
* - setRules / setScale — re-compute conflicts on change
|
|
151
|
+
* - detectConflicts under the hood; status of involved assignments
|
|
152
|
+
* is auto-promoted to `'conflict'` so the React adapter can render
|
|
153
|
+
* without re-running the algorithm.
|
|
154
|
+
*
|
|
155
|
+
* NOT in v0 (deferred to round-10):
|
|
156
|
+
* - exception dates on `AvailabilityRule`
|
|
157
|
+
* - full per-engine undo/redo (use `core.snapshot/restore` instead)
|
|
158
|
+
* - aggregate read APIs over a sub-range
|
|
159
|
+
*/
|
|
160
|
+
declare function createCalendar(core: Core, options?: CalendarEngineOptions): CalendarEngine;
|
|
161
|
+
|
|
162
|
+
interface DetectConflictsArgs {
|
|
163
|
+
scale?: CalendarScale;
|
|
164
|
+
rules?: readonly AvailabilityRule[];
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Find all conflicts in `state`.
|
|
168
|
+
*
|
|
169
|
+
* Algorithm — three independent passes, results de-duplicated by
|
|
170
|
+
* `(reason, sorted-ids)` so the same overlapping pair is never reported
|
|
171
|
+
* twice as `overlap`:
|
|
172
|
+
*
|
|
173
|
+
* 1. **overlap** (per resource): sort assignments by `start` and sweep.
|
|
174
|
+
* Keep an active set ordered by `end` ASC; whenever a new
|
|
175
|
+
* assignment opens, every still-active assignment forms a pairwise
|
|
176
|
+
* `overlap` conflict with it. O(n log n + k) where k = total
|
|
177
|
+
* overlapping pairs.
|
|
178
|
+
*
|
|
179
|
+
* 2. **over-capacity** (per resource with `capacityPerSlot`): bucket
|
|
180
|
+
* every assignment into the slots it touches (1 slot = 1 scale
|
|
181
|
+
* step). For each slot whose concurrent count exceeds capacity,
|
|
182
|
+
* emit one `over-capacity` conflict naming exactly the assignments
|
|
183
|
+
* that touch that slot. This is the simple counter approach (no
|
|
184
|
+
* interval tree) — fine up to ~10⁵ assignments per resource.
|
|
185
|
+
*
|
|
186
|
+
* 3. **outside-availability**: per-assignment check via
|
|
187
|
+
* `isAvailable`. Each violation becomes its own one-element
|
|
188
|
+
* conflict.
|
|
189
|
+
*/
|
|
190
|
+
declare function detectConflicts(state: Pick<CalendarState, 'resources' | 'assignments'>, opts?: DetectConflictsArgs): Conflict[];
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* `true` ⇔ the entire `[start, end)` interval falls inside at least one
|
|
194
|
+
* availability window for `resource`. A resource without any matching
|
|
195
|
+
* rule (or without any rules at all) is considered always available —
|
|
196
|
+
* unconfigured calendars stay friendly.
|
|
197
|
+
*
|
|
198
|
+
* Implementation: walk one UTC day at a time and check that the slice of
|
|
199
|
+
* `[start, end)` intersected with that day is fully covered by the union
|
|
200
|
+
* of windows attached to that day. The union is built by sorting rule
|
|
201
|
+
* windows by `from` and merging overlaps in O(k log k) where k = rules
|
|
202
|
+
* for that day (typically 1–3).
|
|
203
|
+
*/
|
|
204
|
+
declare function isAvailable(resource: Resource, range: {
|
|
205
|
+
start: number;
|
|
206
|
+
end: number;
|
|
207
|
+
}, rules: readonly AvailabilityRule[]): boolean;
|
|
208
|
+
|
|
209
|
+
export { type Assignment, type AssignmentStatus, type AvailabilityRule, type CalendarEngine, type CalendarEngineOptions, type CalendarScale, type CalendarState, type Conflict, type ConflictReason, type DayOfWeek, type DetectConflictsOptions, type Resource, createCalendar, detectConflicts, isAvailable };
|
package/dist/calendar.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// src/engines/namespaces.ts
|
|
2
|
+
var reserved = /* @__PURE__ */ new Map();
|
|
3
|
+
function reservePrefix(engine, prefix) {
|
|
4
|
+
if (!prefix.startsWith("$") || !prefix.endsWith(".")) {
|
|
5
|
+
throw new Error(
|
|
6
|
+
`[skygraph] engine "${engine}" tried to reserve invalid prefix "${prefix}". Engine prefixes must start with "$" and end with ".".`
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
const existing = reserved.get(prefix);
|
|
10
|
+
if (existing !== void 0 && existing !== engine) {
|
|
11
|
+
throw new Error(
|
|
12
|
+
`[skygraph] prefix "${prefix}" is already owned by engine "${existing}", cannot reserve it for "${engine}". Pick a different prefix or update the registry.`
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
reserved.set(prefix, engine);
|
|
16
|
+
return prefix;
|
|
17
|
+
}
|
|
18
|
+
function listReservedPrefixes() {
|
|
19
|
+
return new Map(reserved);
|
|
20
|
+
}
|
|
21
|
+
var FORM_META_PREFIX = reservePrefix("form", "$meta.");
|
|
22
|
+
var TABLE_PREFIX = reservePrefix("table", "$table.");
|
|
23
|
+
var TREE_PREFIX = reservePrefix("tree", "$tree.");
|
|
24
|
+
var GRAPH_PREFIX = reservePrefix("graph", "$graph.");
|
|
25
|
+
var CALENDAR_PREFIX = reservePrefix("calendar", "$calendar.");
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
reservePrefix,
|
|
29
|
+
listReservedPrefixes,
|
|
30
|
+
FORM_META_PREFIX,
|
|
31
|
+
TABLE_PREFIX,
|
|
32
|
+
TREE_PREFIX,
|
|
33
|
+
GRAPH_PREFIX,
|
|
34
|
+
CALENDAR_PREFIX
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=chunk-5CCD5Q4B.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/engines/namespaces.ts"],"sourcesContent":["/**\n * Engine path namespace registry\n *\n * Every engine that stores state in the Core store under a `$`-prefixed\n * namespace MUST register its prefix here. This prevents two engines from\n * silently colliding on the same root path (e.g. two engines both writing\n * under `$meta.`).\n *\n * Rules:\n * 1. Prefixes MUST start with `$` and end with `.`.\n * 2. Prefixes MUST be unique across all engines.\n * 3. An engine MUST use `reservedPrefix(name)` at module load so\n * collisions surface at import time during dev/test.\n * 4. User data MUST NOT live under `$` prefixes — reserve them for engines.\n */\n\nconst reserved = new Map<string, string>()\n\n/**\n * Reserve (or look up) a prefix for an engine. Throws synchronously if the\n * same prefix is already owned by a different engine.\n */\nexport function reservePrefix(engine: string, prefix: string): string {\n if (!prefix.startsWith('$') || !prefix.endsWith('.')) {\n throw new Error(\n `[skygraph] engine \"${engine}\" tried to reserve invalid prefix \"${prefix}\". ` +\n `Engine prefixes must start with \"$\" and end with \".\".`,\n )\n }\n\n const existing = reserved.get(prefix)\n if (existing !== undefined && existing !== engine) {\n throw new Error(\n `[skygraph] prefix \"${prefix}\" is already owned by engine \"${existing}\", ` +\n `cannot reserve it for \"${engine}\". ` +\n `Pick a different prefix or update the registry.`,\n )\n }\n\n reserved.set(prefix, engine)\n return prefix\n}\n\n/** Snapshot of registered engine prefixes (debug / introspection). */\nexport function listReservedPrefixes(): ReadonlyMap<string, string> {\n return new Map(reserved)\n}\n\n/** Test-only helper. DO NOT call from engine code. */\nexport function __resetPrefixes(): void {\n reserved.clear()\n}\n\n// ── Canonical in-tree reservations ─────────────────────────────────────────\n//\n// Listed here so they are reserved even if the engine module is not loaded.\n// New engines must add their entry here and reference it from their\n// implementation via `reservePrefix(...)`.\n\nexport const FORM_META_PREFIX = reservePrefix('form', '$meta.')\nexport const TABLE_PREFIX = reservePrefix('table', '$table.')\nexport const TREE_PREFIX = reservePrefix('tree', '$tree.')\nexport const GRAPH_PREFIX = reservePrefix('graph', '$graph.')\nexport const CALENDAR_PREFIX = reservePrefix('calendar', '$calendar.')\n"],"mappings":";AAgBA,IAAM,WAAW,oBAAI,IAAoB;AAMlC,SAAS,cAAc,QAAgB,QAAwB;AACpE,MAAI,CAAC,OAAO,WAAW,GAAG,KAAK,CAAC,OAAO,SAAS,GAAG,GAAG;AACpD,UAAM,IAAI;AAAA,MACR,sBAAsB,MAAM,sCAAsC,MAAM;AAAA,IAE1E;AAAA,EACF;AAEA,QAAM,WAAW,SAAS,IAAI,MAAM;AACpC,MAAI,aAAa,UAAa,aAAa,QAAQ;AACjD,UAAM,IAAI;AAAA,MACR,sBAAsB,MAAM,iCAAiC,QAAQ,6BACzC,MAAM;AAAA,IAEpC;AAAA,EACF;AAEA,WAAS,IAAI,QAAQ,MAAM;AAC3B,SAAO;AACT;AAGO,SAAS,uBAAoD;AAClE,SAAO,IAAI,IAAI,QAAQ;AACzB;AAaO,IAAM,mBAAmB,cAAc,QAAQ,QAAQ;AACvD,IAAM,eAAe,cAAc,SAAS,SAAS;AACrD,IAAM,cAAc,cAAc,QAAQ,QAAQ;AAClD,IAAM,eAAe,cAAc,SAAS,SAAS;AACrD,IAAM,kBAAkB,cAAc,YAAY,YAAY;","names":[]}
|