@hasna/calendar 0.1.6 → 0.1.8

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.
@@ -1,2 +1,3 @@
1
+ #!/usr/bin/env bun
1
2
  export {};
2
3
  //# sourceMappingURL=index.d.ts.map
package/dist/cli/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env bun
1
2
  // @bun
2
3
  var __create = Object.create;
3
4
  var __getProtoOf = Object.getPrototypeOf;
@@ -3265,9 +3266,47 @@ function rowToEvent(row) {
3265
3266
  updated_at: row.updated_at
3266
3267
  };
3267
3268
  }
3269
+ var ISO_DATE_TIME_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$/;
3270
+ function parseEventTimestamp(value) {
3271
+ const match = ISO_DATE_TIME_RE.exec(value);
3272
+ if (!match) {
3273
+ throw new RangeError("Event start_at and end_at must be valid ISO 8601 date-time strings");
3274
+ }
3275
+ const year = Number(match[1]);
3276
+ const month = Number(match[2]);
3277
+ const day = Number(match[3]);
3278
+ const hour = Number(match[4]);
3279
+ const minute = Number(match[5]);
3280
+ const second = Number(match[6]);
3281
+ const offset = match[7];
3282
+ const maxDay = month >= 1 && month <= 12 ? new Date(Date.UTC(year, month, 0)).getUTCDate() : 0;
3283
+ if (month < 1 || month > 12 || day < 1 || day > maxDay || hour > 23 || minute > 59 || second > 59) {
3284
+ throw new RangeError("Event start_at and end_at must be valid ISO 8601 date-time strings");
3285
+ }
3286
+ if (offset !== "Z") {
3287
+ const offsetHour = Number(offset.slice(1, 3));
3288
+ const offsetMinute = Number(offset.slice(4, 6));
3289
+ if (offsetHour > 23 || offsetMinute > 59) {
3290
+ throw new RangeError("Event start_at and end_at must be valid ISO 8601 date-time strings");
3291
+ }
3292
+ }
3293
+ const timestamp = Date.parse(value);
3294
+ if (!Number.isFinite(timestamp)) {
3295
+ throw new RangeError("Event start_at and end_at must be valid ISO 8601 date-time strings");
3296
+ }
3297
+ return timestamp;
3298
+ }
3299
+ function assertEventEndsAfterStart(startAt, endAt) {
3300
+ const start = parseEventTimestamp(startAt);
3301
+ const end = parseEventTimestamp(endAt);
3302
+ if (end <= start) {
3303
+ throw new RangeError("Event end_at must be after start_at");
3304
+ }
3305
+ }
3268
3306
  function createEvent2(input, db) {
3269
3307
  db = db || getDatabase();
3270
3308
  const id = crypto.randomUUID().slice(0, 8);
3309
+ assertEventEndsAfterStart(input.start_at, input.end_at);
3271
3310
  db.run(`INSERT INTO events (id, calendar_id, org_id, title, description, location, start_at, end_at, all_day, timezone, status, busy_type, visibility, recurrence_rule, recurrence_exception_dates, source_task_id, created_by, metadata)
3272
3311
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.calendar_id, input.org_id, input.title, input.description || null, input.location || null, input.start_at, input.end_at, input.all_day ? 1 : 0, input.timezone || "UTC", input.status || "confirmed", input.busy_type || "busy", input.visibility || "default", input.recurrence_rule || null, input.recurrence_exception_dates ? JSON.stringify(input.recurrence_exception_dates) : null, input.source_task_id || null, input.created_by || null, JSON.stringify(input.metadata || {})]);
3273
3312
  return getEvent(id, db);
@@ -3320,6 +3359,9 @@ function updateEvent(id, input, db) {
3320
3359
  const existing = getEvent(id, db);
3321
3360
  if (!existing)
3322
3361
  throw new NotFoundError("Event", id);
3362
+ const startAt = input.start_at ?? existing.start_at;
3363
+ const endAt = input.end_at ?? existing.end_at;
3364
+ assertEventEndsAfterStart(startAt, endAt);
3323
3365
  db.run(`UPDATE events SET title = ?, description = ?, location = ?, start_at = ?, end_at = ?, all_day = ?, timezone = ?, status = ?, busy_type = ?, visibility = ?, recurrence_rule = ?, recurrence_exception_dates = ?, source_task_id = ?, metadata = ?, updated_at = datetime('now') WHERE id = ?`, [input.title ?? existing.title, input.description !== undefined ? input.description : existing.description, input.location !== undefined ? input.location : existing.location, input.start_at ?? existing.start_at, input.end_at ?? existing.end_at, input.all_day !== undefined ? input.all_day ? 1 : 0 : existing.all_day ? 1 : 0, input.timezone ?? existing.timezone, input.status ?? existing.status, input.busy_type ?? existing.busy_type, input.visibility ?? existing.visibility, input.recurrence_rule !== undefined ? input.recurrence_rule : existing.recurrence_rule, input.recurrence_exception_dates !== undefined ? input.recurrence_exception_dates ? JSON.stringify(input.recurrence_exception_dates) : null : existing.recurrence_exception_dates ? JSON.stringify(existing.recurrence_exception_dates) : null, input.source_task_id !== undefined ? input.source_task_id : existing.source_task_id, JSON.stringify(input.metadata ?? existing.metadata), id]);
3324
3366
  return getEvent(id, db);
3325
3367
  }
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/db/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,KAAK,EAAE,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AA4BnF,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,KAAK,CAWzE;AAED,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,IAAI,CAIhE;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,UAAU,CAAC,MAAM,GAAE,gBAAqB,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,KAAK,EAAE,CAmBhF;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,KAAK,CAWrF;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAI9D;AAID,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,uEAAuE;AACvE,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,cAAc,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,KAAK,EAAE,CAWnH;AAED,0FAA0F;AAC1F,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,cAAc,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,KAAK,EAAE,CAarH;AAID,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,KAAK,EAAE,CAWlF"}
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/db/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,KAAK,EAAE,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AA0EnF,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,KAAK,CAYzE;AAED,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,IAAI,CAIhE;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,UAAU,CAAC,MAAM,GAAE,gBAAqB,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,KAAK,EAAE,CAmBhF;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,KAAK,CAcrF;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAI9D;AAID,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,uEAAuE;AACvE,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,cAAc,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,KAAK,EAAE,CAWnH;AAED,0FAA0F;AAC1F,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,cAAc,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,KAAK,EAAE,CAarH;AAID,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,QAAQ,GAAG,KAAK,EAAE,CAWlF"}
package/dist/index.js CHANGED
@@ -543,9 +543,47 @@ function rowToEvent(row) {
543
543
  updated_at: row.updated_at
544
544
  };
545
545
  }
546
+ var ISO_DATE_TIME_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$/;
547
+ function parseEventTimestamp(value) {
548
+ const match = ISO_DATE_TIME_RE.exec(value);
549
+ if (!match) {
550
+ throw new RangeError("Event start_at and end_at must be valid ISO 8601 date-time strings");
551
+ }
552
+ const year = Number(match[1]);
553
+ const month = Number(match[2]);
554
+ const day = Number(match[3]);
555
+ const hour = Number(match[4]);
556
+ const minute = Number(match[5]);
557
+ const second = Number(match[6]);
558
+ const offset = match[7];
559
+ const maxDay = month >= 1 && month <= 12 ? new Date(Date.UTC(year, month, 0)).getUTCDate() : 0;
560
+ if (month < 1 || month > 12 || day < 1 || day > maxDay || hour > 23 || minute > 59 || second > 59) {
561
+ throw new RangeError("Event start_at and end_at must be valid ISO 8601 date-time strings");
562
+ }
563
+ if (offset !== "Z") {
564
+ const offsetHour = Number(offset.slice(1, 3));
565
+ const offsetMinute = Number(offset.slice(4, 6));
566
+ if (offsetHour > 23 || offsetMinute > 59) {
567
+ throw new RangeError("Event start_at and end_at must be valid ISO 8601 date-time strings");
568
+ }
569
+ }
570
+ const timestamp = Date.parse(value);
571
+ if (!Number.isFinite(timestamp)) {
572
+ throw new RangeError("Event start_at and end_at must be valid ISO 8601 date-time strings");
573
+ }
574
+ return timestamp;
575
+ }
576
+ function assertEventEndsAfterStart(startAt, endAt) {
577
+ const start = parseEventTimestamp(startAt);
578
+ const end = parseEventTimestamp(endAt);
579
+ if (end <= start) {
580
+ throw new RangeError("Event end_at must be after start_at");
581
+ }
582
+ }
546
583
  function createEvent(input, db) {
547
584
  db = db || getDatabase();
548
585
  const id = crypto.randomUUID().slice(0, 8);
586
+ assertEventEndsAfterStart(input.start_at, input.end_at);
549
587
  db.run(`INSERT INTO events (id, calendar_id, org_id, title, description, location, start_at, end_at, all_day, timezone, status, busy_type, visibility, recurrence_rule, recurrence_exception_dates, source_task_id, created_by, metadata)
550
588
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.calendar_id, input.org_id, input.title, input.description || null, input.location || null, input.start_at, input.end_at, input.all_day ? 1 : 0, input.timezone || "UTC", input.status || "confirmed", input.busy_type || "busy", input.visibility || "default", input.recurrence_rule || null, input.recurrence_exception_dates ? JSON.stringify(input.recurrence_exception_dates) : null, input.source_task_id || null, input.created_by || null, JSON.stringify(input.metadata || {})]);
551
589
  return getEvent(id, db);
@@ -598,6 +636,9 @@ function updateEvent(id, input, db) {
598
636
  const existing = getEvent(id, db);
599
637
  if (!existing)
600
638
  throw new NotFoundError("Event", id);
639
+ const startAt = input.start_at ?? existing.start_at;
640
+ const endAt = input.end_at ?? existing.end_at;
641
+ assertEventEndsAfterStart(startAt, endAt);
601
642
  db.run(`UPDATE events SET title = ?, description = ?, location = ?, start_at = ?, end_at = ?, all_day = ?, timezone = ?, status = ?, busy_type = ?, visibility = ?, recurrence_rule = ?, recurrence_exception_dates = ?, source_task_id = ?, metadata = ?, updated_at = datetime('now') WHERE id = ?`, [input.title ?? existing.title, input.description !== undefined ? input.description : existing.description, input.location !== undefined ? input.location : existing.location, input.start_at ?? existing.start_at, input.end_at ?? existing.end_at, input.all_day !== undefined ? input.all_day ? 1 : 0 : existing.all_day ? 1 : 0, input.timezone ?? existing.timezone, input.status ?? existing.status, input.busy_type ?? existing.busy_type, input.visibility ?? existing.visibility, input.recurrence_rule !== undefined ? input.recurrence_rule : existing.recurrence_rule, input.recurrence_exception_dates !== undefined ? input.recurrence_exception_dates ? JSON.stringify(input.recurrence_exception_dates) : null : existing.recurrence_exception_dates ? JSON.stringify(existing.recurrence_exception_dates) : null, input.source_task_id !== undefined ? input.source_task_id : existing.source_task_id, JSON.stringify(input.metadata ?? existing.metadata), id]);
602
643
  return getEvent(id, db);
603
644
  }
package/dist/mcp/index.js CHANGED
@@ -4448,9 +4448,46 @@ function rowToEvent(row) {
4448
4448
  updated_at: row.updated_at
4449
4449
  };
4450
4450
  }
4451
+ function parseEventTimestamp(value) {
4452
+ const match = ISO_DATE_TIME_RE.exec(value);
4453
+ if (!match) {
4454
+ throw new RangeError("Event start_at and end_at must be valid ISO 8601 date-time strings");
4455
+ }
4456
+ const year = Number(match[1]);
4457
+ const month = Number(match[2]);
4458
+ const day = Number(match[3]);
4459
+ const hour = Number(match[4]);
4460
+ const minute = Number(match[5]);
4461
+ const second = Number(match[6]);
4462
+ const offset = match[7];
4463
+ const maxDay = month >= 1 && month <= 12 ? new Date(Date.UTC(year, month, 0)).getUTCDate() : 0;
4464
+ if (month < 1 || month > 12 || day < 1 || day > maxDay || hour > 23 || minute > 59 || second > 59) {
4465
+ throw new RangeError("Event start_at and end_at must be valid ISO 8601 date-time strings");
4466
+ }
4467
+ if (offset !== "Z") {
4468
+ const offsetHour = Number(offset.slice(1, 3));
4469
+ const offsetMinute = Number(offset.slice(4, 6));
4470
+ if (offsetHour > 23 || offsetMinute > 59) {
4471
+ throw new RangeError("Event start_at and end_at must be valid ISO 8601 date-time strings");
4472
+ }
4473
+ }
4474
+ const timestamp = Date.parse(value);
4475
+ if (!Number.isFinite(timestamp)) {
4476
+ throw new RangeError("Event start_at and end_at must be valid ISO 8601 date-time strings");
4477
+ }
4478
+ return timestamp;
4479
+ }
4480
+ function assertEventEndsAfterStart(startAt, endAt) {
4481
+ const start = parseEventTimestamp(startAt);
4482
+ const end = parseEventTimestamp(endAt);
4483
+ if (end <= start) {
4484
+ throw new RangeError("Event end_at must be after start_at");
4485
+ }
4486
+ }
4451
4487
  function createEvent(input, db) {
4452
4488
  db = db || getDatabase();
4453
4489
  const id = crypto.randomUUID().slice(0, 8);
4490
+ assertEventEndsAfterStart(input.start_at, input.end_at);
4454
4491
  db.run(`INSERT INTO events (id, calendar_id, org_id, title, description, location, start_at, end_at, all_day, timezone, status, busy_type, visibility, recurrence_rule, recurrence_exception_dates, source_task_id, created_by, metadata)
4455
4492
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.calendar_id, input.org_id, input.title, input.description || null, input.location || null, input.start_at, input.end_at, input.all_day ? 1 : 0, input.timezone || "UTC", input.status || "confirmed", input.busy_type || "busy", input.visibility || "default", input.recurrence_rule || null, input.recurrence_exception_dates ? JSON.stringify(input.recurrence_exception_dates) : null, input.source_task_id || null, input.created_by || null, JSON.stringify(input.metadata || {})]);
4456
4493
  return getEvent(id, db);
@@ -4503,6 +4540,9 @@ function updateEvent(id, input, db) {
4503
4540
  const existing = getEvent(id, db);
4504
4541
  if (!existing)
4505
4542
  throw new NotFoundError("Event", id);
4543
+ const startAt = input.start_at ?? existing.start_at;
4544
+ const endAt = input.end_at ?? existing.end_at;
4545
+ assertEventEndsAfterStart(startAt, endAt);
4506
4546
  db.run(`UPDATE events SET title = ?, description = ?, location = ?, start_at = ?, end_at = ?, all_day = ?, timezone = ?, status = ?, busy_type = ?, visibility = ?, recurrence_rule = ?, recurrence_exception_dates = ?, source_task_id = ?, metadata = ?, updated_at = datetime('now') WHERE id = ?`, [input.title ?? existing.title, input.description !== undefined ? input.description : existing.description, input.location !== undefined ? input.location : existing.location, input.start_at ?? existing.start_at, input.end_at ?? existing.end_at, input.all_day !== undefined ? input.all_day ? 1 : 0 : existing.all_day ? 1 : 0, input.timezone ?? existing.timezone, input.status ?? existing.status, input.busy_type ?? existing.busy_type, input.visibility ?? existing.visibility, input.recurrence_rule !== undefined ? input.recurrence_rule : existing.recurrence_rule, input.recurrence_exception_dates !== undefined ? input.recurrence_exception_dates ? JSON.stringify(input.recurrence_exception_dates) : null : existing.recurrence_exception_dates ? JSON.stringify(existing.recurrence_exception_dates) : null, input.source_task_id !== undefined ? input.source_task_id : existing.source_task_id, JSON.stringify(input.metadata ?? existing.metadata), id]);
4507
4547
  return getEvent(id, db);
4508
4548
  }
@@ -4527,9 +4567,11 @@ function searchEvents(query, orgId, db) {
4527
4567
  ORDER BY e.start_at`).all(query, ...orgId ? [orgId] : []);
4528
4568
  return rows.map(rowToEvent);
4529
4569
  }
4570
+ var ISO_DATE_TIME_RE;
4530
4571
  var init_events = __esm(() => {
4531
4572
  init_database();
4532
4573
  init_types2();
4574
+ ISO_DATE_TIME_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$/;
4533
4575
  });
4534
4576
 
4535
4577
  // src/db/attendees.ts
@@ -21095,9 +21095,46 @@ function rowToEvent(row) {
21095
21095
  updated_at: row.updated_at
21096
21096
  };
21097
21097
  }
21098
+ function parseEventTimestamp(value) {
21099
+ const match = ISO_DATE_TIME_RE.exec(value);
21100
+ if (!match) {
21101
+ throw new RangeError("Event start_at and end_at must be valid ISO 8601 date-time strings");
21102
+ }
21103
+ const year = Number(match[1]);
21104
+ const month = Number(match[2]);
21105
+ const day = Number(match[3]);
21106
+ const hour = Number(match[4]);
21107
+ const minute = Number(match[5]);
21108
+ const second = Number(match[6]);
21109
+ const offset = match[7];
21110
+ const maxDay = month >= 1 && month <= 12 ? new Date(Date.UTC(year, month, 0)).getUTCDate() : 0;
21111
+ if (month < 1 || month > 12 || day < 1 || day > maxDay || hour > 23 || minute > 59 || second > 59) {
21112
+ throw new RangeError("Event start_at and end_at must be valid ISO 8601 date-time strings");
21113
+ }
21114
+ if (offset !== "Z") {
21115
+ const offsetHour = Number(offset.slice(1, 3));
21116
+ const offsetMinute = Number(offset.slice(4, 6));
21117
+ if (offsetHour > 23 || offsetMinute > 59) {
21118
+ throw new RangeError("Event start_at and end_at must be valid ISO 8601 date-time strings");
21119
+ }
21120
+ }
21121
+ const timestamp = Date.parse(value);
21122
+ if (!Number.isFinite(timestamp)) {
21123
+ throw new RangeError("Event start_at and end_at must be valid ISO 8601 date-time strings");
21124
+ }
21125
+ return timestamp;
21126
+ }
21127
+ function assertEventEndsAfterStart(startAt, endAt) {
21128
+ const start = parseEventTimestamp(startAt);
21129
+ const end = parseEventTimestamp(endAt);
21130
+ if (end <= start) {
21131
+ throw new RangeError("Event end_at must be after start_at");
21132
+ }
21133
+ }
21098
21134
  function createEvent(input, db) {
21099
21135
  db = db || getDatabase();
21100
21136
  const id = crypto.randomUUID().slice(0, 8);
21137
+ assertEventEndsAfterStart(input.start_at, input.end_at);
21101
21138
  db.run(`INSERT INTO events (id, calendar_id, org_id, title, description, location, start_at, end_at, all_day, timezone, status, busy_type, visibility, recurrence_rule, recurrence_exception_dates, source_task_id, created_by, metadata)
21102
21139
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.calendar_id, input.org_id, input.title, input.description || null, input.location || null, input.start_at, input.end_at, input.all_day ? 1 : 0, input.timezone || "UTC", input.status || "confirmed", input.busy_type || "busy", input.visibility || "default", input.recurrence_rule || null, input.recurrence_exception_dates ? JSON.stringify(input.recurrence_exception_dates) : null, input.source_task_id || null, input.created_by || null, JSON.stringify(input.metadata || {})]);
21103
21140
  return getEvent(id, db);
@@ -21150,6 +21187,9 @@ function updateEvent(id, input, db) {
21150
21187
  const existing = getEvent(id, db);
21151
21188
  if (!existing)
21152
21189
  throw new NotFoundError("Event", id);
21190
+ const startAt = input.start_at ?? existing.start_at;
21191
+ const endAt = input.end_at ?? existing.end_at;
21192
+ assertEventEndsAfterStart(startAt, endAt);
21153
21193
  db.run(`UPDATE events SET title = ?, description = ?, location = ?, start_at = ?, end_at = ?, all_day = ?, timezone = ?, status = ?, busy_type = ?, visibility = ?, recurrence_rule = ?, recurrence_exception_dates = ?, source_task_id = ?, metadata = ?, updated_at = datetime('now') WHERE id = ?`, [input.title ?? existing.title, input.description !== undefined ? input.description : existing.description, input.location !== undefined ? input.location : existing.location, input.start_at ?? existing.start_at, input.end_at ?? existing.end_at, input.all_day !== undefined ? input.all_day ? 1 : 0 : existing.all_day ? 1 : 0, input.timezone ?? existing.timezone, input.status ?? existing.status, input.busy_type ?? existing.busy_type, input.visibility ?? existing.visibility, input.recurrence_rule !== undefined ? input.recurrence_rule : existing.recurrence_rule, input.recurrence_exception_dates !== undefined ? input.recurrence_exception_dates ? JSON.stringify(input.recurrence_exception_dates) : null : existing.recurrence_exception_dates ? JSON.stringify(existing.recurrence_exception_dates) : null, input.source_task_id !== undefined ? input.source_task_id : existing.source_task_id, JSON.stringify(input.metadata ?? existing.metadata), id]);
21154
21194
  return getEvent(id, db);
21155
21195
  }
@@ -21174,9 +21214,11 @@ function searchEvents(query, orgId, db) {
21174
21214
  ORDER BY e.start_at`).all(query, ...orgId ? [orgId] : []);
21175
21215
  return rows.map(rowToEvent);
21176
21216
  }
21217
+ var ISO_DATE_TIME_RE;
21177
21218
  var init_events = __esm(() => {
21178
21219
  init_database();
21179
21220
  init_types3();
21221
+ ISO_DATE_TIME_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$/;
21180
21222
  });
21181
21223
 
21182
21224
  // src/db/attendees.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/calendar",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Universal calendar management for AI coding agents - CLI + MCP server + interactive TUI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -61,7 +61,7 @@
61
61
  "license": "Apache-2.0",
62
62
  "dependencies": {
63
63
  "@hasna/cloud": "0.1.28",
64
- "@hasna/events": "^0.1.6",
64
+ "@hasna/events": "^0.1.7",
65
65
  "@modelcontextprotocol/sdk": "^1.12.1",
66
66
  "chalk": "^5.4.1",
67
67
  "commander": "^13.1.0",