@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.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/dist/calendar.cjs +404 -0
  3. package/dist/calendar.cjs.map +1 -0
  4. package/dist/calendar.d.cts +209 -0
  5. package/dist/calendar.d.ts +209 -0
  6. package/dist/calendar.js +12 -0
  7. package/dist/calendar.js.map +1 -0
  8. package/dist/chunk-5CCD5Q4B.js +36 -0
  9. package/dist/chunk-5CCD5Q4B.js.map +1 -0
  10. package/dist/chunk-AB3RLBLW.js +357 -0
  11. package/dist/chunk-AB3RLBLW.js.map +1 -0
  12. package/dist/chunk-GEMALROQ.js +258 -0
  13. package/dist/chunk-GEMALROQ.js.map +1 -0
  14. package/dist/chunk-IY6LJU3L.js +256 -0
  15. package/dist/chunk-IY6LJU3L.js.map +1 -0
  16. package/dist/chunk-KMGRNPLG.js +453 -0
  17. package/dist/chunk-KMGRNPLG.js.map +1 -0
  18. package/dist/chunk-KP75DEA4.js +1012 -0
  19. package/dist/chunk-KP75DEA4.js.map +1 -0
  20. package/dist/chunk-Y7FTBBYX.js +397 -0
  21. package/dist/chunk-Y7FTBBYX.js.map +1 -0
  22. package/dist/chunk-ZWB7JGAJ.js +488 -0
  23. package/dist/chunk-ZWB7JGAJ.js.map +1 -0
  24. package/dist/form.cjs +498 -0
  25. package/dist/form.cjs.map +1 -0
  26. package/dist/form.d.cts +130 -0
  27. package/dist/form.d.ts +130 -0
  28. package/dist/form.js +8 -0
  29. package/dist/form.js.map +1 -0
  30. package/dist/graph.cjs +1065 -0
  31. package/dist/graph.cjs.map +1 -0
  32. package/dist/graph.d.cts +542 -0
  33. package/dist/graph.d.ts +542 -0
  34. package/dist/graph.js +24 -0
  35. package/dist/graph.js.map +1 -0
  36. package/dist/index.cjs +3656 -0
  37. package/dist/index.cjs.map +1 -0
  38. package/dist/index.d.cts +144 -0
  39. package/dist/index.d.ts +144 -0
  40. package/dist/index.js +467 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/runtime-internal.cjs +339 -0
  43. package/dist/runtime-internal.cjs.map +1 -0
  44. package/dist/runtime-internal.d.cts +130 -0
  45. package/dist/runtime-internal.d.ts +130 -0
  46. package/dist/runtime-internal.js +67 -0
  47. package/dist/runtime-internal.js.map +1 -0
  48. package/dist/table.cjs +537 -0
  49. package/dist/table.cjs.map +1 -0
  50. package/dist/table.d.cts +215 -0
  51. package/dist/table.d.ts +215 -0
  52. package/dist/table.js +16 -0
  53. package/dist/table.js.map +1 -0
  54. package/dist/tree.cjs +442 -0
  55. package/dist/tree.cjs.map +1 -0
  56. package/dist/tree.d.cts +76 -0
  57. package/dist/tree.d.ts +76 -0
  58. package/dist/tree.js +8 -0
  59. package/dist/tree.js.map +1 -0
  60. package/dist/types-B-rKQJ4R.d.cts +35 -0
  61. package/dist/types-B-rKQJ4R.d.ts +35 -0
  62. package/dist/virtual.cjs +283 -0
  63. package/dist/virtual.cjs.map +1 -0
  64. package/dist/virtual.d.cts +96 -0
  65. package/dist/virtual.d.ts +96 -0
  66. package/dist/virtual.js +9 -0
  67. package/dist/virtual.js.map +1 -0
  68. package/package.json +98 -18
  69. package/README.md +0 -12
  70. package/index.js +0 -3
@@ -0,0 +1,357 @@
1
+ import {
2
+ CALENDAR_PREFIX
3
+ } from "./chunk-5CCD5Q4B.js";
4
+
5
+ // src/engines/calendar/availability.ts
6
+ var DAY_MS = 864e5;
7
+ function parseHHMM(s, fallback) {
8
+ if (s === void 0) return fallback;
9
+ const m = /^(\d{1,2}):(\d{2})$/.exec(s);
10
+ if (!m) return fallback;
11
+ const h = Number(m[1]);
12
+ const mm = Number(m[2]);
13
+ if (!Number.isFinite(h) || !Number.isFinite(mm)) return fallback;
14
+ if (h < 0 || h > 24 || mm < 0 || mm >= 60) return fallback;
15
+ return h * 36e5 + mm * 6e4;
16
+ }
17
+ function rulesFor(resourceId, rules) {
18
+ return rules.filter((r) => r.resourceId === resourceId);
19
+ }
20
+ function dayBucket(t) {
21
+ const d = new Date(t);
22
+ const dayStart = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
23
+ const dayOfWeek = new Date(dayStart).getUTCDay();
24
+ return { dayStart, dayOfWeek };
25
+ }
26
+ function isAvailable(resource, range, rules) {
27
+ if (range.end <= range.start) return true;
28
+ const own = rulesFor(resource.id, rules);
29
+ if (own.length === 0) return true;
30
+ let cursor = range.start;
31
+ while (cursor < range.end) {
32
+ const { dayStart, dayOfWeek } = dayBucket(cursor);
33
+ const dayEnd = dayStart + DAY_MS;
34
+ const sliceEnd = Math.min(range.end, dayEnd);
35
+ const dayRules = own.filter(
36
+ (r) => r.dayOfWeek === void 0 || r.dayOfWeek === dayOfWeek
37
+ );
38
+ if (dayRules.length === 0) return false;
39
+ const intervals = dayRules.map((r) => {
40
+ const fromMs = parseHHMM(r.from, 0);
41
+ const toMs = parseHHMM(r.to, DAY_MS);
42
+ return { from: dayStart + fromMs, to: dayStart + toMs };
43
+ }).filter((w) => w.to > w.from).sort((a, b) => a.from - b.from);
44
+ if (intervals.length === 0) return false;
45
+ const merged = [];
46
+ for (const w of intervals) {
47
+ const last = merged[merged.length - 1];
48
+ if (last && w.from <= last.to) {
49
+ last.to = Math.max(last.to, w.to);
50
+ } else {
51
+ merged.push({ ...w });
52
+ }
53
+ }
54
+ let needed = cursor;
55
+ for (const w of merged) {
56
+ if (w.from > needed) return false;
57
+ if (w.to >= sliceEnd) {
58
+ needed = sliceEnd;
59
+ break;
60
+ }
61
+ needed = Math.max(needed, w.to);
62
+ }
63
+ if (needed < sliceEnd) return false;
64
+ cursor = sliceEnd;
65
+ }
66
+ return true;
67
+ }
68
+
69
+ // src/engines/calendar/conflicts.ts
70
+ var DAY_MS2 = 864e5;
71
+ var STEP_MS = {
72
+ day: DAY_MS2,
73
+ week: 7 * DAY_MS2,
74
+ month: 30 * DAY_MS2
75
+ };
76
+ function makeId(reason, ids) {
77
+ return `${reason}:${[...ids].sort().join(",")}`;
78
+ }
79
+ function pushConflict(out, reason, ids) {
80
+ if (ids.length === 0) return;
81
+ const sorted = [...ids].sort();
82
+ const id = makeId(reason, sorted);
83
+ if (out.has(id)) return;
84
+ out.set(id, { id, reason, assignmentIds: sorted });
85
+ }
86
+ function detectConflicts(state, opts = {}) {
87
+ const scale = opts.scale ?? "day";
88
+ const step = STEP_MS[scale];
89
+ const rules = opts.rules ?? [];
90
+ const conflicts = /* @__PURE__ */ new Map();
91
+ const byResource = /* @__PURE__ */ new Map();
92
+ for (const a of state.assignments) {
93
+ let bucket = byResource.get(a.resourceId);
94
+ if (!bucket) {
95
+ bucket = [];
96
+ byResource.set(a.resourceId, bucket);
97
+ }
98
+ bucket.push(a);
99
+ }
100
+ for (const list of byResource.values()) {
101
+ const sorted = [...list].sort((x, y) => x.start - y.start);
102
+ for (let i = 0; i < sorted.length; i++) {
103
+ const a = sorted[i];
104
+ for (let j = i + 1; j < sorted.length; j++) {
105
+ const b = sorted[j];
106
+ if (b.start >= a.end) break;
107
+ pushConflict(conflicts, "overlap", [a.id, b.id]);
108
+ }
109
+ }
110
+ }
111
+ const resourceById = /* @__PURE__ */ new Map();
112
+ for (const r of state.resources) resourceById.set(r.id, r);
113
+ for (const [rid, list] of byResource) {
114
+ const resource = resourceById.get(rid);
115
+ if (!resource || resource.capacityPerSlot === void 0) continue;
116
+ const cap = Math.max(0, Math.floor(resource.capacityPerSlot));
117
+ const slotMap = /* @__PURE__ */ new Map();
118
+ for (const a of list) {
119
+ const lo = Math.floor(a.start / step) * step;
120
+ const hi = Math.ceil(a.end / step) * step;
121
+ for (let s = lo; s < hi; s += step) {
122
+ let bucket = slotMap.get(s);
123
+ if (!bucket) {
124
+ bucket = [];
125
+ slotMap.set(s, bucket);
126
+ }
127
+ bucket.push(a.id);
128
+ }
129
+ }
130
+ for (const ids of slotMap.values()) {
131
+ if (ids.length > cap) {
132
+ pushConflict(conflicts, "over-capacity", ids);
133
+ }
134
+ }
135
+ }
136
+ if (rules.length > 0) {
137
+ for (const a of state.assignments) {
138
+ const r = resourceById.get(a.resourceId);
139
+ if (!r) continue;
140
+ if (!isAvailable(r, { start: a.start, end: a.end }, rules)) {
141
+ pushConflict(conflicts, "outside-availability", [a.id]);
142
+ }
143
+ }
144
+ }
145
+ return [...conflicts.values()];
146
+ }
147
+
148
+ // src/engines/calendar/CalendarEngine.ts
149
+ var calendarCounter = 0;
150
+ function createCalendar(core, options) {
151
+ const engineId = `c${calendarCounter++}${options?.name ? `-${options.name}` : ""}`;
152
+ const prefix = CALENDAR_PREFIX;
153
+ const resources = /* @__PURE__ */ new Map();
154
+ const assignments = /* @__PURE__ */ new Map();
155
+ let rules = options?.rules ? [...options.rules] : [];
156
+ let scale = options?.scale ?? "day";
157
+ let conflicts = [];
158
+ const localSubscribers = /* @__PURE__ */ new Set();
159
+ function publish() {
160
+ const resList = [...resources.values()];
161
+ const assignList = [...assignments.values()];
162
+ core.batch(() => {
163
+ core.set(`${prefix}${engineId}.resources`, resList);
164
+ core.set(`${prefix}${engineId}.assignments`, assignList);
165
+ core.set(`${prefix}${engineId}.conflicts`, conflicts);
166
+ core.set(`${prefix}${engineId}.view`, { scale });
167
+ });
168
+ for (const cb of localSubscribers) cb();
169
+ }
170
+ function recompute() {
171
+ conflicts = detectConflicts(
172
+ { resources: [...resources.values()], assignments: [...assignments.values()] },
173
+ { scale, rules }
174
+ );
175
+ const involved = /* @__PURE__ */ new Set();
176
+ for (const c of conflicts) for (const id of c.assignmentIds) involved.add(id);
177
+ for (const [id, a] of assignments) {
178
+ const isConflicting = involved.has(id);
179
+ if (isConflicting && a.status !== "conflict") {
180
+ assignments.set(id, { ...a, status: "conflict" });
181
+ } else if (!isConflicting && a.status === "conflict") {
182
+ assignments.set(id, { ...a, status: "tentative" });
183
+ }
184
+ }
185
+ }
186
+ function commitAndPublish() {
187
+ recompute();
188
+ publish();
189
+ }
190
+ function subscribe(cb) {
191
+ localSubscribers.add(cb);
192
+ return () => {
193
+ localSubscribers.delete(cb);
194
+ };
195
+ }
196
+ function addResource(resource) {
197
+ if (resources.has(resource.id)) {
198
+ throw new Error(
199
+ `[skygraph/calendar] resource id "${resource.id}" already exists`
200
+ );
201
+ }
202
+ resources.set(resource.id, { ...resource });
203
+ commitAndPublish();
204
+ }
205
+ function removeResource(id) {
206
+ if (!resources.has(id)) return;
207
+ resources.delete(id);
208
+ for (const [aid, a] of assignments) {
209
+ if (a.resourceId === id) assignments.delete(aid);
210
+ }
211
+ commitAndPublish();
212
+ }
213
+ function updateResource(id, patch) {
214
+ const r = resources.get(id);
215
+ if (!r) throw new Error(`[skygraph/calendar] resource "${id}" not found`);
216
+ resources.set(id, { ...r, ...patch, id });
217
+ commitAndPublish();
218
+ }
219
+ function getResource(id) {
220
+ return resources.get(id);
221
+ }
222
+ function getResources() {
223
+ return [...resources.values()];
224
+ }
225
+ function addAssignment(assignment) {
226
+ if (assignments.has(assignment.id)) {
227
+ throw new Error(
228
+ `[skygraph/calendar] assignment id "${assignment.id}" already exists`
229
+ );
230
+ }
231
+ if (!resources.has(assignment.resourceId)) {
232
+ throw new Error(
233
+ `[skygraph/calendar] addAssignment: resource "${assignment.resourceId}" not found`
234
+ );
235
+ }
236
+ if (assignment.end <= assignment.start) {
237
+ throw new Error(
238
+ `[skygraph/calendar] addAssignment: end (${assignment.end}) must be greater than start (${assignment.start})`
239
+ );
240
+ }
241
+ assignments.set(assignment.id, { ...assignment });
242
+ commitAndPublish();
243
+ }
244
+ function updateAssignment(id, patch) {
245
+ const a = assignments.get(id);
246
+ if (!a) throw new Error(`[skygraph/calendar] assignment "${id}" not found`);
247
+ const next = { ...a, ...patch, id };
248
+ if (next.end <= next.start) {
249
+ throw new Error(
250
+ `[skygraph/calendar] updateAssignment: end (${next.end}) must be greater than start (${next.start})`
251
+ );
252
+ }
253
+ if (!resources.has(next.resourceId)) {
254
+ throw new Error(
255
+ `[skygraph/calendar] updateAssignment: resource "${next.resourceId}" not found`
256
+ );
257
+ }
258
+ assignments.set(id, next);
259
+ commitAndPublish();
260
+ }
261
+ function removeAssignment(id) {
262
+ if (!assignments.has(id)) return;
263
+ assignments.delete(id);
264
+ commitAndPublish();
265
+ }
266
+ function moveAssignment(id, deltaMs) {
267
+ const a = assignments.get(id);
268
+ if (!a) throw new Error(`[skygraph/calendar] assignment "${id}" not found`);
269
+ if (deltaMs === 0) return;
270
+ assignments.set(id, { ...a, start: a.start + deltaMs, end: a.end + deltaMs });
271
+ commitAndPublish();
272
+ }
273
+ function resizeAssignment(id, side, deltaMs) {
274
+ const a = assignments.get(id);
275
+ if (!a) throw new Error(`[skygraph/calendar] assignment "${id}" not found`);
276
+ if (deltaMs === 0) return;
277
+ let next;
278
+ if (side === "end") {
279
+ const nextEnd = Math.max(a.start + 1, a.end + deltaMs);
280
+ next = { ...a, end: nextEnd };
281
+ } else {
282
+ const nextStart = Math.min(a.end - 1, a.start + deltaMs);
283
+ next = { ...a, start: nextStart };
284
+ }
285
+ assignments.set(id, next);
286
+ commitAndPublish();
287
+ }
288
+ function getAssignment(id) {
289
+ return assignments.get(id);
290
+ }
291
+ function getAssignments() {
292
+ return [...assignments.values()];
293
+ }
294
+ function setRules(next) {
295
+ rules = [...next];
296
+ commitAndPublish();
297
+ }
298
+ function getRules() {
299
+ return [...rules];
300
+ }
301
+ function getConflicts() {
302
+ return [...conflicts];
303
+ }
304
+ function setScale(next) {
305
+ if (next === scale) return;
306
+ scale = next;
307
+ commitAndPublish();
308
+ }
309
+ function getScale() {
310
+ return scale;
311
+ }
312
+ function getState() {
313
+ return {
314
+ resources: [...resources.values()],
315
+ assignments: [...assignments.values()],
316
+ conflicts: [...conflicts]
317
+ };
318
+ }
319
+ function clear() {
320
+ resources.clear();
321
+ assignments.clear();
322
+ rules = [];
323
+ conflicts = [];
324
+ publish();
325
+ }
326
+ publish();
327
+ return {
328
+ id: engineId,
329
+ addResource,
330
+ removeResource,
331
+ updateResource,
332
+ getResource,
333
+ getResources,
334
+ addAssignment,
335
+ updateAssignment,
336
+ removeAssignment,
337
+ moveAssignment,
338
+ resizeAssignment,
339
+ getAssignment,
340
+ getAssignments,
341
+ setRules,
342
+ getRules,
343
+ getConflicts,
344
+ setScale,
345
+ getScale,
346
+ getState,
347
+ subscribe,
348
+ clear
349
+ };
350
+ }
351
+
352
+ export {
353
+ isAvailable,
354
+ detectConflicts,
355
+ createCalendar
356
+ };
357
+ //# sourceMappingURL=chunk-AB3RLBLW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/engines/calendar/availability.ts","../src/engines/calendar/conflicts.ts","../src/engines/calendar/CalendarEngine.ts"],"sourcesContent":["import type { AvailabilityRule, DayOfWeek, Resource } from './types'\n\nconst DAY_MS = 86_400_000\n\n/**\n * Parse `HH:MM` into milliseconds-from-midnight.\n *\n * Returns `null` for malformed input — callers treat that as \"rule has no\n * window on this side\" (i.e. the canonical 00:00 / 24:00 fallback).\n */\nfunction parseHHMM(s: string | undefined, fallback: number): number {\n if (s === undefined) return fallback\n const m = /^(\\d{1,2}):(\\d{2})$/.exec(s)\n if (!m) return fallback\n const h = Number(m[1])\n const mm = Number(m[2])\n if (!Number.isFinite(h) || !Number.isFinite(mm)) return fallback\n if (h < 0 || h > 24 || mm < 0 || mm >= 60) return fallback\n return h * 3_600_000 + mm * 60_000\n}\n\n/** Rules attached to one resource. Cheap to compute, cached per call. */\nfunction rulesFor(\n resourceId: string,\n rules: readonly AvailabilityRule[],\n): AvailabilityRule[] {\n return rules.filter((r) => r.resourceId === resourceId)\n}\n\nfunction dayBucket(t: number): { dayStart: number; dayOfWeek: DayOfWeek } {\n // Floor to UTC day boundary; getUTCDay returns 0..6 (Sun..Sat).\n const d = new Date(t)\n const dayStart =\n Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())\n const dayOfWeek = new Date(dayStart).getUTCDay() as DayOfWeek\n return { dayStart, dayOfWeek }\n}\n\n/**\n * `true` ⇔ the entire `[start, end)` interval falls inside at least one\n * availability window for `resource`. A resource without any matching\n * rule (or without any rules at all) is considered always available —\n * unconfigured calendars stay friendly.\n *\n * Implementation: walk one UTC day at a time and check that the slice of\n * `[start, end)` intersected with that day is fully covered by the union\n * of windows attached to that day. The union is built by sorting rule\n * windows by `from` and merging overlaps in O(k log k) where k = rules\n * for that day (typically 1–3).\n */\nexport function isAvailable(\n resource: Resource,\n range: { start: number; end: number },\n rules: readonly AvailabilityRule[],\n): boolean {\n if (range.end <= range.start) return true\n\n const own = rulesFor(resource.id, rules)\n if (own.length === 0) return true\n\n let cursor = range.start\n while (cursor < range.end) {\n const { dayStart, dayOfWeek } = dayBucket(cursor)\n const dayEnd = dayStart + DAY_MS\n const sliceEnd = Math.min(range.end, dayEnd)\n\n const dayRules = own.filter(\n (r) => r.dayOfWeek === undefined || r.dayOfWeek === dayOfWeek,\n )\n if (dayRules.length === 0) return false\n\n // Build union of windows for this day, in absolute ms.\n const intervals = dayRules\n .map((r) => {\n const fromMs = parseHHMM(r.from, 0)\n const toMs = parseHHMM(r.to, DAY_MS)\n return { from: dayStart + fromMs, to: dayStart + toMs }\n })\n .filter((w) => w.to > w.from)\n .sort((a, b) => a.from - b.from)\n\n if (intervals.length === 0) return false\n\n const merged: Array<{ from: number; to: number }> = []\n for (const w of intervals) {\n const last = merged[merged.length - 1]\n if (last && w.from <= last.to) {\n last.to = Math.max(last.to, w.to)\n } else {\n merged.push({ ...w })\n }\n }\n\n // The required slice for this day is [cursor, sliceEnd). It must be\n // fully contained in the union. We sweep the merged windows.\n let needed = cursor\n for (const w of merged) {\n if (w.from > needed) return false\n if (w.to >= sliceEnd) {\n needed = sliceEnd\n break\n }\n needed = Math.max(needed, w.to)\n }\n if (needed < sliceEnd) return false\n\n cursor = sliceEnd\n }\n\n return true\n}\n","import { isAvailable } from './availability'\nimport type {\n Assignment,\n AvailabilityRule,\n CalendarScale,\n CalendarState,\n Conflict,\n Resource,\n} from './types'\n\nconst DAY_MS = 86_400_000\nconst STEP_MS: Record<CalendarScale, number> = {\n day: DAY_MS,\n week: 7 * DAY_MS,\n month: 30 * DAY_MS,\n}\n\nexport interface DetectConflictsArgs {\n scale?: CalendarScale\n rules?: readonly AvailabilityRule[]\n}\n\nfunction makeId(reason: Conflict['reason'], ids: readonly string[]): string {\n return `${reason}:${[...ids].sort().join(',')}`\n}\n\nfunction pushConflict(\n out: Map<string, Conflict>,\n reason: Conflict['reason'],\n ids: string[],\n): void {\n if (ids.length === 0) return\n const sorted = [...ids].sort()\n const id = makeId(reason, sorted)\n if (out.has(id)) return\n out.set(id, { id, reason, assignmentIds: sorted })\n}\n\n/**\n * Find all conflicts in `state`.\n *\n * Algorithm — three independent passes, results de-duplicated by\n * `(reason, sorted-ids)` so the same overlapping pair is never reported\n * twice as `overlap`:\n *\n * 1. **overlap** (per resource): sort assignments by `start` and sweep.\n * Keep an active set ordered by `end` ASC; whenever a new\n * assignment opens, every still-active assignment forms a pairwise\n * `overlap` conflict with it. O(n log n + k) where k = total\n * overlapping pairs.\n *\n * 2. **over-capacity** (per resource with `capacityPerSlot`): bucket\n * every assignment into the slots it touches (1 slot = 1 scale\n * step). For each slot whose concurrent count exceeds capacity,\n * emit one `over-capacity` conflict naming exactly the assignments\n * that touch that slot. This is the simple counter approach (no\n * interval tree) — fine up to ~10⁵ assignments per resource.\n *\n * 3. **outside-availability**: per-assignment check via\n * `isAvailable`. Each violation becomes its own one-element\n * conflict.\n */\nexport function detectConflicts(\n state: Pick<CalendarState, 'resources' | 'assignments'>,\n opts: DetectConflictsArgs = {},\n): Conflict[] {\n const scale = opts.scale ?? 'day'\n const step = STEP_MS[scale]\n const rules = opts.rules ?? []\n const conflicts = new Map<string, Conflict>()\n\n const byResource = new Map<string, Assignment[]>()\n for (const a of state.assignments) {\n let bucket = byResource.get(a.resourceId)\n if (!bucket) {\n bucket = []\n byResource.set(a.resourceId, bucket)\n }\n bucket.push(a)\n }\n\n // ── 1. Pairwise overlap (per resource) ─────────────────────────────\n for (const list of byResource.values()) {\n const sorted = [...list].sort((x, y) => x.start - y.start)\n for (let i = 0; i < sorted.length; i++) {\n const a = sorted[i]!\n for (let j = i + 1; j < sorted.length; j++) {\n const b = sorted[j]!\n if (b.start >= a.end) break\n // Touching boundaries (b.start === a.end) is NOT a conflict —\n // assignment ranges are half-open [start, end).\n pushConflict(conflicts, 'overlap', [a.id, b.id])\n }\n }\n }\n\n // ── 2. Over-capacity per slot (per resource with capacityPerSlot) ──\n const resourceById = new Map<string, Resource>()\n for (const r of state.resources) resourceById.set(r.id, r)\n\n for (const [rid, list] of byResource) {\n const resource = resourceById.get(rid)\n if (!resource || resource.capacityPerSlot === undefined) continue\n const cap = Math.max(0, Math.floor(resource.capacityPerSlot))\n\n const slotMap = new Map<number, string[]>()\n for (const a of list) {\n // Every slot the assignment touches.\n const lo = Math.floor(a.start / step) * step\n const hi = Math.ceil(a.end / step) * step\n for (let s = lo; s < hi; s += step) {\n let bucket = slotMap.get(s)\n if (!bucket) {\n bucket = []\n slotMap.set(s, bucket)\n }\n bucket.push(a.id)\n }\n }\n\n for (const ids of slotMap.values()) {\n if (ids.length > cap) {\n pushConflict(conflicts, 'over-capacity', ids)\n }\n }\n }\n\n // ── 3. Outside availability (per assignment) ───────────────────────\n if (rules.length > 0) {\n for (const a of state.assignments) {\n const r = resourceById.get(a.resourceId)\n if (!r) continue\n if (!isAvailable(r, { start: a.start, end: a.end }, rules)) {\n pushConflict(conflicts, 'outside-availability', [a.id])\n }\n }\n }\n\n return [...conflicts.values()]\n}\n","import type { Core } from '../../types'\nimport { CALENDAR_PREFIX } from '../namespaces'\nimport { detectConflicts } from './conflicts'\nimport type {\n Assignment,\n AvailabilityRule,\n CalendarEngine,\n CalendarEngineOptions,\n CalendarScale,\n CalendarState,\n Conflict,\n Resource,\n} from './types'\n\nlet calendarCounter = 0\n\n/**\n * Resource Calendar engine — the sixth member of the\n * form / table / tree / virtual / graph / **calendar** family.\n *\n * The in-memory maps are the source of truth and the public read API;\n * snapshots are mirrored to the Core store under\n * `$calendar.<id>.{resources,assignments,conflicts,view}` so other\n * engines / external observers can react via `core.subscribe(...)`.\n *\n * v0 scope:\n * - addResource / removeResource / updateResource\n * - addAssignment / updateAssignment / removeAssignment\n * - moveAssignment(id, deltaMs) — shifts both ends together\n * - resizeAssignment(id, side, deltaMs) — clamps so width >= 1ms\n * - setRules / setScale — re-compute conflicts on change\n * - detectConflicts under the hood; status of involved assignments\n * is auto-promoted to `'conflict'` so the React adapter can render\n * without re-running the algorithm.\n *\n * NOT in v0 (deferred to round-10):\n * - exception dates on `AvailabilityRule`\n * - full per-engine undo/redo (use `core.snapshot/restore` instead)\n * - aggregate read APIs over a sub-range\n */\nexport function createCalendar(\n core: Core,\n options?: CalendarEngineOptions,\n): CalendarEngine {\n const engineId = `c${calendarCounter++}${options?.name ? `-${options.name}` : ''}`\n const prefix = CALENDAR_PREFIX\n\n const resources = new Map<string, Resource>()\n const assignments = new Map<string, Assignment>()\n let rules: AvailabilityRule[] = options?.rules ? [...options.rules] : []\n let scale: CalendarScale = options?.scale ?? 'day'\n let conflicts: Conflict[] = []\n\n const localSubscribers = new Set<() => void>()\n\n function publish(): void {\n const resList = [...resources.values()]\n const assignList = [...assignments.values()]\n core.batch(() => {\n core.set(`${prefix}${engineId}.resources`, resList)\n core.set(`${prefix}${engineId}.assignments`, assignList)\n core.set(`${prefix}${engineId}.conflicts`, conflicts)\n core.set(`${prefix}${engineId}.view`, { scale })\n })\n for (const cb of localSubscribers) cb()\n }\n\n function recompute(): void {\n conflicts = detectConflicts(\n { resources: [...resources.values()], assignments: [...assignments.values()] },\n { scale, rules },\n )\n // Promote involved assignments to status: 'conflict'. Assignments\n // that aren't in any conflict get their original (or default)\n // status restored. We don't lose the underlying intent: the\n // promotion is reversible and recomputed on every mutation.\n const involved = new Set<string>()\n for (const c of conflicts) for (const id of c.assignmentIds) involved.add(id)\n for (const [id, a] of assignments) {\n const isConflicting = involved.has(id)\n if (isConflicting && a.status !== 'conflict') {\n assignments.set(id, { ...a, status: 'conflict' })\n } else if (!isConflicting && a.status === 'conflict') {\n // Drop the auto-`conflict` flag — the original intent (tentative\n // / confirmed / undefined) was lost when we promoted, and the\n // adapter only needs to know \"no longer conflicting\", so we\n // default back to `'tentative'` which is the safest fallback.\n assignments.set(id, { ...a, status: 'tentative' })\n }\n }\n }\n\n function commitAndPublish(): void {\n recompute()\n publish()\n }\n\n function subscribe(cb: () => void): () => void {\n localSubscribers.add(cb)\n return () => {\n localSubscribers.delete(cb)\n }\n }\n\n function addResource(resource: Resource): void {\n if (resources.has(resource.id)) {\n throw new Error(\n `[skygraph/calendar] resource id \"${resource.id}\" already exists`,\n )\n }\n resources.set(resource.id, { ...resource })\n commitAndPublish()\n }\n\n function removeResource(id: string): void {\n if (!resources.has(id)) return\n resources.delete(id)\n // Cascade: drop assignments tied to this resource.\n for (const [aid, a] of assignments) {\n if (a.resourceId === id) assignments.delete(aid)\n }\n commitAndPublish()\n }\n\n function updateResource(id: string, patch: Partial<Resource>): void {\n const r = resources.get(id)\n if (!r) throw new Error(`[skygraph/calendar] resource \"${id}\" not found`)\n resources.set(id, { ...r, ...patch, id })\n commitAndPublish()\n }\n\n function getResource(id: string): Resource | undefined {\n return resources.get(id)\n }\n\n function getResources(): Resource[] {\n return [...resources.values()]\n }\n\n function addAssignment(assignment: Assignment): void {\n if (assignments.has(assignment.id)) {\n throw new Error(\n `[skygraph/calendar] assignment id \"${assignment.id}\" already exists`,\n )\n }\n if (!resources.has(assignment.resourceId)) {\n throw new Error(\n `[skygraph/calendar] addAssignment: resource \"${assignment.resourceId}\" not found`,\n )\n }\n if (assignment.end <= assignment.start) {\n throw new Error(\n `[skygraph/calendar] addAssignment: end (${assignment.end}) must be greater than start (${assignment.start})`,\n )\n }\n assignments.set(assignment.id, { ...assignment })\n commitAndPublish()\n }\n\n function updateAssignment(id: string, patch: Partial<Assignment>): void {\n const a = assignments.get(id)\n if (!a) throw new Error(`[skygraph/calendar] assignment \"${id}\" not found`)\n const next: Assignment = { ...a, ...patch, id }\n if (next.end <= next.start) {\n throw new Error(\n `[skygraph/calendar] updateAssignment: end (${next.end}) must be greater than start (${next.start})`,\n )\n }\n if (!resources.has(next.resourceId)) {\n throw new Error(\n `[skygraph/calendar] updateAssignment: resource \"${next.resourceId}\" not found`,\n )\n }\n assignments.set(id, next)\n commitAndPublish()\n }\n\n function removeAssignment(id: string): void {\n if (!assignments.has(id)) return\n assignments.delete(id)\n commitAndPublish()\n }\n\n function moveAssignment(id: string, deltaMs: number): void {\n const a = assignments.get(id)\n if (!a) throw new Error(`[skygraph/calendar] assignment \"${id}\" not found`)\n if (deltaMs === 0) return\n assignments.set(id, { ...a, start: a.start + deltaMs, end: a.end + deltaMs })\n commitAndPublish()\n }\n\n function resizeAssignment(\n id: string,\n side: 'start' | 'end',\n deltaMs: number,\n ): void {\n const a = assignments.get(id)\n if (!a) throw new Error(`[skygraph/calendar] assignment \"${id}\" not found`)\n if (deltaMs === 0) return\n let next: Assignment\n if (side === 'end') {\n const nextEnd = Math.max(a.start + 1, a.end + deltaMs)\n next = { ...a, end: nextEnd }\n } else {\n const nextStart = Math.min(a.end - 1, a.start + deltaMs)\n next = { ...a, start: nextStart }\n }\n assignments.set(id, next)\n commitAndPublish()\n }\n\n function getAssignment(id: string): Assignment | undefined {\n return assignments.get(id)\n }\n\n function getAssignments(): Assignment[] {\n return [...assignments.values()]\n }\n\n function setRules(next: readonly AvailabilityRule[]): void {\n rules = [...next]\n commitAndPublish()\n }\n\n function getRules(): AvailabilityRule[] {\n return [...rules]\n }\n\n function getConflicts(): Conflict[] {\n return [...conflicts]\n }\n\n function setScale(next: CalendarScale): void {\n if (next === scale) return\n scale = next\n commitAndPublish()\n }\n\n function getScale(): CalendarScale {\n return scale\n }\n\n function getState(): CalendarState {\n return {\n resources: [...resources.values()],\n assignments: [...assignments.values()],\n conflicts: [...conflicts],\n }\n }\n\n function clear(): void {\n resources.clear()\n assignments.clear()\n rules = []\n conflicts = []\n publish()\n }\n\n publish() // initial empty publish so subscribers see a stable shape\n\n return {\n id: engineId,\n addResource,\n removeResource,\n updateResource,\n getResource,\n getResources,\n addAssignment,\n updateAssignment,\n removeAssignment,\n moveAssignment,\n resizeAssignment,\n getAssignment,\n getAssignments,\n setRules,\n getRules,\n getConflicts,\n setScale,\n getScale,\n getState,\n subscribe,\n clear,\n }\n}\n"],"mappings":"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","names":["DAY_MS"]}
@@ -0,0 +1,258 @@
1
+ // src/runtime/Node.ts
2
+ function createNode(id, value) {
3
+ return { id, value, version: 0 };
4
+ }
5
+
6
+ // src/runtime/Store.ts
7
+ var Store = class {
8
+ nodes = /* @__PURE__ */ new Map();
9
+ has(path) {
10
+ return this.nodes.has(path);
11
+ }
12
+ get(path) {
13
+ return this.nodes.get(path)?.value;
14
+ }
15
+ getNode(path) {
16
+ return this.nodes.get(path);
17
+ }
18
+ set(path, value) {
19
+ let node = this.nodes.get(path);
20
+ if (node) {
21
+ node.value = value;
22
+ node.version++;
23
+ return node;
24
+ }
25
+ node = createNode(path, value);
26
+ this.nodes.set(path, node);
27
+ return node;
28
+ }
29
+ delete(path) {
30
+ return this.nodes.delete(path);
31
+ }
32
+ paths() {
33
+ return this.nodes.keys();
34
+ }
35
+ size() {
36
+ return this.nodes.size;
37
+ }
38
+ };
39
+
40
+ // src/runtime/SubscriptionEngine.ts
41
+ var SubscriptionEngine = class {
42
+ listeners = /* @__PURE__ */ new Map();
43
+ subscribe(path, cb) {
44
+ let set = this.listeners.get(path);
45
+ if (!set) {
46
+ set = /* @__PURE__ */ new Set();
47
+ this.listeners.set(path, set);
48
+ }
49
+ set.add(cb);
50
+ return () => {
51
+ set.delete(cb);
52
+ if (set.size === 0) {
53
+ this.listeners.delete(path);
54
+ }
55
+ };
56
+ }
57
+ notify(path, value) {
58
+ const set = this.listeners.get(path);
59
+ if (!set) return;
60
+ for (const cb of set) {
61
+ cb(value);
62
+ }
63
+ }
64
+ notifyMany(changes) {
65
+ for (const [path, value] of changes) {
66
+ this.notify(path, value);
67
+ }
68
+ }
69
+ hasListeners(path) {
70
+ const set = this.listeners.get(path);
71
+ return set !== void 0 && set.size > 0;
72
+ }
73
+ clear() {
74
+ this.listeners.clear();
75
+ }
76
+ };
77
+
78
+ // src/runtime/Batch.ts
79
+ var Batch = class {
80
+ depth = 0;
81
+ pending = /* @__PURE__ */ new Map();
82
+ get isActive() {
83
+ return this.depth > 0;
84
+ }
85
+ start() {
86
+ this.depth++;
87
+ }
88
+ end() {
89
+ this.depth--;
90
+ if (this.depth > 0) return null;
91
+ if (this.pending.size === 0) return null;
92
+ const changes = new Map(this.pending);
93
+ this.pending.clear();
94
+ return changes;
95
+ }
96
+ enqueue(path, value) {
97
+ this.pending.set(path, value);
98
+ }
99
+ };
100
+
101
+ // src/runtime/Transaction.ts
102
+ var Transaction = class {
103
+ active = false;
104
+ staged = [];
105
+ get isActive() {
106
+ return this.active;
107
+ }
108
+ start() {
109
+ if (this.active) {
110
+ throw new Error("SkyGraph: nested transactions are not supported");
111
+ }
112
+ this.active = true;
113
+ this.staged = [];
114
+ }
115
+ /** STAGE: record a write without mutating the store */
116
+ stage(path, value, prevValue) {
117
+ this.staged.push({ path, value, prevValue });
118
+ }
119
+ /**
120
+ * RESOLVE + COMMIT: drop unchanged values, apply the rest to store.
121
+ * Returns the map of actually changed paths and their new values.
122
+ */
123
+ commit(store) {
124
+ const changes = /* @__PURE__ */ new Map();
125
+ for (const write of this.staged) {
126
+ if (Object.is(write.value, write.prevValue)) continue;
127
+ store.set(write.path, write.value);
128
+ changes.set(write.path, write.value);
129
+ }
130
+ this.active = false;
131
+ this.staged = [];
132
+ return changes;
133
+ }
134
+ getStaged() {
135
+ return this.staged;
136
+ }
137
+ complete() {
138
+ this.active = false;
139
+ this.staged = [];
140
+ }
141
+ rollback() {
142
+ this.active = false;
143
+ this.staged = [];
144
+ }
145
+ };
146
+
147
+ // src/runtime/CycleDetector.ts
148
+ var CycleDetector = class {
149
+ graph = /* @__PURE__ */ new Map();
150
+ addEdge(from, to) {
151
+ let deps = this.graph.get(from);
152
+ if (!deps) {
153
+ deps = /* @__PURE__ */ new Set();
154
+ this.graph.set(from, deps);
155
+ }
156
+ deps.add(to);
157
+ if (this.hasCycle(to, /* @__PURE__ */ new Set([from]))) {
158
+ deps.delete(to);
159
+ if (deps.size === 0) this.graph.delete(from);
160
+ throw new Error(
161
+ `SkyGraph: circular dependency detected: ${from} -> ${to}`
162
+ );
163
+ }
164
+ }
165
+ removeNode(id) {
166
+ this.graph.delete(id);
167
+ for (const deps of this.graph.values()) {
168
+ deps.delete(id);
169
+ }
170
+ }
171
+ getDependents(id) {
172
+ const targets = this.graph.get(id);
173
+ return targets ? [...targets] : [];
174
+ }
175
+ hasCycle(node, visited) {
176
+ if (visited.has(node)) return true;
177
+ const deps = this.graph.get(node);
178
+ if (!deps) return false;
179
+ visited.add(node);
180
+ for (const dep of deps) {
181
+ if (this.hasCycle(dep, new Set(visited))) return true;
182
+ }
183
+ return false;
184
+ }
185
+ };
186
+
187
+ // src/runtime/ComputedEngine.ts
188
+ var ComputedEngine = class {
189
+ computedNodes = /* @__PURE__ */ new Map();
190
+ cycleDetector = new CycleDetector();
191
+ register(target, deps, fn) {
192
+ if (this.computedNodes.has(target)) {
193
+ this.remove(target);
194
+ }
195
+ for (const dep of deps) {
196
+ this.cycleDetector.addEdge(dep, target);
197
+ }
198
+ this.computedNodes.set(target, { target, deps, fn });
199
+ }
200
+ remove(target) {
201
+ this.computedNodes.delete(target);
202
+ this.cycleDetector.removeNode(target);
203
+ }
204
+ isComputed(path) {
205
+ return this.computedNodes.has(path);
206
+ }
207
+ /**
208
+ * Like propagate, but returns both new and old values for middleware support.
209
+ * Does NOT write to the store — the caller handles that via dispatchWrite.
210
+ */
211
+ propagateRaw(changedPaths, store) {
212
+ const result = /* @__PURE__ */ new Map();
213
+ const visited = /* @__PURE__ */ new Set();
214
+ const queue = [...changedPaths];
215
+ while (queue.length > 0) {
216
+ const path = queue.shift();
217
+ if (visited.has(path)) continue;
218
+ visited.add(path);
219
+ const dependents = this.cycleDetector.getDependents(path);
220
+ for (const depTarget of dependents) {
221
+ const computed = this.computedNodes.get(depTarget);
222
+ if (!computed) continue;
223
+ const depValues = computed.deps.map((d) => {
224
+ const pending = result.get(d);
225
+ return pending ? pending.value : store.get(d);
226
+ });
227
+ const newValue = computed.fn(...depValues);
228
+ const oldValue = result.has(depTarget) ? result.get(depTarget).oldValue : store.get(depTarget);
229
+ if (!Object.is(newValue, oldValue)) {
230
+ result.set(depTarget, { value: newValue, oldValue });
231
+ queue.push(depTarget);
232
+ }
233
+ }
234
+ }
235
+ return result;
236
+ }
237
+ /**
238
+ * Initialize all computed values based on current store state.
239
+ */
240
+ initializeAll(store) {
241
+ for (const computed of this.computedNodes.values()) {
242
+ const depValues = computed.deps.map((d) => store.get(d));
243
+ const value = computed.fn(...depValues);
244
+ store.set(computed.target, value);
245
+ }
246
+ }
247
+ };
248
+
249
+ export {
250
+ createNode,
251
+ Store,
252
+ SubscriptionEngine,
253
+ Batch,
254
+ Transaction,
255
+ CycleDetector,
256
+ ComputedEngine
257
+ };
258
+ //# sourceMappingURL=chunk-GEMALROQ.js.map