@rubytech/create-maxy-code 0.1.140 → 0.1.142
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/package.json +1 -1
- package/payload/platform/config/brand.json +1 -1
- package/payload/platform/neo4j/schema.cypher +42 -67
- package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.d.ts.map +1 -1
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.js +14 -8
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.js.map +1 -1
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-export.d.ts +1 -1
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-export.d.ts.map +1 -1
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-export.js +10 -7
- package/payload/platform/plugins/contacts/mcp/dist/tools/contact-export.js.map +1 -1
- package/payload/platform/plugins/docs/references/internals.md +1 -1
- package/payload/platform/plugins/docs/references/neo4j.md +3 -3
- package/payload/platform/plugins/docs/references/plugins-guide.md +1 -0
- package/payload/platform/plugins/email/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/email/PLUGIN.md +3 -7
- package/payload/platform/plugins/email/mcp/dist/index.js +7 -46
- package/payload/platform/plugins/email/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/credentials.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/credentials.js +4 -15
- package/payload/platform/plugins/email/mcp/dist/lib/credentials.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/graph.d.ts +51 -43
- package/payload/platform/plugins/email/mcp/dist/lib/graph.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/graph.js +223 -229
- package/payload/platform/plugins/email/mcp/dist/lib/graph.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.js +7 -4
- package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-graph-query.d.ts +14 -4
- package/payload/platform/plugins/email/mcp/dist/tools/email-graph-query.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-graph-query.js +69 -84
- package/payload/platform/plugins/email/mcp/dist/tools/email-graph-query.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-reply.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-reply.js +49 -64
- package/payload/platform/plugins/email/mcp/dist/tools/email-reply.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-setup.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-setup.js +1 -2
- package/payload/platform/plugins/email/mcp/dist/tools/email-setup.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.d.ts +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.js +3 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.d.ts +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.js +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/x-dm.d.ts +3 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/x-dm.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/x-dm.js +101 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/x-dm.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.js +7 -4
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts +59 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js +322 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js.map +1 -1
- package/payload/platform/plugins/memory/references/schema-base.md +4 -4
- package/payload/platform/plugins/memory/skills/conversation-archive/SKILL.md +2 -1
- package/payload/platform/plugins/memory/skills/document-ingest/SKILL.md +1 -0
- package/payload/platform/plugins/notion-import/skills/notion-import/SKILL.md +13 -9
- package/payload/platform/plugins/notion-import/skills/notion-import/references/databases.md +11 -9
- package/payload/platform/plugins/notion-import/skills/notion-import/references/page-tree.md +12 -12
- package/payload/platform/plugins/scheduling/PLUGIN.md +18 -0
- package/payload/platform/plugins/scheduling/mcp/dist/index.js +93 -0
- package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/ics-graph-ingest.d.ts +80 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/ics-graph-ingest.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/ics-graph-ingest.js +395 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/ics-graph-ingest.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/ics.d.ts +16 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/ics.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/ics.js +111 -2
- package/payload/platform/plugins/scheduling/mcp/dist/lib/ics.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-archive-ics.d.ts +20 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-archive-ics.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-archive-ics.js +63 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-archive-ics.js.map +1 -0
- package/payload/platform/plugins/x-import/.claude-plugin/plugin.json +8 -0
- package/payload/platform/plugins/x-import/PLUGIN.md +33 -0
- package/payload/platform/plugins/x-import/references/archive-shape.md +124 -0
- package/payload/platform/plugins/x-import/references/transcript-shape.md +121 -0
- package/payload/platform/plugins/x-import/skills/x-import/SKILL.md +123 -0
- package/payload/platform/templates/specialists/agents/database-operator.md +6 -2
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graph-ingest path for ICS calendar archives.
|
|
3
|
+
*
|
|
4
|
+
* .ics file ──▶ parseICS ──▶ (dry-run preview │ commit writes)
|
|
5
|
+
*
|
|
6
|
+
* Writes one `:Meeting` per VEVENT, anchors it to the importing `:AdminUser`
|
|
7
|
+
* via `[:IMPORTED]`, resolves attendees to `:Person` by lowercased email, and
|
|
8
|
+
* MERGEs `[:ATTENDED]` edges with role + partstat.
|
|
9
|
+
*
|
|
10
|
+
* Cross-account collision rule: `Person.email` is globally unique in the
|
|
11
|
+
* schema. When an attendee email already exists under a different accountId,
|
|
12
|
+
* the import skips the attendance edge (no edge written, the foreign Person
|
|
13
|
+
* is untouched) and increments `skippedCrossAccountPerson`. The Meeting still
|
|
14
|
+
* lands, anchored to the importer's AdminUser. This preserves absolute brand
|
|
15
|
+
* / account isolation while keeping the import as a whole non-fatal.
|
|
16
|
+
*/
|
|
17
|
+
import { Session } from "neo4j-driver";
|
|
18
|
+
import { type ParsedAttendee } from "./ics.js";
|
|
19
|
+
export interface IcsIngestParams {
|
|
20
|
+
/** The .ics file content (already read by the tool wrapper). */
|
|
21
|
+
fileContent: string;
|
|
22
|
+
/** Path-cleansed filename used for diagnostics + `:Meeting.sourceFilename`. */
|
|
23
|
+
filename: string;
|
|
24
|
+
mode: "dry-run" | "commit";
|
|
25
|
+
accountId: string;
|
|
26
|
+
/** The importing AdminUser. Required — we anchor every Meeting to them. */
|
|
27
|
+
userId: string;
|
|
28
|
+
sessionId?: string;
|
|
29
|
+
session: Session;
|
|
30
|
+
}
|
|
31
|
+
export interface AttendeePreviewEntry {
|
|
32
|
+
address: string;
|
|
33
|
+
displayName?: string;
|
|
34
|
+
status: "existing-same-account" | "existing-cross-account" | "will-create" | "non-mailto";
|
|
35
|
+
/** Set when status is existing-*. */
|
|
36
|
+
existingDisplayName?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface IcsIngestDryRunResult {
|
|
39
|
+
mode: "dry-run";
|
|
40
|
+
filename: string;
|
|
41
|
+
importId: string;
|
|
42
|
+
events: number;
|
|
43
|
+
validMeetings: number;
|
|
44
|
+
skippedNoUid: number;
|
|
45
|
+
skippedNoStartTime: number;
|
|
46
|
+
/** Aggregate attendee preview. */
|
|
47
|
+
preview: {
|
|
48
|
+
attendees: number;
|
|
49
|
+
organizers: number;
|
|
50
|
+
nonMailtoAttendees: number;
|
|
51
|
+
existingSameAccount: number;
|
|
52
|
+
existingCrossAccount: number;
|
|
53
|
+
willCreate: number;
|
|
54
|
+
};
|
|
55
|
+
/** Up to 20 representative attendees, classified — surfaces what a commit would do. */
|
|
56
|
+
sampleAttendees: AttendeePreviewEntry[];
|
|
57
|
+
parseErrors: string[];
|
|
58
|
+
ms: number;
|
|
59
|
+
}
|
|
60
|
+
export interface IcsIngestCommitResult {
|
|
61
|
+
mode: "commit";
|
|
62
|
+
filename: string;
|
|
63
|
+
importId: string;
|
|
64
|
+
events: number;
|
|
65
|
+
meetingsCreated: number;
|
|
66
|
+
meetingsUpdated: number;
|
|
67
|
+
peopleCreated: number;
|
|
68
|
+
existingPersonsLinked: number;
|
|
69
|
+
attendees: number;
|
|
70
|
+
organizers: number;
|
|
71
|
+
skippedNoUid: number;
|
|
72
|
+
skippedNoStartTime: number;
|
|
73
|
+
skippedCrossAccountPerson: number;
|
|
74
|
+
parseErrors: string[];
|
|
75
|
+
ms: number;
|
|
76
|
+
}
|
|
77
|
+
export type IcsIngestResult = IcsIngestDryRunResult | IcsIngestCommitResult;
|
|
78
|
+
export declare function ingestIcs(params: IcsIngestParams): Promise<IcsIngestResult>;
|
|
79
|
+
export type { ParsedAttendee };
|
|
80
|
+
//# sourceMappingURL=ics-graph-ingest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ics-graph-ingest.d.ts","sourceRoot":"","sources":["../../src/lib/ics-graph-ingest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAA8B,KAAK,cAAc,EAAE,MAAM,UAAU,CAAC;AAM3E,MAAM,WAAW,eAAe;IAC9B,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC;IACpB,+EAA+E;IAC/E,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EACF,uBAAuB,GACvB,wBAAwB,GACxB,aAAa,GACb,YAAY,CAAC;IACjB,qCAAqC;IACrC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kCAAkC;IAClC,OAAO,EAAE;QACP,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,mBAAmB,EAAE,MAAM,CAAC;QAC5B,oBAAoB,EAAE,MAAM,CAAC;QAC7B,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,uFAAuF;IACvF,eAAe,EAAE,oBAAoB,EAAE,CAAC;IACxC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,yBAAyB,EAAE,MAAM,CAAC;IAClC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,MAAM,eAAe,GAAG,qBAAqB,GAAG,qBAAqB,CAAC;AAM5E,wBAAsB,SAAS,CAC7B,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,eAAe,CAAC,CA8D1B;AAkYD,YAAY,EAAE,cAAc,EAAE,CAAC"}
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graph-ingest path for ICS calendar archives.
|
|
3
|
+
*
|
|
4
|
+
* .ics file ──▶ parseICS ──▶ (dry-run preview │ commit writes)
|
|
5
|
+
*
|
|
6
|
+
* Writes one `:Meeting` per VEVENT, anchors it to the importing `:AdminUser`
|
|
7
|
+
* via `[:IMPORTED]`, resolves attendees to `:Person` by lowercased email, and
|
|
8
|
+
* MERGEs `[:ATTENDED]` edges with role + partstat.
|
|
9
|
+
*
|
|
10
|
+
* Cross-account collision rule: `Person.email` is globally unique in the
|
|
11
|
+
* schema. When an attendee email already exists under a different accountId,
|
|
12
|
+
* the import skips the attendance edge (no edge written, the foreign Person
|
|
13
|
+
* is untouched) and increments `skippedCrossAccountPerson`. The Meeting still
|
|
14
|
+
* lands, anchored to the importer's AdminUser. This preserves absolute brand
|
|
15
|
+
* / account isolation while keeping the import as a whole non-fatal.
|
|
16
|
+
*/
|
|
17
|
+
import { randomUUID } from "node:crypto";
|
|
18
|
+
import { parseICS } from "./ics.js";
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Entry point
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
export async function ingestIcs(params) {
|
|
23
|
+
const start = Date.now();
|
|
24
|
+
const importId = randomUUID();
|
|
25
|
+
const { fileContent, filename, mode, accountId, userId, sessionId, session } = params;
|
|
26
|
+
const parsed = parseICS(fileContent);
|
|
27
|
+
// Partition events into valid / skipped buckets. parseICS already rejects
|
|
28
|
+
// VEVENTs without DTSTART (they land in parseErrors); the parser stops short
|
|
29
|
+
// of inferring skip-reason buckets, so we classify here too. UID is required
|
|
30
|
+
// for the (accountId, uid) composite key.
|
|
31
|
+
const valid = [];
|
|
32
|
+
let skippedNoUid = 0;
|
|
33
|
+
let skippedNoStartTime = 0;
|
|
34
|
+
for (const ev of parsed.events) {
|
|
35
|
+
if (!ev.dtstart) {
|
|
36
|
+
skippedNoStartTime++;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (!ev.uid) {
|
|
40
|
+
skippedNoUid++;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
valid.push(ev);
|
|
44
|
+
}
|
|
45
|
+
// Events the parser already rejected for missing DTSTART surface via
|
|
46
|
+
// parseErrors. Count those too so the operator-visible signal matches the
|
|
47
|
+
// file's actual content.
|
|
48
|
+
for (const err of parsed.parseErrors) {
|
|
49
|
+
if (err.includes("missing required DTSTART"))
|
|
50
|
+
skippedNoStartTime++;
|
|
51
|
+
}
|
|
52
|
+
if (mode === "dry-run") {
|
|
53
|
+
return dryRunPreview({
|
|
54
|
+
session,
|
|
55
|
+
accountId,
|
|
56
|
+
valid,
|
|
57
|
+
filename,
|
|
58
|
+
importId,
|
|
59
|
+
skippedNoUid,
|
|
60
|
+
skippedNoStartTime,
|
|
61
|
+
parseErrors: parsed.parseErrors,
|
|
62
|
+
eventsParsed: parsed.events.length,
|
|
63
|
+
start,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return commit({
|
|
67
|
+
session,
|
|
68
|
+
accountId,
|
|
69
|
+
userId,
|
|
70
|
+
sessionId,
|
|
71
|
+
valid,
|
|
72
|
+
filename,
|
|
73
|
+
importId,
|
|
74
|
+
skippedNoUid,
|
|
75
|
+
skippedNoStartTime,
|
|
76
|
+
parseErrors: parsed.parseErrors,
|
|
77
|
+
eventsParsed: parsed.events.length,
|
|
78
|
+
start,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
async function dryRunPreview(ctx) {
|
|
82
|
+
// Flatten attendees across valid events. Track raw counts so the preview
|
|
83
|
+
// numbers match what the commit phase would emit.
|
|
84
|
+
let attendees = 0;
|
|
85
|
+
let organizers = 0;
|
|
86
|
+
let nonMailto = 0;
|
|
87
|
+
const addressOrder = []; // dedup-preserving insertion order
|
|
88
|
+
const addressSeen = new Set();
|
|
89
|
+
const addressDisplayName = new Map();
|
|
90
|
+
for (const ev of ctx.valid) {
|
|
91
|
+
for (const a of ev.attendees) {
|
|
92
|
+
if (a.role === "organizer")
|
|
93
|
+
organizers++;
|
|
94
|
+
else
|
|
95
|
+
attendees++;
|
|
96
|
+
if (!a.address) {
|
|
97
|
+
nonMailto++;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (!addressSeen.has(a.address)) {
|
|
101
|
+
addressSeen.add(a.address);
|
|
102
|
+
addressOrder.push(a.address);
|
|
103
|
+
}
|
|
104
|
+
if (a.displayName && !addressDisplayName.has(a.address)) {
|
|
105
|
+
addressDisplayName.set(a.address, a.displayName);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// One round-trip to classify every unique address against the live graph.
|
|
110
|
+
const existing = new Map();
|
|
111
|
+
if (addressOrder.length > 0) {
|
|
112
|
+
const result = await ctx.session.run(`
|
|
113
|
+
UNWIND $emails AS email
|
|
114
|
+
MATCH (p:Person {email: email})
|
|
115
|
+
RETURN p.email AS email, p.accountId AS accountId, p.displayName AS displayName
|
|
116
|
+
`, { emails: addressOrder });
|
|
117
|
+
for (const rec of result.records) {
|
|
118
|
+
existing.set(rec.get("email"), {
|
|
119
|
+
accountId: rec.get("accountId") ?? null,
|
|
120
|
+
displayName: rec.get("displayName") ?? null,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
let existingSameAccount = 0;
|
|
125
|
+
let existingCrossAccount = 0;
|
|
126
|
+
let willCreate = 0;
|
|
127
|
+
const sample = [];
|
|
128
|
+
const SAMPLE_CAP = 20;
|
|
129
|
+
for (const address of addressOrder) {
|
|
130
|
+
const hit = existing.get(address);
|
|
131
|
+
let status;
|
|
132
|
+
let existingDisplayName;
|
|
133
|
+
if (!hit) {
|
|
134
|
+
willCreate++;
|
|
135
|
+
status = "will-create";
|
|
136
|
+
}
|
|
137
|
+
else if (hit.accountId === ctx.accountId) {
|
|
138
|
+
existingSameAccount++;
|
|
139
|
+
status = "existing-same-account";
|
|
140
|
+
if (hit.displayName)
|
|
141
|
+
existingDisplayName = hit.displayName;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
existingCrossAccount++;
|
|
145
|
+
status = "existing-cross-account";
|
|
146
|
+
if (hit.displayName)
|
|
147
|
+
existingDisplayName = hit.displayName;
|
|
148
|
+
}
|
|
149
|
+
if (sample.length < SAMPLE_CAP) {
|
|
150
|
+
const entry = { address, status };
|
|
151
|
+
const dn = addressDisplayName.get(address);
|
|
152
|
+
if (dn)
|
|
153
|
+
entry.displayName = dn;
|
|
154
|
+
if (existingDisplayName)
|
|
155
|
+
entry.existingDisplayName = existingDisplayName;
|
|
156
|
+
sample.push(entry);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// Non-mailto attendees are interesting enough to sample at least one.
|
|
160
|
+
if (nonMailto > 0 && sample.length < SAMPLE_CAP) {
|
|
161
|
+
for (const ev of ctx.valid) {
|
|
162
|
+
for (const a of ev.attendees) {
|
|
163
|
+
if (a.address)
|
|
164
|
+
continue;
|
|
165
|
+
sample.push({
|
|
166
|
+
address: a.rawValue,
|
|
167
|
+
status: "non-mailto",
|
|
168
|
+
...(a.displayName ? { displayName: a.displayName } : {}),
|
|
169
|
+
});
|
|
170
|
+
if (sample.length >= SAMPLE_CAP)
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
if (sample.length >= SAMPLE_CAP)
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const ms = Date.now() - ctx.start;
|
|
178
|
+
process.stderr.write(`[ics-ingest] importId=${ctx.importId} filename=${ctx.filename} mode=dry-run ` +
|
|
179
|
+
`events=${ctx.eventsParsed} meetings=${ctx.valid.length} ` +
|
|
180
|
+
`attendees=${attendees} organizers=${organizers} nonMailto=${nonMailto} ` +
|
|
181
|
+
`existingSame=${existingSameAccount} existingCross=${existingCrossAccount} ` +
|
|
182
|
+
`willCreate=${willCreate} skippedNoUid=${ctx.skippedNoUid} ` +
|
|
183
|
+
`skippedNoStartTime=${ctx.skippedNoStartTime} ms=${ms}\n`);
|
|
184
|
+
return {
|
|
185
|
+
mode: "dry-run",
|
|
186
|
+
filename: ctx.filename,
|
|
187
|
+
importId: ctx.importId,
|
|
188
|
+
events: ctx.eventsParsed,
|
|
189
|
+
validMeetings: ctx.valid.length,
|
|
190
|
+
skippedNoUid: ctx.skippedNoUid,
|
|
191
|
+
skippedNoStartTime: ctx.skippedNoStartTime,
|
|
192
|
+
preview: {
|
|
193
|
+
attendees,
|
|
194
|
+
organizers,
|
|
195
|
+
nonMailtoAttendees: nonMailto,
|
|
196
|
+
existingSameAccount,
|
|
197
|
+
existingCrossAccount,
|
|
198
|
+
willCreate,
|
|
199
|
+
},
|
|
200
|
+
sampleAttendees: sample,
|
|
201
|
+
parseErrors: ctx.parseErrors,
|
|
202
|
+
ms,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
async function commit(ctx) {
|
|
206
|
+
// Resolve the importing AdminUser once. We refuse to start the commit if
|
|
207
|
+
// the userId doesn't resolve — anchoring every Meeting to a real AdminUser
|
|
208
|
+
// is what keeps :Meeting from being an orphan.
|
|
209
|
+
const adminCheck = await ctx.session.run(`MATCH (a:AdminUser {userId: $userId}) RETURN elementId(a) AS id LIMIT 1`, { userId: ctx.userId });
|
|
210
|
+
if (adminCheck.records.length === 0) {
|
|
211
|
+
throw new Error(`ics-ingest: no :AdminUser found for userId=${ctx.userId}. ` +
|
|
212
|
+
`The importer must exist in the graph before ingesting an .ics archive.`);
|
|
213
|
+
}
|
|
214
|
+
let meetingsCreated = 0;
|
|
215
|
+
let meetingsUpdated = 0;
|
|
216
|
+
let peopleCreated = 0;
|
|
217
|
+
let existingPersonsLinked = 0;
|
|
218
|
+
let skippedCrossAccountPerson = 0;
|
|
219
|
+
let attendees = 0;
|
|
220
|
+
let organizers = 0;
|
|
221
|
+
// One transaction per meeting keeps the blast radius small — if attendee
|
|
222
|
+
// resolution fails on event #4000, events 1..3999 are already durably
|
|
223
|
+
// committed. Calendar archives are long flat lists, not interlinked.
|
|
224
|
+
for (const ev of ctx.valid) {
|
|
225
|
+
const meetingProps = {
|
|
226
|
+
uid: ev.uid,
|
|
227
|
+
title: ev.summary,
|
|
228
|
+
startsAt: ev.dtstart,
|
|
229
|
+
endsAt: ev.dtend ?? null,
|
|
230
|
+
location: ev.location ?? null,
|
|
231
|
+
description: ev.description ?? null,
|
|
232
|
+
isAllDay: ev.isAllDay,
|
|
233
|
+
sourceFilename: ctx.filename,
|
|
234
|
+
accountId: ctx.accountId,
|
|
235
|
+
sessionId: ctx.sessionId ?? null,
|
|
236
|
+
};
|
|
237
|
+
const meetingResult = await ctx.session.executeWrite(async (tx) => {
|
|
238
|
+
const r = await tx.run(`
|
|
239
|
+
MATCH (a:AdminUser {userId: $userId})
|
|
240
|
+
MERGE (m:Meeting {accountId: $accountId, uid: $props.uid})
|
|
241
|
+
ON CREATE SET
|
|
242
|
+
m.title = $props.title,
|
|
243
|
+
m.startsAt = datetime($props.startsAt),
|
|
244
|
+
m.endsAt = CASE WHEN $props.endsAt IS NULL THEN null ELSE datetime($props.endsAt) END,
|
|
245
|
+
m.location = $props.location,
|
|
246
|
+
m.description = $props.description,
|
|
247
|
+
m.isAllDay = $props.isAllDay,
|
|
248
|
+
m.source = 'ics',
|
|
249
|
+
m.sourceFilename = $props.sourceFilename,
|
|
250
|
+
m.scope = 'admin',
|
|
251
|
+
m.createdAt = datetime(),
|
|
252
|
+
m.updatedAt = datetime(),
|
|
253
|
+
m.createdByAgent = 'ics-ingest',
|
|
254
|
+
m.createdBySource = 'ics-ingest',
|
|
255
|
+
m.createdBySession = $sessionId,
|
|
256
|
+
m._created = true
|
|
257
|
+
ON MATCH SET
|
|
258
|
+
m.title = $props.title,
|
|
259
|
+
m.startsAt = datetime($props.startsAt),
|
|
260
|
+
m.endsAt = CASE WHEN $props.endsAt IS NULL THEN null ELSE datetime($props.endsAt) END,
|
|
261
|
+
m.location = $props.location,
|
|
262
|
+
m.description = $props.description,
|
|
263
|
+
m.isAllDay = $props.isAllDay,
|
|
264
|
+
m.sourceFilename = $props.sourceFilename,
|
|
265
|
+
m.updatedAt = datetime(),
|
|
266
|
+
m._created = false
|
|
267
|
+
WITH a, m, m._created AS created
|
|
268
|
+
REMOVE m._created
|
|
269
|
+
MERGE (a)-[imp:IMPORTED]->(m)
|
|
270
|
+
ON CREATE SET imp.createdAt = datetime(), imp.source = 'ics-ingest'
|
|
271
|
+
RETURN created, elementId(m) AS meetingId
|
|
272
|
+
`, {
|
|
273
|
+
userId: ctx.userId,
|
|
274
|
+
accountId: ctx.accountId,
|
|
275
|
+
props: meetingProps,
|
|
276
|
+
sessionId: ctx.sessionId ?? null,
|
|
277
|
+
});
|
|
278
|
+
const created = r.records[0].get("created");
|
|
279
|
+
const meetingId = r.records[0].get("meetingId");
|
|
280
|
+
return { created, meetingId };
|
|
281
|
+
});
|
|
282
|
+
if (meetingResult.created)
|
|
283
|
+
meetingsCreated++;
|
|
284
|
+
else
|
|
285
|
+
meetingsUpdated++;
|
|
286
|
+
// Attendee + organizer resolution. We separate organizers from attendees
|
|
287
|
+
// only for counting; the edge written is :ATTENDED in both cases, with the
|
|
288
|
+
// role property carrying the distinction.
|
|
289
|
+
for (const att of ev.attendees) {
|
|
290
|
+
if (att.role === "organizer")
|
|
291
|
+
organizers++;
|
|
292
|
+
else
|
|
293
|
+
attendees++;
|
|
294
|
+
if (!att.address)
|
|
295
|
+
continue; // non-mailto: counted but no graph effect
|
|
296
|
+
const personResult = await ctx.session.executeWrite(async (tx) => {
|
|
297
|
+
// Pre-flight existence check so we never silently create an :ATTENDED
|
|
298
|
+
// edge from a foreign-accountId Person.
|
|
299
|
+
const lookup = await tx.run(`MATCH (p:Person {email: $email}) RETURN p.accountId AS accountId LIMIT 1`, { email: att.address });
|
|
300
|
+
const existingAccountId = lookup.records[0]?.get("accountId");
|
|
301
|
+
if (existingAccountId && existingAccountId !== ctx.accountId) {
|
|
302
|
+
return { outcome: "cross-account-skip" };
|
|
303
|
+
}
|
|
304
|
+
if (existingAccountId === ctx.accountId) {
|
|
305
|
+
// Same-account Person: link without overwriting their identity.
|
|
306
|
+
await tx.run(`
|
|
307
|
+
MATCH (p:Person {email: $email})
|
|
308
|
+
MATCH (m:Meeting) WHERE elementId(m) = $meetingId
|
|
309
|
+
MERGE (p)-[r:ATTENDED]->(m)
|
|
310
|
+
ON CREATE SET r.role = $role, r.partstat = $partstat,
|
|
311
|
+
r.createdAt = datetime(),
|
|
312
|
+
r.createdBySource = 'ics-ingest'
|
|
313
|
+
ON MATCH SET r.role = $role, r.partstat = $partstat
|
|
314
|
+
FOREACH (_ IN CASE WHEN p.displayName IS NULL AND $displayName IS NOT NULL THEN [1] ELSE [] END |
|
|
315
|
+
SET p.displayName = $displayName
|
|
316
|
+
)
|
|
317
|
+
`, {
|
|
318
|
+
email: att.address,
|
|
319
|
+
meetingId: meetingResult.meetingId,
|
|
320
|
+
role: att.role,
|
|
321
|
+
partstat: att.partstat ?? null,
|
|
322
|
+
displayName: att.displayName ?? null,
|
|
323
|
+
});
|
|
324
|
+
return { outcome: "linked-existing" };
|
|
325
|
+
}
|
|
326
|
+
// Brand new Person stub under the importer's account.
|
|
327
|
+
await tx.run(`
|
|
328
|
+
MATCH (m:Meeting) WHERE elementId(m) = $meetingId
|
|
329
|
+
CREATE (p:Person {
|
|
330
|
+
email: $email,
|
|
331
|
+
accountId: $accountId,
|
|
332
|
+
scope: 'admin',
|
|
333
|
+
source: 'ics-ingest',
|
|
334
|
+
displayName: $displayName,
|
|
335
|
+
createdAt: datetime(),
|
|
336
|
+
createdByAgent: 'ics-ingest',
|
|
337
|
+
createdBySource: 'ics-ingest',
|
|
338
|
+
createdBySession: $sessionId
|
|
339
|
+
})
|
|
340
|
+
CREATE (p)-[:ATTENDED {
|
|
341
|
+
role: $role,
|
|
342
|
+
partstat: $partstat,
|
|
343
|
+
createdAt: datetime(),
|
|
344
|
+
createdBySource: 'ics-ingest'
|
|
345
|
+
}]->(m)
|
|
346
|
+
`, {
|
|
347
|
+
email: att.address,
|
|
348
|
+
accountId: ctx.accountId,
|
|
349
|
+
displayName: att.displayName ?? null,
|
|
350
|
+
sessionId: ctx.sessionId ?? null,
|
|
351
|
+
meetingId: meetingResult.meetingId,
|
|
352
|
+
role: att.role,
|
|
353
|
+
partstat: att.partstat ?? null,
|
|
354
|
+
});
|
|
355
|
+
return { outcome: "person-created" };
|
|
356
|
+
});
|
|
357
|
+
if (personResult.outcome === "cross-account-skip") {
|
|
358
|
+
skippedCrossAccountPerson++;
|
|
359
|
+
}
|
|
360
|
+
else if (personResult.outcome === "linked-existing") {
|
|
361
|
+
existingPersonsLinked++;
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
peopleCreated++;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
const ms = Date.now() - ctx.start;
|
|
369
|
+
process.stderr.write(`[ics-ingest] importId=${ctx.importId} filename=${ctx.filename} mode=commit ` +
|
|
370
|
+
`events=${ctx.eventsParsed} meetings=${ctx.valid.length} ` +
|
|
371
|
+
`meetingsCreated=${meetingsCreated} meetingsUpdated=${meetingsUpdated} ` +
|
|
372
|
+
`peopleCreated=${peopleCreated} existingPersonsLinked=${existingPersonsLinked} ` +
|
|
373
|
+
`attendees=${attendees} organizers=${organizers} ` +
|
|
374
|
+
`skippedCrossAccountPerson=${skippedCrossAccountPerson} ` +
|
|
375
|
+
`skippedNoUid=${ctx.skippedNoUid} skippedNoStartTime=${ctx.skippedNoStartTime} ` +
|
|
376
|
+
`ms=${ms}\n`);
|
|
377
|
+
return {
|
|
378
|
+
mode: "commit",
|
|
379
|
+
filename: ctx.filename,
|
|
380
|
+
importId: ctx.importId,
|
|
381
|
+
events: ctx.eventsParsed,
|
|
382
|
+
meetingsCreated,
|
|
383
|
+
meetingsUpdated,
|
|
384
|
+
peopleCreated,
|
|
385
|
+
existingPersonsLinked,
|
|
386
|
+
attendees,
|
|
387
|
+
organizers,
|
|
388
|
+
skippedNoUid: ctx.skippedNoUid,
|
|
389
|
+
skippedNoStartTime: ctx.skippedNoStartTime,
|
|
390
|
+
skippedCrossAccountPerson,
|
|
391
|
+
parseErrors: ctx.parseErrors,
|
|
392
|
+
ms,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
//# sourceMappingURL=ics-graph-ingest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ics-graph-ingest.js","sourceRoot":"","sources":["../../src/lib/ics-graph-ingest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,QAAQ,EAAyC,MAAM,UAAU,CAAC;AA0E3E,8EAA8E;AAC9E,cAAc;AACd,8EAA8E;AAE9E,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,MAAuB;IAEvB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,MAAM,QAAQ,GAAG,UAAU,EAAE,CAAC;IAC9B,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,GAC1E,MAAM,CAAC;IAET,MAAM,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;IAErC,0EAA0E;IAC1E,6EAA6E;IAC7E,6EAA6E;IAC7E,0CAA0C;IAC1C,MAAM,KAAK,GAAkB,EAAE,CAAC;IAChC,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,kBAAkB,GAAG,CAAC,CAAC;IAC3B,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAC/B,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC;YAChB,kBAAkB,EAAE,CAAC;YACrB,SAAS;QACX,CAAC;QACD,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC;YACZ,YAAY,EAAE,CAAC;YACf,SAAS;QACX,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IACD,qEAAqE;IACrE,0EAA0E;IAC1E,yBAAyB;IACzB,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;QACrC,IAAI,GAAG,CAAC,QAAQ,CAAC,0BAA0B,CAAC;YAAE,kBAAkB,EAAE,CAAC;IACrE,CAAC;IAED,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,aAAa,CAAC;YACnB,OAAO;YACP,SAAS;YACT,KAAK;YACL,QAAQ;YACR,QAAQ;YACR,YAAY;YACZ,kBAAkB;YAClB,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM;YAClC,KAAK;SACN,CAAC,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC;QACZ,OAAO;QACP,SAAS;QACT,MAAM;QACN,SAAS;QACT,KAAK;QACL,QAAQ;QACR,QAAQ;QACR,YAAY;QACZ,kBAAkB;QAClB,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM;QAClC,KAAK;KACN,CAAC,CAAC;AACL,CAAC;AAmBD,KAAK,UAAU,aAAa,CAAC,GAAc;IACzC,yEAAyE;IACzE,kDAAkD;IAClD,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,MAAM,YAAY,GAAa,EAAE,CAAC,CAAC,mCAAmC;IACtE,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IACtC,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAkB,CAAC;IAErD,KAAK,MAAM,EAAE,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;QAC3B,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,SAAS,EAAE,CAAC;YAC7B,IAAI,CAAC,CAAC,IAAI,KAAK,WAAW;gBAAE,UAAU,EAAE,CAAC;;gBACpC,SAAS,EAAE,CAAC;YACjB,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;gBACf,SAAS,EAAE,CAAC;gBACZ,SAAS;YACX,CAAC;YACD,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;gBAChC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;gBAC3B,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;YAC/B,CAAC;YACD,IAAI,CAAC,CAAC,WAAW,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;gBACxD,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC;YACnD,CAAC;QACH,CAAC;IACH,CAAC;IAED,0EAA0E;IAC1E,MAAM,QAAQ,GAAG,IAAI,GAAG,EAGrB,CAAC;IACJ,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,GAAG,CAClC;;;;OAIC,EACD,EAAE,MAAM,EAAE,YAAY,EAAE,CACzB,CAAC;QACF,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACjC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAW,EAAE;gBACvC,SAAS,EAAG,GAAG,CAAC,GAAG,CAAC,WAAW,CAAmB,IAAI,IAAI;gBAC1D,WAAW,EAAG,GAAG,CAAC,GAAG,CAAC,aAAa,CAAmB,IAAI,IAAI;aAC/D,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,IAAI,mBAAmB,GAAG,CAAC,CAAC;IAC5B,IAAI,oBAAoB,GAAG,CAAC,CAAC;IAC7B,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,MAAM,UAAU,GAAG,EAAE,CAAC;IAEtB,KAAK,MAAM,OAAO,IAAI,YAAY,EAAE,CAAC;QACnC,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAClC,IAAI,MAAsC,CAAC;QAC3C,IAAI,mBAAuC,CAAC;QAC5C,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,UAAU,EAAE,CAAC;YACb,MAAM,GAAG,aAAa,CAAC;QACzB,CAAC;aAAM,IAAI,GAAG,CAAC,SAAS,KAAK,GAAG,CAAC,SAAS,EAAE,CAAC;YAC3C,mBAAmB,EAAE,CAAC;YACtB,MAAM,GAAG,uBAAuB,CAAC;YACjC,IAAI,GAAG,CAAC,WAAW;gBAAE,mBAAmB,GAAG,GAAG,CAAC,WAAW,CAAC;QAC7D,CAAC;aAAM,CAAC;YACN,oBAAoB,EAAE,CAAC;YACvB,MAAM,GAAG,wBAAwB,CAAC;YAClC,IAAI,GAAG,CAAC,WAAW;gBAAE,mBAAmB,GAAG,GAAG,CAAC,WAAW,CAAC;QAC7D,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,GAAG,UAAU,EAAE,CAAC;YAC/B,MAAM,KAAK,GAAyB,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;YACxD,MAAM,EAAE,GAAG,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAC3C,IAAI,EAAE;gBAAE,KAAK,CAAC,WAAW,GAAG,EAAE,CAAC;YAC/B,IAAI,mBAAmB;gBAAE,KAAK,CAAC,mBAAmB,GAAG,mBAAmB,CAAC;YACzE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IAED,sEAAsE;IACtE,IAAI,SAAS,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,UAAU,EAAE,CAAC;QAChD,KAAK,MAAM,EAAE,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YAC3B,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,SAAS,EAAE,CAAC;gBAC7B,IAAI,CAAC,CAAC,OAAO;oBAAE,SAAS;gBACxB,MAAM,CAAC,IAAI,CAAC;oBACV,OAAO,EAAE,CAAC,CAAC,QAAQ;oBACnB,MAAM,EAAE,YAAY;oBACpB,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBACzD,CAAC,CAAC;gBACH,IAAI,MAAM,CAAC,MAAM,IAAI,UAAU;oBAAE,MAAM;YACzC,CAAC;YACD,IAAI,MAAM,CAAC,MAAM,IAAI,UAAU;gBAAE,MAAM;QACzC,CAAC;IACH,CAAC;IAED,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC;IAClC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,yBAAyB,GAAG,CAAC,QAAQ,aAAa,GAAG,CAAC,QAAQ,gBAAgB;QAC5E,UAAU,GAAG,CAAC,YAAY,aAAa,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG;QAC1D,aAAa,SAAS,eAAe,UAAU,cAAc,SAAS,GAAG;QACzE,gBAAgB,mBAAmB,kBAAkB,oBAAoB,GAAG;QAC5E,cAAc,UAAU,iBAAiB,GAAG,CAAC,YAAY,GAAG;QAC5D,sBAAsB,GAAG,CAAC,kBAAkB,OAAO,EAAE,IAAI,CAC5D,CAAC;IAEF,OAAO;QACL,IAAI,EAAE,SAAS;QACf,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,MAAM,EAAE,GAAG,CAAC,YAAY;QACxB,aAAa,EAAE,GAAG,CAAC,KAAK,CAAC,MAAM;QAC/B,YAAY,EAAE,GAAG,CAAC,YAAY;QAC9B,kBAAkB,EAAE,GAAG,CAAC,kBAAkB;QAC1C,OAAO,EAAE;YACP,SAAS;YACT,UAAU;YACV,kBAAkB,EAAE,SAAS;YAC7B,mBAAmB;YACnB,oBAAoB;YACpB,UAAU;SACX;QACD,eAAe,EAAE,MAAM;QACvB,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,EAAE;KACH,CAAC;AACJ,CAAC;AAqBD,KAAK,UAAU,MAAM,CAAC,GAAc;IAClC,yEAAyE;IACzE,2EAA2E;IAC3E,+CAA+C;IAC/C,MAAM,UAAU,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,GAAG,CACtC,yEAAyE,EACzE,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CACvB,CAAC;IACF,IAAI,UAAU,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CACb,8CAA8C,GAAG,CAAC,MAAM,IAAI;YAC1D,wEAAwE,CAC3E,CAAC;IACJ,CAAC;IAED,IAAI,eAAe,GAAG,CAAC,CAAC;IACxB,IAAI,eAAe,GAAG,CAAC,CAAC;IACxB,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,IAAI,qBAAqB,GAAG,CAAC,CAAC;IAC9B,IAAI,yBAAyB,GAAG,CAAC,CAAC;IAClC,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,UAAU,GAAG,CAAC,CAAC;IAEnB,yEAAyE;IACzE,sEAAsE;IACtE,qEAAqE;IACrE,KAAK,MAAM,EAAE,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;QAC3B,MAAM,YAAY,GAAG;YACnB,GAAG,EAAE,EAAE,CAAC,GAAa;YACrB,KAAK,EAAE,EAAE,CAAC,OAAO;YACjB,QAAQ,EAAE,EAAE,CAAC,OAAO;YACpB,MAAM,EAAE,EAAE,CAAC,KAAK,IAAI,IAAI;YACxB,QAAQ,EAAE,EAAE,CAAC,QAAQ,IAAI,IAAI;YAC7B,WAAW,EAAE,EAAE,CAAC,WAAW,IAAI,IAAI;YACnC,QAAQ,EAAE,EAAE,CAAC,QAAQ;YACrB,cAAc,EAAE,GAAG,CAAC,QAAQ;YAC5B,SAAS,EAAE,GAAG,CAAC,SAAS;YACxB,SAAS,EAAE,GAAG,CAAC,SAAS,IAAI,IAAI;SACjC,CAAC;QAEF,MAAM,aAAa,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;YAChE,MAAM,CAAC,GAAG,MAAM,EAAE,CAAC,GAAG,CACpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;SAkCC,EACD;gBACE,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,SAAS,EAAE,GAAG,CAAC,SAAS;gBACxB,KAAK,EAAE,YAAY;gBACnB,SAAS,EAAE,GAAG,CAAC,SAAS,IAAI,IAAI;aACjC,CACF,CAAC;YACF,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAY,CAAC;YACvD,MAAM,SAAS,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAW,CAAC;YAC1D,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;QAChC,CAAC,CAAC,CAAC;QAEH,IAAI,aAAa,CAAC,OAAO;YAAE,eAAe,EAAE,CAAC;;YACxC,eAAe,EAAE,CAAC;QAEvB,yEAAyE;QACzE,2EAA2E;QAC3E,0CAA0C;QAC1C,KAAK,MAAM,GAAG,IAAI,EAAE,CAAC,SAAS,EAAE,CAAC;YAC/B,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW;gBAAE,UAAU,EAAE,CAAC;;gBACtC,SAAS,EAAE,CAAC;YACjB,IAAI,CAAC,GAAG,CAAC,OAAO;gBAAE,SAAS,CAAC,0CAA0C;YAEtE,MAAM,YAAY,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;gBAC/D,sEAAsE;gBACtE,wCAAwC;gBACxC,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG,CACzB,0EAA0E,EAC1E,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,CACvB,CAAC;gBACF,MAAM,iBAAiB,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,WAAW,CAG/C,CAAC;gBAEd,IAAI,iBAAiB,IAAI,iBAAiB,KAAK,GAAG,CAAC,SAAS,EAAE,CAAC;oBAC7D,OAAO,EAAE,OAAO,EAAE,oBAA6B,EAAE,CAAC;gBACpD,CAAC;gBAED,IAAI,iBAAiB,KAAK,GAAG,CAAC,SAAS,EAAE,CAAC;oBACxC,gEAAgE;oBAChE,MAAM,EAAE,CAAC,GAAG,CACV;;;;;;;;;;;aAWC,EACD;wBACE,KAAK,EAAE,GAAG,CAAC,OAAO;wBAClB,SAAS,EAAE,aAAa,CAAC,SAAS;wBAClC,IAAI,EAAE,GAAG,CAAC,IAAI;wBACd,QAAQ,EAAE,GAAG,CAAC,QAAQ,IAAI,IAAI;wBAC9B,WAAW,EAAE,GAAG,CAAC,WAAW,IAAI,IAAI;qBACrC,CACF,CAAC;oBACF,OAAO,EAAE,OAAO,EAAE,iBAA0B,EAAE,CAAC;gBACjD,CAAC;gBAED,sDAAsD;gBACtD,MAAM,EAAE,CAAC,GAAG,CACV;;;;;;;;;;;;;;;;;;;WAmBC,EACD;oBACE,KAAK,EAAE,GAAG,CAAC,OAAO;oBAClB,SAAS,EAAE,GAAG,CAAC,SAAS;oBACxB,WAAW,EAAE,GAAG,CAAC,WAAW,IAAI,IAAI;oBACpC,SAAS,EAAE,GAAG,CAAC,SAAS,IAAI,IAAI;oBAChC,SAAS,EAAE,aAAa,CAAC,SAAS;oBAClC,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,QAAQ,EAAE,GAAG,CAAC,QAAQ,IAAI,IAAI;iBAC/B,CACF,CAAC;gBACF,OAAO,EAAE,OAAO,EAAE,gBAAyB,EAAE,CAAC;YAChD,CAAC,CAAC,CAAC;YAEH,IAAI,YAAY,CAAC,OAAO,KAAK,oBAAoB,EAAE,CAAC;gBAClD,yBAAyB,EAAE,CAAC;YAC9B,CAAC;iBAAM,IAAI,YAAY,CAAC,OAAO,KAAK,iBAAiB,EAAE,CAAC;gBACtD,qBAAqB,EAAE,CAAC;YAC1B,CAAC;iBAAM,CAAC;gBACN,aAAa,EAAE,CAAC;YAClB,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC;IAClC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,yBAAyB,GAAG,CAAC,QAAQ,aAAa,GAAG,CAAC,QAAQ,eAAe;QAC3E,UAAU,GAAG,CAAC,YAAY,aAAa,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG;QAC1D,mBAAmB,eAAe,oBAAoB,eAAe,GAAG;QACxE,iBAAiB,aAAa,0BAA0B,qBAAqB,GAAG;QAChF,aAAa,SAAS,eAAe,UAAU,GAAG;QAClD,6BAA6B,yBAAyB,GAAG;QACzD,gBAAgB,GAAG,CAAC,YAAY,uBAAuB,GAAG,CAAC,kBAAkB,GAAG;QAChF,MAAM,EAAE,IAAI,CACf,CAAC;IAEF,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,MAAM,EAAE,GAAG,CAAC,YAAY;QACxB,eAAe;QACf,eAAe;QACf,aAAa;QACb,qBAAqB;QACrB,SAAS;QACT,UAAU;QACV,YAAY,EAAE,GAAG,CAAC,YAAY;QAC9B,kBAAkB,EAAE,GAAG,CAAC,kBAAkB;QAC1C,yBAAyB;QACzB,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,EAAE;KACH,CAAC;AACJ,CAAC"}
|
|
@@ -20,6 +20,18 @@ export interface ICSEvent {
|
|
|
20
20
|
recurrence?: string;
|
|
21
21
|
timezone?: string;
|
|
22
22
|
}
|
|
23
|
+
export type AttendeeRole = "organizer" | "chair" | "req-participant" | "opt-participant" | "non-participant";
|
|
24
|
+
export interface ParsedAttendee {
|
|
25
|
+
/** Lowercased email local+domain from `mailto:` URI; null for non-mailto values (rooms, resources, groups). */
|
|
26
|
+
address: string | null;
|
|
27
|
+
/** CN parameter, unescaped. */
|
|
28
|
+
displayName?: string;
|
|
29
|
+
role: AttendeeRole;
|
|
30
|
+
/** PARTSTAT parameter, lowercased (e.g. "accepted", "declined", "tentative", "needs-action"). */
|
|
31
|
+
partstat?: string;
|
|
32
|
+
/** Raw URI value, kept verbatim for visibility on non-mailto entries. */
|
|
33
|
+
rawValue: string;
|
|
34
|
+
}
|
|
23
35
|
export interface ParsedEvent {
|
|
24
36
|
summary: string;
|
|
25
37
|
dtstart: string;
|
|
@@ -28,6 +40,10 @@ export interface ParsedEvent {
|
|
|
28
40
|
description?: string;
|
|
29
41
|
uid?: string;
|
|
30
42
|
recurrence?: string;
|
|
43
|
+
/** True when DTSTART carries `VALUE=DATE` (RFC 5545 §3.3.4 date-only). */
|
|
44
|
+
isAllDay: boolean;
|
|
45
|
+
/** Every ATTENDEE plus the ORGANIZER if present. Empty array if none. */
|
|
46
|
+
attendees: ParsedAttendee[];
|
|
31
47
|
}
|
|
32
48
|
export interface ParseResult {
|
|
33
49
|
events: ParsedEvent[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ics.d.ts","sourceRoot":"","sources":["../../src/lib/ics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AASH,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"ics.d.ts","sourceRoot":"","sources":["../../src/lib/ics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AASH,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,YAAY,GACpB,WAAW,GACX,OAAO,GACP,iBAAiB,GACjB,iBAAiB,GACjB,iBAAiB,CAAC;AAEtB,MAAM,WAAW,cAAc;IAC7B,+GAA+G;IAC/G,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,+BAA+B;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,YAAY,CAAC;IACnB,iGAAiG;IACjG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,QAAQ,EAAE,OAAO,CAAC;IAClB,yEAAyE;IACzE,SAAS,EAAE,cAAc,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AA6GD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAkCrD;AAmCD,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,MAAM,CA6BjF;AAiFD,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,CAkJrD"}
|