@hraness/peopleblade 0.1.1 → 0.1.2

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.
@@ -0,0 +1,1827 @@
1
+ // @bun
2
+ // src/local/cloud-sync-client.ts
3
+ import { Effect as Effect3 } from "effect";
4
+
5
+ // src/lib/contracts.ts
6
+ import { createHash } from "crypto";
7
+ import { z } from "zod";
8
+ var enrichmentPolicyVersion = "identity-bound-claims-v15";
9
+ var enrichmentPriorityPolicyVersion = "enrichment-value-v9";
10
+ var exactProfileTitleOnlyEvidenceExcerpt = "Title-only result for the exact stored public profile URL; no page text was returned.";
11
+ var sourceLabelSchema = z.enum([
12
+ "apple",
13
+ "beeper",
14
+ "gmail",
15
+ "google",
16
+ "imessage",
17
+ "instagram",
18
+ "linkedin",
19
+ "substack",
20
+ "telegram",
21
+ "whatsapp",
22
+ "x"
23
+ ]);
24
+ var boundedText = (maximum) => z.string().trim().min(1).max(maximum);
25
+ var nullableText = (maximum) => boundedText(maximum).nullable();
26
+ var httpUrlSchema = z.url().max(4096).refine((value) => {
27
+ try {
28
+ const protocol = new URL(value).protocol;
29
+ return protocol === "https:" || protocol === "http:";
30
+ } catch {
31
+ return false;
32
+ }
33
+ }, "URL must use HTTP or HTTPS");
34
+ function isCanonicalBirthday(value) {
35
+ const match = /^(?:(\d{4})-|--)(\d{2})-(\d{2})$/u.exec(value);
36
+ if (match === null)
37
+ return false;
38
+ const year = match[1] === undefined ? 2000 : Number(match[1]);
39
+ const month = Number(match[2]);
40
+ const day = Number(match[3]);
41
+ if (year < 1 || year > 9999 || month < 1 || month > 12 || day < 1)
42
+ return false;
43
+ const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
44
+ const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
45
+ return day <= days[month - 1];
46
+ }
47
+ var birthdaySchema = z.string().refine(isCanonicalBirthday, "Birthday must be a real canonical YYYY-MM-DD or --MM-DD date").nullable();
48
+ var cloudEmailSchema = z.email().max(1024);
49
+ var identityAnchorsSchema = z.discriminatedUnion("state", [
50
+ z.object({
51
+ state: z.literal("available"),
52
+ emails: z.array(cloudEmailSchema).max(100),
53
+ phones: z.array(boundedText(256)).max(100),
54
+ profileUrls: z.array(httpUrlSchema).max(100)
55
+ }).strict(),
56
+ z.object({ state: z.literal("unavailable") }).strict()
57
+ ]);
58
+ var automaticEnrichmentSelectionSchema = z.object({
59
+ version: z.literal(1),
60
+ state: z.enum(["eligible", "blocked"])
61
+ }).strict();
62
+ var unavailableAutomaticEnrichmentSelectionSchema = z.object({
63
+ version: z.literal(0),
64
+ state: z.literal("unavailable")
65
+ }).strict();
66
+ var legacyEnrichmentInputSchema = z.object({
67
+ displayName: boundedText(1024),
68
+ emails: z.array(cloudEmailSchema).max(100),
69
+ phones: z.array(boundedText(256)).max(100),
70
+ organization: nullableText(1024),
71
+ title: nullableText(1024),
72
+ sources: z.array(sourceLabelSchema).max(16),
73
+ providerHandles: z.record(boundedText(128), boundedText(2048)).refine((value) => Object.keys(value).length <= 50, "providerHandles contains too many entries")
74
+ }).strict();
75
+ var enrichmentInputSchema = legacyEnrichmentInputSchema.extend({
76
+ enrichmentInputVersion: z.literal(2),
77
+ identityAnchors: identityAnchorsSchema
78
+ }).strict();
79
+ function enrichmentInputSha256(value) {
80
+ return sha256(canonicalJson(enrichmentInputSchema.parse({
81
+ displayName: value.displayName,
82
+ emails: value.emails,
83
+ phones: value.phones,
84
+ organization: value.organization,
85
+ title: value.title,
86
+ sources: value.sources,
87
+ providerHandles: value.providerHandles,
88
+ enrichmentInputVersion: value.enrichmentInputVersion,
89
+ identityAnchors: value.identityAnchors
90
+ })));
91
+ }
92
+ function legacyEnrichmentInputSha256(value) {
93
+ return sha256(canonicalJson(legacyEnrichmentInputSchema.parse({
94
+ displayName: value.displayName,
95
+ emails: value.emails,
96
+ phones: value.phones,
97
+ organization: value.organization,
98
+ title: value.title,
99
+ sources: value.sources,
100
+ providerHandles: value.providerHandles
101
+ })));
102
+ }
103
+ var legacyV1CloudContactSchema = z.object({
104
+ id: z.string().regex(/^[a-f0-9]{64}$/u),
105
+ localPersonId: z.string().regex(/^[1-9][0-9]{0,18}$/u),
106
+ displayName: boundedText(1024),
107
+ emails: z.array(cloudEmailSchema).max(100),
108
+ phones: z.array(boundedText(256)).max(100),
109
+ organization: nullableText(1024),
110
+ title: nullableText(1024),
111
+ birthday: birthdaySchema,
112
+ sources: z.array(sourceLabelSchema).max(16),
113
+ providerHandles: z.record(boundedText(128), boundedText(2048)).refine((value) => Object.keys(value).length <= 50, "providerHandles contains too many entries"),
114
+ interactionCount: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
115
+ reciprocal: z.boolean(),
116
+ firstInteractionAt: z.iso.datetime({ offset: true }).nullable(),
117
+ lastInteractionAt: z.iso.datetime({ offset: true }).nullable(),
118
+ updatedAt: z.iso.datetime({ offset: true }),
119
+ metadataSha256: z.string().regex(/^[a-f0-9]{64}$/u),
120
+ enrichmentInputSha256: z.string().regex(/^[a-f0-9]{64}$/u)
121
+ }).strict();
122
+ var legacyV0CloudContactSchema = legacyV1CloudContactSchema.omit({ enrichmentInputSha256: true }).strict();
123
+ var legacyV2CloudContactSchema = legacyV1CloudContactSchema.extend({
124
+ enrichmentInputVersion: z.literal(2),
125
+ identityAnchors: identityAnchorsSchema
126
+ }).strict().superRefine((contact, context) => {
127
+ if (contact.identityAnchors.state !== "available") {
128
+ context.addIssue({
129
+ code: "custom",
130
+ path: ["identityAnchors", "state"],
131
+ message: "Current sync contacts require available identity anchors"
132
+ });
133
+ return;
134
+ }
135
+ const emails = new Set(contact.emails);
136
+ const phones = new Set(contact.phones);
137
+ contact.identityAnchors.emails.forEach((email, index) => {
138
+ if (!emails.has(email))
139
+ context.addIssue({
140
+ code: "custom",
141
+ path: ["identityAnchors", "emails", index],
142
+ message: "Identity email anchor must also be an observed email"
143
+ });
144
+ });
145
+ contact.identityAnchors.phones.forEach((phone, index) => {
146
+ if (!phones.has(phone))
147
+ context.addIssue({
148
+ code: "custom",
149
+ path: ["identityAnchors", "phones", index],
150
+ message: "Identity phone anchor must also be an observed phone"
151
+ });
152
+ });
153
+ });
154
+ var cloudContactSchema = legacyV1CloudContactSchema.extend({
155
+ enrichmentInputVersion: z.literal(2),
156
+ identityAnchors: identityAnchorsSchema,
157
+ automaticEnrichmentSelection: automaticEnrichmentSelectionSchema
158
+ }).strict().superRefine((contact, context) => {
159
+ if (contact.identityAnchors.state !== "available") {
160
+ context.addIssue({
161
+ code: "custom",
162
+ path: ["identityAnchors", "state"],
163
+ message: "Current sync contacts require available identity anchors"
164
+ });
165
+ return;
166
+ }
167
+ const emails = new Set(contact.emails);
168
+ const phones = new Set(contact.phones);
169
+ contact.identityAnchors.emails.forEach((email, index) => {
170
+ if (!emails.has(email))
171
+ context.addIssue({
172
+ code: "custom",
173
+ path: ["identityAnchors", "emails", index],
174
+ message: "Identity email anchor must also be an observed email"
175
+ });
176
+ });
177
+ contact.identityAnchors.phones.forEach((phone, index) => {
178
+ if (!phones.has(phone))
179
+ context.addIssue({
180
+ code: "custom",
181
+ path: ["identityAnchors", "phones", index],
182
+ message: "Identity phone anchor must also be an observed phone"
183
+ });
184
+ });
185
+ });
186
+ var quarantinedCloudContactSchema = legacyV1CloudContactSchema.extend({
187
+ enrichmentInputVersion: z.literal(2),
188
+ identityAnchors: z.object({ state: z.literal("unavailable") }).strict(),
189
+ automaticEnrichmentSelection: unavailableAutomaticEnrichmentSelectionSchema
190
+ }).strict();
191
+ var selectionQuarantinedCloudContactSchema = legacyV1CloudContactSchema.extend({
192
+ enrichmentInputVersion: z.literal(2),
193
+ identityAnchors: identityAnchorsSchema,
194
+ automaticEnrichmentSelection: unavailableAutomaticEnrichmentSelectionSchema
195
+ }).strict().superRefine((contact, context) => {
196
+ if (contact.identityAnchors.state !== "available") {
197
+ context.addIssue({
198
+ code: "custom",
199
+ path: ["identityAnchors", "state"],
200
+ message: "Current sync contacts require available identity anchors"
201
+ });
202
+ return;
203
+ }
204
+ const emails = new Set(contact.emails);
205
+ const phones = new Set(contact.phones);
206
+ contact.identityAnchors.emails.forEach((email, index) => {
207
+ if (!emails.has(email))
208
+ context.addIssue({
209
+ code: "custom",
210
+ path: ["identityAnchors", "emails", index],
211
+ message: "Identity email anchor must also be an observed email"
212
+ });
213
+ });
214
+ contact.identityAnchors.phones.forEach((phone, index) => {
215
+ if (!phones.has(phone))
216
+ context.addIssue({
217
+ code: "custom",
218
+ path: ["identityAnchors", "phones", index],
219
+ message: "Identity phone anchor must also be an observed phone"
220
+ });
221
+ });
222
+ });
223
+ var syncStartSchema = z.object({
224
+ contactCount: z.number().int().min(0).max(1e6),
225
+ databaseFingerprint: z.string().regex(/^[a-f0-9]{64}$/u),
226
+ schemaVersion: z.number().int().min(1).max(1000),
227
+ resumeVersion: z.literal(1).optional()
228
+ }).strict();
229
+ var syncPageSchema = z.object({
230
+ snapshotId: z.uuid(),
231
+ ordinal: z.number().int().min(0).max(1e5),
232
+ contacts: z.array(cloudContactSchema).min(1).max(200)
233
+ }).strict();
234
+ function syncContactInputVersion(contact) {
235
+ if ("enrichmentInputVersion" in contact && contact.enrichmentInputVersion === 2)
236
+ return 2;
237
+ return "enrichmentInputSha256" in contact && typeof contact.enrichmentInputSha256 === "string" ? 1 : 0;
238
+ }
239
+ function syncSelectionVersion(contact) {
240
+ return "automaticEnrichmentSelection" in contact ? 1 : 0;
241
+ }
242
+ var serverSyncPageInputSchema = z.object({
243
+ snapshotId: z.uuid(),
244
+ ordinal: z.number().int().min(0).max(1e5),
245
+ contacts: z.array(z.union([
246
+ cloudContactSchema,
247
+ legacyV2CloudContactSchema,
248
+ legacyV1CloudContactSchema,
249
+ legacyV0CloudContactSchema
250
+ ])).min(1).max(200)
251
+ }).strict().superRefine((input, context) => {
252
+ const coordinateVersions = new Set(input.contacts.map((contact) => `${syncContactInputVersion(contact)}:${syncSelectionVersion(contact)}`));
253
+ if (coordinateVersions.size !== 1) {
254
+ context.addIssue({
255
+ code: "custom",
256
+ path: ["contacts"],
257
+ message: "Sync page contacts must use one projection version."
258
+ });
259
+ }
260
+ input.contacts.forEach((contact, index) => {
261
+ const version = syncContactInputVersion(contact);
262
+ if (version === 0)
263
+ return;
264
+ const expected = version === 2 ? enrichmentInputSha256(contact) : legacyEnrichmentInputSha256(contact);
265
+ const actual = "enrichmentInputSha256" in contact ? contact.enrichmentInputSha256 : null;
266
+ if (actual !== expected)
267
+ context.addIssue({
268
+ code: "custom",
269
+ path: ["contacts", index, "enrichmentInputSha256"],
270
+ message: `Sync contact enrichment input hash is invalid for version ${version}`
271
+ });
272
+ });
273
+ });
274
+ var serverSyncPageSchema = serverSyncPageInputSchema.transform((input) => {
275
+ const inputVersion = syncContactInputVersion(input.contacts[0]);
276
+ const selectionVersion = syncSelectionVersion(input.contacts[0]);
277
+ return {
278
+ snapshotId: input.snapshotId,
279
+ ordinal: input.ordinal,
280
+ inputVersion,
281
+ selectionVersion,
282
+ legacyPayloadSha256: inputVersion === 2 && selectionVersion === 1 ? null : sha256(canonicalJson(input.contacts)),
283
+ contacts: input.contacts.map((contact) => {
284
+ if (inputVersion === 2 && selectionVersion === 1)
285
+ return cloudContactSchema.parse(contact);
286
+ if (inputVersion === 2)
287
+ return selectionQuarantinedCloudContactSchema.parse({
288
+ ...contact,
289
+ automaticEnrichmentSelection: { version: 0, state: "unavailable" }
290
+ });
291
+ const upgraded = {
292
+ ...contact,
293
+ enrichmentInputVersion: 2,
294
+ identityAnchors: { state: "unavailable" },
295
+ automaticEnrichmentSelection: { version: 0, state: "unavailable" }
296
+ };
297
+ return quarantinedCloudContactSchema.parse({
298
+ ...upgraded,
299
+ enrichmentInputSha256: enrichmentInputSha256(upgraded)
300
+ });
301
+ })
302
+ };
303
+ });
304
+ var syncFinishSchema = z.object({
305
+ snapshotId: z.uuid(),
306
+ pages: z.number().int().min(0).max(1e5),
307
+ contactCount: z.number().int().min(0).max(1e6)
308
+ }).strict();
309
+ var cliEnrichmentContactSchema = z.object({
310
+ localPersonId: z.string().regex(/^[1-9][0-9]{0,18}$/u),
311
+ enrichmentInputVersion: z.literal(2),
312
+ enrichmentInputSha256: z.string().regex(/^[a-f0-9]{64}$/u)
313
+ }).strict();
314
+ var legacyV1CliEnrichmentContactSchema = z.object({
315
+ localPersonId: z.string().regex(/^[1-9][0-9]{0,18}$/u),
316
+ enrichmentInputSha256: z.string().regex(/^[a-f0-9]{64}$/u)
317
+ }).strict();
318
+ var legacyV0CliEnrichmentContactSchema = z.object({
319
+ localPersonId: z.string().regex(/^[1-9][0-9]{0,18}$/u),
320
+ metadataSha256: z.string().regex(/^[a-f0-9]{64}$/u)
321
+ }).strict();
322
+ var serverCliEnrichmentContactSchema = z.union([
323
+ cliEnrichmentContactSchema,
324
+ legacyV1CliEnrichmentContactSchema,
325
+ legacyV0CliEnrichmentContactSchema
326
+ ]);
327
+ function cliEnrichmentInputVersion(contact) {
328
+ if ("enrichmentInputVersion" in contact)
329
+ return 2;
330
+ return "enrichmentInputSha256" in contact ? 1 : 0;
331
+ }
332
+ var serverCliEnrichmentContactsSchema = z.array(serverCliEnrichmentContactSchema).min(1).max(100).refine((contacts) => new Set(contacts.map(cliEnrichmentInputVersion)).size === 1, "A CLI enrichment request must use one hash-coordinate version.");
333
+ var cliEnrichmentPreviewSchema = z.object({
334
+ contacts: z.array(cliEnrichmentContactSchema).min(1).max(100)
335
+ }).strict();
336
+ var cliPrioritizedEnrichmentPreviewSchema = z.object({
337
+ requestedCount: z.number().int().min(1).max(100),
338
+ priorityPolicyVersion: z.literal(enrichmentPriorityPolicyVersion)
339
+ }).strict();
340
+ var serverCliEnrichmentPreviewSchema = z.object({
341
+ contacts: serverCliEnrichmentContactsSchema
342
+ }).strict();
343
+ var cliEnrichmentConfirmationTokenSchema = z.string().regex(/^[A-Za-z0-9_-]{43}$/u);
344
+ var cliEnrichmentStartSchema = z.object({
345
+ previewId: z.uuid(),
346
+ confirmationToken: cliEnrichmentConfirmationTokenSchema,
347
+ contacts: z.array(cliEnrichmentContactSchema).min(1).max(100)
348
+ }).strict();
349
+ var serverCliEnrichmentStartSchema = z.object({
350
+ previewId: z.uuid(),
351
+ confirmationToken: cliEnrichmentConfirmationTokenSchema,
352
+ contacts: serverCliEnrichmentContactsSchema
353
+ }).strict();
354
+ var cliEnrichmentStatusSchema = z.object({
355
+ jobId: z.uuid()
356
+ }).strict();
357
+ var cliEnrichmentRevalidationSchema = z.object({
358
+ limit: z.number().int().min(1).max(100)
359
+ }).strict();
360
+ var cliEnrichmentRevalidationResponseSchema = z.object({
361
+ processed: z.number().int().min(0).max(100),
362
+ accepted: z.number().int().min(0).max(100),
363
+ rejected: z.number().int().min(0).max(100),
364
+ hasMore: z.boolean(),
365
+ targetPolicyVersion: z.literal(enrichmentPolicyVersion),
366
+ validationProfileSha256: z.string().regex(/^[a-f0-9]{64}$/u)
367
+ }).strict().superRefine((value, context) => {
368
+ if (value.accepted + value.rejected !== value.processed) {
369
+ context.addIssue({
370
+ code: "custom",
371
+ message: "Accepted and rejected counts must equal the processed count.",
372
+ path: ["processed"]
373
+ });
374
+ }
375
+ });
376
+ var deviceStartSchema = z.object({
377
+ deviceName: boundedText(128),
378
+ tokenSha256: z.string().regex(/^[a-f0-9]{64}$/u)
379
+ }).strict();
380
+ var deviceCodeSchema = z.string().regex(/^[A-Za-z0-9_-]{64}$/u);
381
+ var deviceStatusSchema = z.object({
382
+ deviceCode: deviceCodeSchema
383
+ }).strict();
384
+ var cloudDeviceIdSchema = z.uuid();
385
+ var cloudDevicesRequestSchema = z.object({}).strict();
386
+ var cloudDeviceRevokeRequestSchema = z.object({
387
+ deviceId: cloudDeviceIdSchema
388
+ }).strict();
389
+ var cloudDeviceSchema = z.object({
390
+ id: cloudDeviceIdSchema,
391
+ name: boundedText(128),
392
+ createdAt: z.iso.datetime({ offset: true }),
393
+ lastSeenAt: z.iso.datetime({ offset: true }),
394
+ revokedAt: z.iso.datetime({ offset: true }).nullable(),
395
+ current: z.boolean()
396
+ }).strict();
397
+ var cloudDevicesResponseSchema = z.object({
398
+ devices: z.array(cloudDeviceSchema).max(200)
399
+ }).strict();
400
+ var cloudDeviceRevokeResponseSchema = z.object({
401
+ deviceId: cloudDeviceIdSchema,
402
+ revokedAt: z.iso.datetime({ offset: true })
403
+ }).strict();
404
+ var enrichmentClaimSchema = z.object({
405
+ field: z.enum(["headline", "organization", "role", "location", "website", "publicEmail"]),
406
+ evidenceIndexes: z.array(z.number().int().min(0).max(4)).min(1).max(5)
407
+ }).strict();
408
+ var enrichmentOutputSchema = z.object({
409
+ identityMatch: z.enum(["confirmed", "possible", "insufficient"]),
410
+ identityEvidenceIndexes: z.array(z.number().int().min(0).max(4)).max(5),
411
+ confidence: z.number().int().min(0).max(100),
412
+ headline: nullableText(1024),
413
+ organization: nullableText(1024),
414
+ role: nullableText(1024),
415
+ location: nullableText(1024),
416
+ website: httpUrlSchema.nullable(),
417
+ publicEmail: z.email().max(1024).nullable(),
418
+ publicEmailEvidenceIndex: z.number().int().min(0).max(4).nullable(),
419
+ notes: z.string().trim().max(2000),
420
+ claims: z.array(enrichmentClaimSchema).max(24)
421
+ }).strict();
422
+ var enrichmentEvidenceReferenceSchema = z.object({
423
+ url: httpUrlSchema,
424
+ title: boundedText(1024),
425
+ excerpt: boundedText(4000),
426
+ kind: z.enum(["public_page", "provider_attestation"]).optional()
427
+ }).strict();
428
+ var enrichmentIdentitySubjectSchema = enrichmentInputSchema.pick({
429
+ displayName: true,
430
+ emails: true,
431
+ phones: true,
432
+ identityAnchors: true,
433
+ organization: true,
434
+ title: true,
435
+ providerHandles: true
436
+ }).superRefine((subject, context) => {
437
+ if (subject.identityAnchors.state !== "available")
438
+ return;
439
+ const emails = new Set(subject.emails);
440
+ const phones = new Set(subject.phones);
441
+ subject.identityAnchors.emails.forEach((email, index) => {
442
+ if (!emails.has(email))
443
+ context.addIssue({
444
+ code: "custom",
445
+ path: ["identityAnchors", "emails", index],
446
+ message: "Identity email anchor must also be an observed email"
447
+ });
448
+ });
449
+ subject.identityAnchors.phones.forEach((phone, index) => {
450
+ if (!phones.has(phone))
451
+ context.addIssue({
452
+ code: "custom",
453
+ path: ["identityAnchors", "phones", index],
454
+ message: "Identity phone anchor must also be an observed phone"
455
+ });
456
+ });
457
+ });
458
+ var enrichmentEvidenceRecordSchema = enrichmentEvidenceReferenceSchema.extend({
459
+ sourceId: boundedText(2048)
460
+ }).strict();
461
+ var publicEvidenceSchema = enrichmentEvidenceReferenceSchema.omit({ kind: true }).strict();
462
+ var manualResearchSchema = enrichmentOutputSchema.extend({
463
+ evidence: z.array(publicEvidenceSchema).max(5)
464
+ }).strict();
465
+ function normalizedEvidenceText(value) {
466
+ return value.normalize("NFKC").toLocaleLowerCase("en-US").replaceAll(/[^\p{L}\p{N}]+/gu, " ").trim();
467
+ }
468
+ var LATIN_IDENTITY_FOLDABLE_MARKS = new Set([
469
+ ..."\u0300\u0301\u0302\u0303\u0304\u0306\u0307\u0308\u0309\u030A\u030B\u030C\u030F\u0311\u031B" + "\u0323\u0324\u0325\u0326\u0327\u0328\u032D\u032E\u0330\u0331"
470
+ ]);
471
+ var HISTORICAL_PROFESSIONAL_MARKERS = new Set([
472
+ "alum",
473
+ "alumni",
474
+ "ceased",
475
+ "departed",
476
+ "down",
477
+ "emeritus",
478
+ "ended",
479
+ "ex",
480
+ "exited",
481
+ "former",
482
+ "formerly",
483
+ "left",
484
+ "past",
485
+ "previous",
486
+ "previously",
487
+ "quit",
488
+ "resigned",
489
+ "retired",
490
+ "sold",
491
+ "stepped",
492
+ "switched",
493
+ "until",
494
+ "was",
495
+ "were"
496
+ ]);
497
+ var CURRENT_PROFESSIONAL_MARKERS = new Set([
498
+ "are",
499
+ "current",
500
+ "currently",
501
+ "is",
502
+ "now",
503
+ "present",
504
+ "presently",
505
+ "serves",
506
+ "works"
507
+ ]);
508
+ var RESIDENCE_LOCATION_MARKERS = new Set([
509
+ "base",
510
+ "based",
511
+ "home",
512
+ "lives",
513
+ "located",
514
+ "location",
515
+ "residence",
516
+ "resident",
517
+ "resides"
518
+ ]);
519
+ var EVENT_LOCATION_MARKERS = new Set([
520
+ "conference",
521
+ "conferences",
522
+ "event",
523
+ "events",
524
+ "keynote",
525
+ "keynotes",
526
+ "meetup",
527
+ "meetups",
528
+ "panel",
529
+ "panels",
530
+ "presented",
531
+ "presenting",
532
+ "presentation",
533
+ "speaker",
534
+ "speakers",
535
+ "speaking",
536
+ "spoke",
537
+ "summit",
538
+ "summits",
539
+ "talk",
540
+ "talks",
541
+ "workshop",
542
+ "workshops"
543
+ ]);
544
+ var NON_PERSON_LOCATION_MARKERS = new Set([
545
+ "branch",
546
+ "branches",
547
+ "business",
548
+ "businesses",
549
+ "company",
550
+ "companies",
551
+ "employer",
552
+ "headquarters",
553
+ "hq",
554
+ "office",
555
+ "offices",
556
+ "organization",
557
+ "organisations",
558
+ "organizations",
559
+ "startup",
560
+ "startups",
561
+ "team",
562
+ "teams",
563
+ "venue",
564
+ "venues"
565
+ ]);
566
+ var HISTORICAL_LOCATION_MARKERS = new Set([
567
+ "ex",
568
+ "former",
569
+ "formerly",
570
+ "once",
571
+ "past",
572
+ "previous",
573
+ "previously",
574
+ "used",
575
+ "was",
576
+ "were"
577
+ ]);
578
+ var LOCATION_COPULA_AND_MODIFIERS = new Set([
579
+ "are",
580
+ "current",
581
+ "currently",
582
+ "is",
583
+ "now",
584
+ "presently"
585
+ ]);
586
+ var PERSON_LOCATION_PRONOUNS = new Set(["he", "she", "they"]);
587
+ var LOCATION_RELATION_GLUE = new Set(["at", "for", "in", "is", "of", "to"]);
588
+ var LOCATION_SUPERSESSION_MARKERS = new Set([
589
+ "departed",
590
+ "left",
591
+ "moved",
592
+ "relocated",
593
+ "relocating",
594
+ "vacated"
595
+ ]);
596
+ var PROFESSIONAL_EVENT_MARKERS = new Set([
597
+ "attended",
598
+ "attending",
599
+ "conference",
600
+ "dinner",
601
+ "event",
602
+ "game",
603
+ "guest",
604
+ "hosted",
605
+ "interviewed",
606
+ "keynote",
607
+ "lunch",
608
+ "meet",
609
+ "meeting",
610
+ "met",
611
+ "panel",
612
+ "presented",
613
+ "presenting",
614
+ "speaker",
615
+ "speaking",
616
+ "spoke",
617
+ "summit",
618
+ "talk",
619
+ "venue",
620
+ "visited",
621
+ "visiting",
622
+ "webinar",
623
+ "workshop"
624
+ ]);
625
+ var NON_EMPLOYMENT_RELATION_MARKERS = new Set([
626
+ "candidate",
627
+ "client",
628
+ "competitor",
629
+ "critic",
630
+ "criticized",
631
+ "customer",
632
+ "grant",
633
+ "grantee",
634
+ "invested",
635
+ "investor",
636
+ "invests",
637
+ "received",
638
+ "sponsor",
639
+ "sponsored",
640
+ "vendor"
641
+ ]);
642
+ var SIMPLE_AFFILIATION_GLUE = new Set([
643
+ "a",
644
+ "an",
645
+ "as",
646
+ "at",
647
+ "current",
648
+ "currently",
649
+ "is",
650
+ "the"
651
+ ]);
652
+ var PROFESSIONAL_ROLE_MARKERS = new Set([
653
+ "chief",
654
+ "cofounder",
655
+ "director",
656
+ "employee",
657
+ "founder",
658
+ "lead",
659
+ "leader",
660
+ "member",
661
+ "owner",
662
+ "partner",
663
+ "president",
664
+ "principal"
665
+ ]);
666
+ var PROFESSIONAL_RELATION_BOUNDARIES = new Set(["and", "but", "then", "while"]);
667
+ var PROFESSIONAL_RELATION_MODIFIERS = new Set(["current", "currently", "now", "presently"]);
668
+ var ORGANIZATION_SUFFIX_DISQUALIFIERS = new Set([
669
+ "alum",
670
+ "alumni",
671
+ "candidate",
672
+ "client",
673
+ "competitor",
674
+ "critic",
675
+ "customer",
676
+ "guest",
677
+ "investee",
678
+ "investor",
679
+ "sponsor",
680
+ "vendor"
681
+ ]);
682
+ var ROLE_SUFFIX_DISQUALIFIERS = new Set([
683
+ "alum",
684
+ "alumni",
685
+ "aspirant",
686
+ "candidate",
687
+ "emeritus",
688
+ "impersonator",
689
+ "nominee",
690
+ "pretender"
691
+ ]);
692
+ var PROFESSIONAL_SUBJECT_PRONOUNS = new Set(["he", "her", "his", "she", "their", "they"]);
693
+ var CURRENT_PROFESSIONAL_CLAUSE_MARKERS = new Set([
694
+ "appointed",
695
+ "are",
696
+ "employed",
697
+ "founded",
698
+ "is",
699
+ "joined",
700
+ "leads",
701
+ "named",
702
+ "owns",
703
+ "serves",
704
+ "serving",
705
+ "works",
706
+ "working"
707
+ ]);
708
+ var ROLE_EMAIL_LOCAL_PARTS = new Set([
709
+ "abuse",
710
+ "accounts",
711
+ "admin",
712
+ "admissions",
713
+ "assistant",
714
+ "billing",
715
+ "careers",
716
+ "community",
717
+ "connect",
718
+ "contact",
719
+ "contactus",
720
+ "customerservice",
721
+ "events",
722
+ "finance",
723
+ "general",
724
+ "hello",
725
+ "help",
726
+ "hr",
727
+ "info",
728
+ "inquiries",
729
+ "investors",
730
+ "jobs",
731
+ "legal",
732
+ "mail",
733
+ "marketing",
734
+ "media",
735
+ "newsletter",
736
+ "noreply",
737
+ "office",
738
+ "operations",
739
+ "ops",
740
+ "partners",
741
+ "postmaster",
742
+ "press",
743
+ "privacy",
744
+ "reception",
745
+ "sales",
746
+ "security",
747
+ "support",
748
+ "team",
749
+ "webmaster"
750
+ ]);
751
+ var PROFILE_IDENTITY_LINK_NEGATIONS = new Set([
752
+ "aint",
753
+ "alleged",
754
+ "allegedly",
755
+ "arent",
756
+ "cannot",
757
+ "cant",
758
+ "couldnt",
759
+ "criticized",
760
+ "denied",
761
+ "denies",
762
+ "different",
763
+ "disassociated",
764
+ "disavowed",
765
+ "disavows",
766
+ "disclaimed",
767
+ "disclaims",
768
+ "didnt",
769
+ "doesnt",
770
+ "dont",
771
+ "fake",
772
+ "fan",
773
+ "fraudulent",
774
+ "hadnt",
775
+ "hasnt",
776
+ "havent",
777
+ "impersonator",
778
+ "incorrect",
779
+ "isnt",
780
+ "lacked",
781
+ "lacking",
782
+ "lacks",
783
+ "mirror",
784
+ "misattributed",
785
+ "mistaken",
786
+ "neither",
787
+ "never",
788
+ "no",
789
+ "nor",
790
+ "not",
791
+ "parody",
792
+ "purported",
793
+ "satire",
794
+ "shant",
795
+ "shouldnt",
796
+ "supposed",
797
+ "unaffiliated",
798
+ "unassociated",
799
+ "unconnected",
800
+ "unofficial",
801
+ "unrelated",
802
+ "wasnt",
803
+ "werent",
804
+ "without",
805
+ "wont",
806
+ "wouldnt",
807
+ "wrong",
808
+ "zero"
809
+ ]);
810
+ var PROFILE_IDENTITY_NEGATED_AUXILIARY_STEMS = new Set([
811
+ "ain",
812
+ "aren",
813
+ "can",
814
+ "couldn",
815
+ "didn",
816
+ "doesn",
817
+ "don",
818
+ "hadn",
819
+ "hasn",
820
+ "haven",
821
+ "isn",
822
+ "mightn",
823
+ "mustn",
824
+ "needn",
825
+ "shan",
826
+ "shouldn",
827
+ "wasn",
828
+ "weren",
829
+ "won",
830
+ "wouldn"
831
+ ]);
832
+ var PROFILE_IDENTITY_CURRENT_PREFIX_TOKENS = new Set([
833
+ "a",
834
+ "an",
835
+ "current",
836
+ "currently",
837
+ "now",
838
+ "present",
839
+ "presently",
840
+ "the"
841
+ ]);
842
+ var PROFILE_IDENTITY_PROFESSIONAL_ROLE_TOKENS = new Set([
843
+ "advisor",
844
+ "analyst",
845
+ "architect",
846
+ "attorney",
847
+ "ceo",
848
+ "cfo",
849
+ "chief",
850
+ "cofounder",
851
+ "consultant",
852
+ "cto",
853
+ "designer",
854
+ "developer",
855
+ "director",
856
+ "employee",
857
+ "engineer",
858
+ "executive",
859
+ "founder",
860
+ "investor",
861
+ "lead",
862
+ "leader",
863
+ "manager",
864
+ "member",
865
+ "officer",
866
+ "owner",
867
+ "partner",
868
+ "president",
869
+ "principal",
870
+ "professor",
871
+ "researcher",
872
+ "scientist"
873
+ ]);
874
+ var PROFILE_IDENTITY_EMPLOYMENT_CONTINUATION_STARTS = new Set([
875
+ "appointed",
876
+ "employed",
877
+ "founded",
878
+ "joined",
879
+ "leads",
880
+ "named",
881
+ "owns",
882
+ "serves",
883
+ "serving",
884
+ "works",
885
+ "working"
886
+ ]);
887
+ var PROFILE_IDENTITY_LOCATION_CONTINUATION_STARTS = new Set([
888
+ "base",
889
+ "based",
890
+ "lives",
891
+ "located",
892
+ "location",
893
+ "residence",
894
+ "resident",
895
+ "resides"
896
+ ]);
897
+ var PROFILE_IDENTITY_SUBJECT_CONTINUATION_DISQUALIFIERS = new Set([
898
+ "another",
899
+ "association",
900
+ "belongs",
901
+ "by",
902
+ "client",
903
+ "colleague",
904
+ "connection",
905
+ "created",
906
+ "discussed",
907
+ "else",
908
+ "follows",
909
+ "impersonator",
910
+ "mentioned",
911
+ "owned",
912
+ "recommends",
913
+ "redirected",
914
+ "redirects",
915
+ "someone",
916
+ "used",
917
+ "uses"
918
+ ]);
919
+ var WEBSITE_ATTRIBUTION_NEGATIONS = new Set([
920
+ "criticized",
921
+ "denied",
922
+ "fake",
923
+ "fraudulent",
924
+ "impersonator",
925
+ "not",
926
+ "unofficial",
927
+ "unrelated"
928
+ ]);
929
+ var TIER_ONE_ROLE_NOUNS = new Set([
930
+ "accountant",
931
+ "administrator",
932
+ "adviser",
933
+ "advisor",
934
+ "analyst",
935
+ "architect",
936
+ "artist",
937
+ "associate",
938
+ "attorney",
939
+ "author",
940
+ "builder",
941
+ "chair",
942
+ "chairman",
943
+ "chairperson",
944
+ "chief",
945
+ "clinician",
946
+ "coach",
947
+ "commissioner",
948
+ "consultant",
949
+ "controller",
950
+ "coordinator",
951
+ "counsel",
952
+ "creator",
953
+ "cto",
954
+ "ceo",
955
+ "cfo",
956
+ "cio",
957
+ "cmo",
958
+ "coo",
959
+ "dean",
960
+ "designer",
961
+ "developer",
962
+ "director",
963
+ "editor",
964
+ "engineer",
965
+ "entrepreneur",
966
+ "evangelist",
967
+ "executive",
968
+ "fellow",
969
+ "founder",
970
+ "head",
971
+ "investor",
972
+ "lead",
973
+ "leader",
974
+ "lecturer",
975
+ "manager",
976
+ "member",
977
+ "officer",
978
+ "operator",
979
+ "owner",
980
+ "partner",
981
+ "physician",
982
+ "president",
983
+ "principal",
984
+ "producer",
985
+ "professor",
986
+ "recruiter",
987
+ "researcher",
988
+ "scientist",
989
+ "specialist",
990
+ "strategist",
991
+ "supervisor",
992
+ "svp",
993
+ "trustee",
994
+ "vp"
995
+ ]);
996
+ var TIER_ONE_ROLE_WORDS = new Set([
997
+ ...TIER_ONE_ROLE_NOUNS,
998
+ "acquisition",
999
+ "ai",
1000
+ "artificial",
1001
+ "assistant",
1002
+ "banking",
1003
+ "board",
1004
+ "business",
1005
+ "clinical",
1006
+ "commercial",
1007
+ "community",
1008
+ "co",
1009
+ "creative",
1010
+ "customer",
1011
+ "data",
1012
+ "design",
1013
+ "development",
1014
+ "digital",
1015
+ "engineering",
1016
+ "executive",
1017
+ "finance",
1018
+ "financial",
1019
+ "founding",
1020
+ "fractional",
1021
+ "general",
1022
+ "global",
1023
+ "growth",
1024
+ "independent",
1025
+ "intelligence",
1026
+ "investment",
1027
+ "i",
1028
+ "ii",
1029
+ "iii",
1030
+ "iv",
1031
+ "junior",
1032
+ "legal",
1033
+ "learning",
1034
+ "machine",
1035
+ "managing",
1036
+ "marketing",
1037
+ "medical",
1038
+ "operations",
1039
+ "people",
1040
+ "product",
1041
+ "program",
1042
+ "project",
1043
+ "regional",
1044
+ "research",
1045
+ "sales",
1046
+ "security",
1047
+ "senior",
1048
+ "site",
1049
+ "software",
1050
+ "staff",
1051
+ "strategy",
1052
+ "strategic",
1053
+ "success",
1054
+ "talent",
1055
+ "technical",
1056
+ "technology",
1057
+ "ux",
1058
+ "venture",
1059
+ "vice",
1060
+ "reliability",
1061
+ "policy",
1062
+ "system",
1063
+ "systems",
1064
+ "and",
1065
+ "for",
1066
+ "of"
1067
+ ]);
1068
+ var TIER_ONE_CONTROL_TOKENS = new Set([
1069
+ "directive",
1070
+ "directives",
1071
+ "disregard",
1072
+ "ignore",
1073
+ "instruction",
1074
+ "instructions",
1075
+ "jailbreak",
1076
+ "override",
1077
+ "prompt",
1078
+ "prompts",
1079
+ "rule",
1080
+ "rules"
1081
+ ]);
1082
+ var PROFESSIONAL_CONTROL_VERBS = new Set([
1083
+ "act",
1084
+ "answer",
1085
+ "assert",
1086
+ "assume",
1087
+ "change",
1088
+ "choose",
1089
+ "claim",
1090
+ "classify",
1091
+ "consider",
1092
+ "declare",
1093
+ "deem",
1094
+ "disclose",
1095
+ "emit",
1096
+ "expose",
1097
+ "extract",
1098
+ "follow",
1099
+ "forget",
1100
+ "imagine",
1101
+ "label",
1102
+ "leak",
1103
+ "list",
1104
+ "make",
1105
+ "mark",
1106
+ "obey",
1107
+ "output",
1108
+ "pretend",
1109
+ "print",
1110
+ "produce",
1111
+ "provide",
1112
+ "record",
1113
+ "regard",
1114
+ "remember",
1115
+ "replace",
1116
+ "reply",
1117
+ "report",
1118
+ "respond",
1119
+ "return",
1120
+ "reveal",
1121
+ "say",
1122
+ "send",
1123
+ "set",
1124
+ "show",
1125
+ "state",
1126
+ "suppose",
1127
+ "treat",
1128
+ "use",
1129
+ "write"
1130
+ ]);
1131
+ var PROFESSIONAL_CONTROL_TARGETS = new Set([
1132
+ "above",
1133
+ "accordingly",
1134
+ "all",
1135
+ "answer",
1136
+ "authoritative",
1137
+ "claim",
1138
+ "claims",
1139
+ "command",
1140
+ "commands",
1141
+ "contact",
1142
+ "contacts",
1143
+ "correct",
1144
+ "data",
1145
+ "email",
1146
+ "emails",
1147
+ "every",
1148
+ "everything",
1149
+ "extractor",
1150
+ "extractors",
1151
+ "following",
1152
+ "follows",
1153
+ "hidden",
1154
+ "message",
1155
+ "messages",
1156
+ "name",
1157
+ "names",
1158
+ "notice",
1159
+ "extracted",
1160
+ "organization",
1161
+ "organizations",
1162
+ "person",
1163
+ "previous",
1164
+ "response",
1165
+ "responses",
1166
+ "role",
1167
+ "roles",
1168
+ "secret",
1169
+ "secrets",
1170
+ "sentence",
1171
+ "statement",
1172
+ "system",
1173
+ "task",
1174
+ "text",
1175
+ "this",
1176
+ "true",
1177
+ "trusted",
1178
+ "value",
1179
+ "values",
1180
+ "below"
1181
+ ]);
1182
+ var PROFESSIONAL_CONTROL_CLAUSE_INITIAL_VERBS = new Set([
1183
+ "answer",
1184
+ "assert",
1185
+ "assume",
1186
+ "change",
1187
+ "choose",
1188
+ "claim",
1189
+ "classify",
1190
+ "consider",
1191
+ "declare",
1192
+ "deem",
1193
+ "disregard",
1194
+ "emit",
1195
+ "extract",
1196
+ "forget",
1197
+ "ignore",
1198
+ "imagine",
1199
+ "label",
1200
+ "list",
1201
+ "make",
1202
+ "mark",
1203
+ "obey",
1204
+ "output",
1205
+ "pretend",
1206
+ "print",
1207
+ "produce",
1208
+ "provide",
1209
+ "record",
1210
+ "regard",
1211
+ "remember",
1212
+ "replace",
1213
+ "reply",
1214
+ "report",
1215
+ "respond",
1216
+ "return",
1217
+ "reveal",
1218
+ "say",
1219
+ "set",
1220
+ "state",
1221
+ "suppose",
1222
+ "treat",
1223
+ "use",
1224
+ "write"
1225
+ ]);
1226
+ var TIER_ONE_ROLE_CONNECTORS = new Set(["and", "for", "of"]);
1227
+ var GENERIC_PROFESSIONAL_DEPARTURE_TARGETS = new Set([
1228
+ "a",
1229
+ "an",
1230
+ "business",
1231
+ "company",
1232
+ "employer",
1233
+ "employment",
1234
+ "firm",
1235
+ "her",
1236
+ "his",
1237
+ "its",
1238
+ "job",
1239
+ "organization",
1240
+ "position",
1241
+ "role",
1242
+ "startup",
1243
+ "team",
1244
+ "that",
1245
+ "the",
1246
+ "their",
1247
+ "this"
1248
+ ]);
1249
+ var IDENTITY_GRADE_CONTENT_FREE_TEXT = new Set([
1250
+ "directory listing",
1251
+ "identity confirmation",
1252
+ "identity verified",
1253
+ "known contact",
1254
+ "known identity",
1255
+ "no additional structured facts",
1256
+ "no structured facts",
1257
+ "profile",
1258
+ "profile page",
1259
+ "professional profile",
1260
+ "public directory listing",
1261
+ "public profile",
1262
+ "public professional profile",
1263
+ "verified",
1264
+ "verified contact",
1265
+ "verified identity",
1266
+ "verified profile",
1267
+ normalizedEvidenceText(exactProfileTitleOnlyEvidenceExcerpt)
1268
+ ]);
1269
+ function canonicalJson(value) {
1270
+ if (value === null || typeof value !== "object")
1271
+ return JSON.stringify(value);
1272
+ if (Array.isArray(value))
1273
+ return `[${value.map(canonicalJson).join(",")}]`;
1274
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`).join(",")}}`;
1275
+ }
1276
+ function sha256(value) {
1277
+ return createHash("sha256").update(value).digest("hex");
1278
+ }
1279
+
1280
+ // src/local/cloud-configuration.ts
1281
+ import { z as z3 } from "zod";
1282
+
1283
+ // src/local/config.ts
1284
+ import { closeSync, constants, existsSync, mkdirSync, openSync, readFileSync, renameSync, writeFileSync } from "fs";
1285
+ import { z as z2 } from "zod";
1286
+
1287
+ // src/local/paths.ts
1288
+ import { homedir, hostname, platform } from "os";
1289
+ import { join } from "path";
1290
+ function peoplebladeDirectory() {
1291
+ if (platform() === "darwin")
1292
+ return join(homedir(), "Library", "Application Support", "PeopleBlade");
1293
+ if (platform() === "win32")
1294
+ return join(process.env.LOCALAPPDATA ?? join(homedir(), "AppData", "Local"), "PeopleBlade");
1295
+ return join(process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share"), "peopleblade");
1296
+ }
1297
+ function peoplebladeConfigPath() {
1298
+ return join(peoplebladeDirectory(), "config.json");
1299
+ }
1300
+
1301
+ // src/local/config.ts
1302
+ var configSchema = z2.object({
1303
+ cloud: z2.object({
1304
+ baseUrl: z2.url().max(2048),
1305
+ deviceId: z2.uuid(),
1306
+ token: z2.string().min(20).max(512)
1307
+ }).strict().nullable()
1308
+ }).strict();
1309
+ function readLocalConfig() {
1310
+ const path = peoplebladeConfigPath();
1311
+ if (!existsSync(path))
1312
+ return { cloud: null };
1313
+ return configSchema.parse(JSON.parse(readFileSync(path, "utf8")));
1314
+ }
1315
+
1316
+ // src/local/cloud-configuration.ts
1317
+ var cloudConfigurationSchema = z3.object({
1318
+ baseUrl: z3.url().max(2048),
1319
+ deviceId: z3.uuid(),
1320
+ token: z3.string().min(20).max(512)
1321
+ }).strict();
1322
+ function cloudConfiguration(override) {
1323
+ const config = override ?? readLocalConfig().cloud;
1324
+ if (config === null)
1325
+ throw new Error("Run `peopleblade cloud signin` first.");
1326
+ return cloudConfigurationSchema.parse(config);
1327
+ }
1328
+
1329
+ // src/local/contact-read-model.ts
1330
+ var canonicalInteractionRollupCtesSql = `
1331
+ canonical_interaction_inputs AS (
1332
+ SELECT member.canonical_person_id AS person_id,
1333
+ metric.provider,
1334
+ CASE WHEN metric.provider = 'beeper'
1335
+ THEN coalesce(metric_realm.service, metric.provider) ELSE metric.provider END AS logical_service,
1336
+ CASE WHEN metric.provider = 'beeper'
1337
+ THEN 'beeper' ELSE 'direct' END AS source_lane,
1338
+ metric.interaction_count,
1339
+ metric.reciprocal,
1340
+ metric.first_interaction_at,
1341
+ metric.last_interaction_at
1342
+ FROM interaction_metrics metric
1343
+ JOIN person_identity_components member ON member.person_id = metric.person_id
1344
+ LEFT JOIN source_realms metric_realm
1345
+ ON metric_realm.authority = metric.provider AND metric_realm.account_key = metric.account_key
1346
+ WHERE (metric_realm.id IS NULL OR metric_realm.active = 1)
1347
+ AND (
1348
+ NOT EXISTS (
1349
+ SELECT 1
1350
+ FROM provider_resources observed_resource
1351
+ WHERE observed_resource.provider = metric.provider
1352
+ AND observed_resource.account_key = metric.account_key
1353
+ AND observed_resource.person_id = metric.person_id
1354
+ )
1355
+ OR EXISTS (
1356
+ SELECT 1
1357
+ FROM provider_resources active_resource
1358
+ LEFT JOIN source_realms active_realm ON active_realm.id = active_resource.source_realm_id
1359
+ WHERE active_resource.provider = metric.provider
1360
+ AND active_resource.account_key = metric.account_key
1361
+ AND active_resource.person_id = metric.person_id
1362
+ AND active_resource.active = 1
1363
+ AND (active_realm.id IS NULL OR active_realm.active = 1)
1364
+ )
1365
+ )
1366
+ ),
1367
+ canonical_interaction_lanes AS (
1368
+ SELECT person_id, logical_service, source_lane,
1369
+ sum(interaction_count) AS interaction_count,
1370
+ max(reciprocal) AS reciprocal,
1371
+ min(first_interaction_at) AS first_interaction_at,
1372
+ max(last_interaction_at) AS last_interaction_at
1373
+ FROM canonical_interaction_inputs
1374
+ GROUP BY person_id, logical_service, source_lane
1375
+ ),
1376
+ canonical_interaction_services AS (
1377
+ SELECT person_id, logical_service,
1378
+ max(interaction_count) AS interaction_count,
1379
+ max(reciprocal) AS reciprocal,
1380
+ min(first_interaction_at) AS first_interaction_at,
1381
+ max(last_interaction_at) AS last_interaction_at
1382
+ FROM canonical_interaction_lanes
1383
+ GROUP BY person_id, logical_service
1384
+ ),
1385
+ canonical_interaction_rollup AS (
1386
+ SELECT person_id,
1387
+ sum(interaction_count) AS interaction_count,
1388
+ max(reciprocal) AS reciprocal,
1389
+ min(first_interaction_at) AS first_interaction_at,
1390
+ max(last_interaction_at) AS last_interaction_at
1391
+ FROM canonical_interaction_services
1392
+ GROUP BY person_id
1393
+ )
1394
+ `;
1395
+
1396
+ // src/local/provider-handles.ts
1397
+ import { createHmac } from "crypto";
1398
+ import { z as z4 } from "zod";
1399
+ var sha256Schema = z4.string().regex(/^[a-f0-9]{64}$/u);
1400
+ var providerHandleCandidateSchema = z4.object({
1401
+ provider: z4.string().min(1).max(64),
1402
+ service: z4.string().min(1).max(64).nullable(),
1403
+ realmExternalIdSha256: sha256Schema.nullable(),
1404
+ bindingAuthSha256: sha256Schema.nullable(),
1405
+ handle: z4.string().min(1)
1406
+ }).strict();
1407
+ function localDatabaseInstanceId(database) {
1408
+ const row = database.query("SELECT instance_id FROM local_state WHERE singleton = 1").get();
1409
+ if (row === null || !/^[a-f0-9]{64}$/u.test(row.instance_id)) {
1410
+ throw new Error("PeopleBlade local database identity is missing.");
1411
+ }
1412
+ return row.instance_id;
1413
+ }
1414
+ function providerHandleKey(candidate, databaseInstanceId) {
1415
+ if (candidate.provider !== "beeper")
1416
+ return candidate.provider;
1417
+ if (candidate.service === null || candidate.realmExternalIdSha256 === null || candidate.bindingAuthSha256 === null)
1418
+ throw new Error("Beeper provider handle is missing its bound source realm.");
1419
+ const realmDiscriminator = createHmac("sha256", databaseInstanceId).update(canonicalJson([
1420
+ "peopleblade-beeper-provider-handle-realm-v1",
1421
+ candidate.bindingAuthSha256,
1422
+ candidate.realmExternalIdSha256
1423
+ ])).digest("hex").slice(0, 48);
1424
+ return `beeper:${candidate.service}:${realmDiscriminator}`;
1425
+ }
1426
+ function parseProviderHandleCandidates(value, databaseInstanceId) {
1427
+ const localSalt = sha256Schema.parse(databaseInstanceId);
1428
+ const parsed = JSON.parse(value);
1429
+ const candidates = z4.array(providerHandleCandidateSchema).max(50).parse(parsed);
1430
+ const handles = {};
1431
+ for (const candidate of candidates) {
1432
+ const key = providerHandleKey(candidate, localSalt);
1433
+ if (Object.hasOwn(handles, key)) {
1434
+ throw new Error("Local provider handles contain a stable-key collision.");
1435
+ }
1436
+ handles[key] = candidate.handle;
1437
+ }
1438
+ return handles;
1439
+ }
1440
+
1441
+ // src/local/projection.ts
1442
+ function parseStringArray(value) {
1443
+ const parsed = JSON.parse(value);
1444
+ if (!Array.isArray(parsed) || !parsed.every((item) => typeof item === "string"))
1445
+ throw new Error("Invalid local contact array.");
1446
+ return parsed;
1447
+ }
1448
+ function iso(value) {
1449
+ if (value === null)
1450
+ return null;
1451
+ const parsed = new Date(value.includes("T") ? value : `${value.replace(" ", "T")}Z`);
1452
+ return Number.isNaN(parsed.valueOf()) ? null : parsed.toISOString();
1453
+ }
1454
+ function localDatabaseFingerprint(database) {
1455
+ return localDatabaseInstanceId(database);
1456
+ }
1457
+ function localSchemaVersion(database) {
1458
+ const row = database.query("SELECT count(*) AS count FROM schema_migrations").get();
1459
+ if (row === null || !Number.isSafeInteger(row.count) || row.count < 1)
1460
+ throw new Error("PeopleBlade schema is not initialized.");
1461
+ return row.count;
1462
+ }
1463
+ function projectCloudContacts(database) {
1464
+ const rows = database.query(`
1465
+ WITH
1466
+ ${canonicalInteractionRollupCtesSql},
1467
+ person_rollup AS (
1468
+ SELECT member.canonical_person_id AS person_id,
1469
+ CASE WHEN count(DISTINCT person.birthday) = 1 THEN min(person.birthday) ELSE NULL END AS birthday,
1470
+ max(person.do_not_contact) AS do_not_contact,
1471
+ max(person.updated_at) AS updated_at
1472
+ FROM person_identity_components member
1473
+ JOIN people person ON person.id = member.person_id
1474
+ GROUP BY member.canonical_person_id
1475
+ ),
1476
+ deduplicated_methods AS (
1477
+ SELECT member.canonical_person_id AS person_id, method.kind, method.normalized_value,
1478
+ max(method.is_primary) AS primary_rank, min(method.id) AS first_id,
1479
+ min(CASE WHEN method.identity_eligible = 1 AND method.confidence = 'exact'
1480
+ AND (method.provider_resource_id IS NULL OR (
1481
+ resource.active = 1 AND (realm.id IS NULL OR realm.active = 1)
1482
+ ))
1483
+ THEN method.id END) AS identity_anchor_id
1484
+ FROM contact_methods method
1485
+ JOIN person_identity_components member ON member.person_id = method.person_id
1486
+ LEFT JOIN provider_resources resource ON resource.id = method.provider_resource_id
1487
+ LEFT JOIN source_realms realm ON realm.id = resource.source_realm_id
1488
+ WHERE method.active = 1 AND method.kind IN ('email', 'phone')
1489
+ GROUP BY member.canonical_person_id, method.kind, method.normalized_value
1490
+ ),
1491
+ ordered_methods AS (
1492
+ SELECT item.person_id, item.kind, method.value,
1493
+ item.identity_anchor_id IS NOT NULL AS identity_anchor
1494
+ FROM deduplicated_methods item
1495
+ JOIN contact_methods method ON method.id = coalesce(item.identity_anchor_id, item.first_id)
1496
+ ORDER BY item.person_id, item.kind, item.identity_anchor_id IS NOT NULL DESC,
1497
+ item.primary_rank DESC, item.first_id
1498
+ ),
1499
+ email_rollup AS (
1500
+ SELECT person_id, json_group_array(value) AS emails_json
1501
+ FROM ordered_methods WHERE kind = 'email' GROUP BY person_id
1502
+ ),
1503
+ phone_rollup AS (
1504
+ SELECT person_id, json_group_array(value) AS phones_json
1505
+ FROM ordered_methods WHERE kind = 'phone' GROUP BY person_id
1506
+ ),
1507
+ identity_email_rollup AS (
1508
+ SELECT person_id, json_group_array(value) AS identity_emails_json
1509
+ FROM ordered_methods WHERE kind = 'email' AND identity_anchor GROUP BY person_id
1510
+ ),
1511
+ identity_phone_rollup AS (
1512
+ SELECT person_id, json_group_array(value) AS identity_phones_json
1513
+ FROM ordered_methods WHERE kind = 'phone' AND identity_anchor GROUP BY person_id
1514
+ ),
1515
+ identity_profile_url_rollup AS (
1516
+ SELECT person_id, json_group_array(profile_url) AS identity_profile_urls_json
1517
+ FROM (
1518
+ SELECT member.canonical_person_id AS person_id,
1519
+ trim(resource.profile_url) AS profile_url, min(resource.id) AS first_id
1520
+ FROM provider_resources resource
1521
+ JOIN person_identity_components member ON member.person_id = resource.person_id
1522
+ LEFT JOIN source_realms realm ON realm.id = resource.source_realm_id
1523
+ WHERE resource.active = 1 AND resource.profile_url_identity_eligible = 1
1524
+ AND resource.profile_url IS NOT NULL AND trim(resource.profile_url) <> ''
1525
+ AND (realm.id IS NULL OR realm.active = 1)
1526
+ GROUP BY member.canonical_person_id, trim(resource.profile_url)
1527
+ ORDER BY member.canonical_person_id, first_id
1528
+ )
1529
+ GROUP BY person_id
1530
+ ),
1531
+ resource_eligibility AS (
1532
+ SELECT member.canonical_person_id AS person_id,
1533
+ max(resource.active) AS has_active_resource
1534
+ FROM provider_resources resource
1535
+ JOIN person_identity_components member ON member.person_id = resource.person_id
1536
+ GROUP BY member.canonical_person_id
1537
+ ),
1538
+ source_values AS (
1539
+ SELECT member.canonical_person_id AS person_id, resource.provider
1540
+ FROM provider_resources resource
1541
+ JOIN person_identity_components member ON member.person_id = resource.person_id
1542
+ WHERE resource.active = 1
1543
+ UNION
1544
+ SELECT metric.person_id, metric.provider
1545
+ FROM canonical_interaction_inputs metric
1546
+ ),
1547
+ source_rollup AS (
1548
+ SELECT person_id, json_group_array(provider) AS sources_json
1549
+ FROM (SELECT person_id, provider FROM source_values ORDER BY person_id, provider)
1550
+ GROUP BY person_id
1551
+ ),
1552
+ handle_choices AS (
1553
+ SELECT person_id, provider, service, realm_external_id_sha256,
1554
+ binding_auth_sha256, resource_id
1555
+ FROM (
1556
+ SELECT member.canonical_person_id AS person_id,
1557
+ resource.provider,
1558
+ CASE WHEN resource.provider='beeper' THEN realm.service ELSE NULL END AS service,
1559
+ CASE WHEN resource.provider='beeper' THEN realm.external_id_sha256 ELSE NULL END AS realm_external_id_sha256,
1560
+ CASE WHEN resource.provider='beeper' THEN incarnation.auth_sha256 ELSE NULL END AS binding_auth_sha256,
1561
+ resource.id AS resource_id,
1562
+ row_number() OVER (
1563
+ PARTITION BY member.canonical_person_id, resource.provider,
1564
+ CASE WHEN resource.provider='beeper' THEN resource.source_realm_id ELSE NULL END
1565
+ ORDER BY CASE
1566
+ WHEN resource.profile_url IS NOT NULL AND trim(resource.profile_url) <> '' THEN 0
1567
+ WHEN resource.username IS NOT NULL AND trim(resource.username) <> '' THEN 1
1568
+ ELSE 2
1569
+ END, resource.id
1570
+ ) AS preference_rank
1571
+ FROM provider_resources resource
1572
+ JOIN person_identity_components member ON member.person_id = resource.person_id
1573
+ LEFT JOIN source_realms realm ON realm.id = resource.source_realm_id
1574
+ LEFT JOIN source_bindings binding ON binding.id = realm.source_binding_id
1575
+ LEFT JOIN current_source_binding_incarnations incarnation ON incarnation.source_binding_id = binding.id
1576
+ WHERE resource.active = 1 AND (realm.id IS NULL OR realm.active = 1)
1577
+ )
1578
+ WHERE preference_rank = 1
1579
+ ),
1580
+ handle_rollup AS (
1581
+ SELECT person_id, json_group_array(json_object(
1582
+ 'provider', provider,
1583
+ 'service', service,
1584
+ 'realmExternalIdSha256', realm_external_id_sha256,
1585
+ 'bindingAuthSha256', binding_auth_sha256,
1586
+ 'handle', handle
1587
+ )) AS handles_json
1588
+ FROM (
1589
+ SELECT choice.person_id, choice.provider, choice.service,
1590
+ choice.realm_external_id_sha256, choice.binding_auth_sha256,
1591
+ CASE
1592
+ WHEN resource.profile_url IS NOT NULL AND trim(resource.profile_url) <> '' THEN resource.profile_url
1593
+ WHEN resource.username IS NOT NULL AND trim(resource.username) <> '' THEN resource.username
1594
+ ELSE resource.resource_id
1595
+ END AS handle
1596
+ FROM handle_choices choice
1597
+ JOIN provider_resources resource ON resource.id = choice.resource_id
1598
+ ORDER BY choice.person_id, choice.provider, choice.service,
1599
+ choice.realm_external_id_sha256
1600
+ )
1601
+ GROUP BY person_id
1602
+ )
1603
+ SELECT p.id, p.display_name, p.organization, p.title,
1604
+ person_rollup.birthday,
1605
+ coalesce(person_rollup.do_not_contact, p.do_not_contact) AS do_not_contact,
1606
+ coalesce(person_rollup.updated_at, p.updated_at) AS updated_at,
1607
+ coalesce(canonical_interaction_rollup.interaction_count, 0) AS interaction_count,
1608
+ coalesce(canonical_interaction_rollup.reciprocal, 0) AS reciprocal,
1609
+ canonical_interaction_rollup.first_interaction_at,
1610
+ canonical_interaction_rollup.last_interaction_at,
1611
+ coalesce(email_rollup.emails_json, '[]') AS emails_json,
1612
+ coalesce(phone_rollup.phones_json, '[]') AS phones_json,
1613
+ coalesce(identity_email_rollup.identity_emails_json, '[]') AS identity_emails_json,
1614
+ coalesce(identity_phone_rollup.identity_phones_json, '[]') AS identity_phones_json,
1615
+ coalesce(identity_profile_url_rollup.identity_profile_urls_json, '[]') AS identity_profile_urls_json,
1616
+ coalesce(source_rollup.sources_json, '[]') AS sources_json,
1617
+ coalesce(handle_rollup.handles_json, '[]') AS handles_json
1618
+ FROM people p
1619
+ JOIN person_identity_components root ON root.person_id = p.id AND root.canonical_person_id = p.id
1620
+ LEFT JOIN person_rollup ON person_rollup.person_id = p.id
1621
+ LEFT JOIN canonical_interaction_rollup ON canonical_interaction_rollup.person_id = p.id
1622
+ LEFT JOIN email_rollup ON email_rollup.person_id = p.id
1623
+ LEFT JOIN phone_rollup ON phone_rollup.person_id = p.id
1624
+ LEFT JOIN identity_email_rollup ON identity_email_rollup.person_id = p.id
1625
+ LEFT JOIN identity_phone_rollup ON identity_phone_rollup.person_id = p.id
1626
+ LEFT JOIN identity_profile_url_rollup ON identity_profile_url_rollup.person_id = p.id
1627
+ LEFT JOIN source_rollup ON source_rollup.person_id = p.id
1628
+ LEFT JOIN handle_rollup ON handle_rollup.person_id = p.id
1629
+ LEFT JOIN resource_eligibility ON resource_eligibility.person_id = p.id
1630
+ WHERE resource_eligibility.person_id IS NULL
1631
+ OR resource_eligibility.has_active_resource = 1
1632
+ OR canonical_interaction_rollup.person_id IS NOT NULL
1633
+ ORDER BY p.id
1634
+ `).all();
1635
+ const instance = localDatabaseFingerprint(database);
1636
+ return rows.map((row) => {
1637
+ const sources = sourceLabelSchema.array().parse(parseStringArray(row.sources_json).filter((source) => sourceLabelSchema.safeParse(source).success));
1638
+ const data = {
1639
+ id: sha256(`peopleblade\x00${instance}\x00${row.id}`),
1640
+ localPersonId: String(row.id),
1641
+ displayName: row.display_name ?? "Unnamed contact",
1642
+ emails: parseStringArray(row.emails_json).filter((email) => cloudEmailSchema.safeParse(email).success).slice(0, 100),
1643
+ phones: parseStringArray(row.phones_json).slice(0, 100),
1644
+ organization: row.organization,
1645
+ title: row.title,
1646
+ birthday: row.birthday,
1647
+ sources,
1648
+ providerHandles: parseProviderHandleCandidates(row.handles_json, instance),
1649
+ enrichmentInputVersion: 2,
1650
+ identityAnchors: {
1651
+ state: "available",
1652
+ emails: parseStringArray(row.identity_emails_json).filter((email) => cloudEmailSchema.safeParse(email).success).slice(0, 100),
1653
+ phones: parseStringArray(row.identity_phones_json).slice(0, 100),
1654
+ profileUrls: parseStringArray(row.identity_profile_urls_json).filter((url) => httpUrlSchema.safeParse(url).success).slice(0, 100)
1655
+ },
1656
+ automaticEnrichmentSelection: {
1657
+ version: 1,
1658
+ state: row.do_not_contact === 1 ? "blocked" : "eligible"
1659
+ },
1660
+ interactionCount: row.interaction_count,
1661
+ reciprocal: row.reciprocal === 1,
1662
+ firstInteractionAt: iso(row.first_interaction_at),
1663
+ lastInteractionAt: iso(row.last_interaction_at),
1664
+ updatedAt: iso(row.updated_at) ?? new Date(0).toISOString()
1665
+ };
1666
+ return cloudContactSchema.parse({
1667
+ ...data,
1668
+ metadataSha256: sha256(canonicalJson(data)),
1669
+ enrichmentInputSha256: enrichmentInputSha256(data)
1670
+ });
1671
+ });
1672
+ }
1673
+
1674
+ // src/local/cloud-sync-runtime.ts
1675
+ import {
1676
+ Cause,
1677
+ Exit,
1678
+ Layer,
1679
+ ManagedRuntime,
1680
+ Option
1681
+ } from "effect";
1682
+
1683
+ // src/local/cloud-sync-program.ts
1684
+ import { Effect as Effect2 } from "effect";
1685
+
1686
+ // src/local/cloud-sync-service.ts
1687
+ import { Context, Data, Effect } from "effect";
1688
+ import { z as z5 } from "zod";
1689
+
1690
+ class CloudSyncError extends Data.TaggedError("CloudSyncError") {
1691
+ }
1692
+ var syncValue = (operation, read) => Effect.try({ try: read, catch: (cause) => new CloudSyncError({ operation, cause }) });
1693
+ var syncStartResponse = z5.object({
1694
+ snapshotId: z5.uuid(),
1695
+ resumed: z5.boolean(),
1696
+ uploadedContacts: z5.number().int().min(0).max(1e6),
1697
+ uploadedPages: z5.number().int().min(0).max(1e5),
1698
+ uploadedPageContactCounts: z5.array(z5.number().int().min(1).max(200)).max(1e5)
1699
+ }).strict().superRefine((value, context) => {
1700
+ if (value.uploadedPageContactCounts.length !== value.uploadedPages)
1701
+ context.addIssue({
1702
+ code: "custom",
1703
+ path: ["uploadedPageContactCounts"],
1704
+ message: "Uploaded sync-page manifest length does not match its page count"
1705
+ });
1706
+ const contactCount = value.uploadedPageContactCounts.reduce((sum, count) => sum + count, 0);
1707
+ if (contactCount !== value.uploadedContacts)
1708
+ context.addIssue({
1709
+ code: "custom",
1710
+ path: ["uploadedContacts"],
1711
+ message: "Uploaded sync-page manifest does not match its contact count"
1712
+ });
1713
+ if (!value.resumed && (value.uploadedContacts !== 0 || value.uploadedPages !== 0))
1714
+ context.addIssue({
1715
+ code: "custom",
1716
+ path: ["resumed"],
1717
+ message: "A fresh sync snapshot cannot report uploaded progress"
1718
+ });
1719
+ });
1720
+
1721
+ class CloudSync extends Context.Tag("peopleblade/local/CloudSync/v1")() {
1722
+ }
1723
+
1724
+ // src/local/cloud-sync-program.ts
1725
+ var syncCloudProgram = Effect2.gen(function* () {
1726
+ const ports = yield* CloudSync;
1727
+ const prepared = yield* ports.prepare;
1728
+ const { contacts, pageSize } = prepared;
1729
+ const started = yield* ports.start(prepared.start);
1730
+ if (started.uploadedContacts > contacts.length)
1731
+ return yield* Effect2.fail(new CloudSyncError({
1732
+ operation: "resume",
1733
+ cause: new Error("Cloud sync resume progress exceeds the current local projection.")
1734
+ }));
1735
+ let pages = 0;
1736
+ let offset = 0;
1737
+ for (const priorPageContacts of started.uploadedPageContactCounts) {
1738
+ const replayContacts = contacts.slice(offset, offset + priorPageContacts);
1739
+ if (replayContacts.length !== priorPageContacts)
1740
+ return yield* Effect2.fail(new CloudSyncError({
1741
+ operation: "resume",
1742
+ cause: new Error("Cloud sync resume manifest exceeds the current local projection.")
1743
+ }));
1744
+ const input = yield* syncValue("page", () => syncPageSchema.parse({ snapshotId: started.snapshotId, ordinal: pages, contacts: replayContacts }));
1745
+ yield* ports.page(input);
1746
+ pages += 1;
1747
+ offset += priorPageContacts;
1748
+ }
1749
+ if (offset !== started.uploadedContacts || pages !== started.uploadedPages)
1750
+ return yield* Effect2.fail(new CloudSyncError({
1751
+ operation: "resume",
1752
+ cause: new Error("Cloud sync resume manifest did not reproduce its reported progress.")
1753
+ }));
1754
+ for (;offset < contacts.length; offset += pageSize) {
1755
+ const input = yield* syncValue("page", () => syncPageSchema.parse({ snapshotId: started.snapshotId, ordinal: pages, contacts: contacts.slice(offset, offset + pageSize) }));
1756
+ yield* ports.page(input);
1757
+ pages += 1;
1758
+ }
1759
+ const finish = yield* syncValue("finish", () => syncFinishSchema.parse({ snapshotId: started.snapshotId, pages, contactCount: contacts.length }));
1760
+ yield* ports.finish(finish);
1761
+ yield* ports.commit(started.snapshotId);
1762
+ return { snapshotId: started.snapshotId, contacts: contacts.length, pages, resumed: started.resumed, replayedPages: started.uploadedPages };
1763
+ });
1764
+
1765
+ // src/local/cloud-sync-runtime.ts
1766
+ async function runCloudSync(ports, signal) {
1767
+ const runtime = ManagedRuntime.make(Layer.succeed(CloudSync, ports));
1768
+ try {
1769
+ const exit = await runtime.runPromiseExit(syncCloudProgram, { signal });
1770
+ if (Exit.isSuccess(exit))
1771
+ return exit.value;
1772
+ const failure = Cause.failureOption(exit.cause);
1773
+ if (Option.isSome(failure) && failure.value instanceof CloudSyncError)
1774
+ throw failure.value.cause;
1775
+ throw Cause.squash(exit.cause);
1776
+ } finally {
1777
+ await runtime.dispose();
1778
+ }
1779
+ }
1780
+
1781
+ // src/local/cloud-sync-client.ts
1782
+ var SYNC_PAGE_TIMEOUT_MS = 240000;
1783
+ function cloudSyncPorts(database, options, postJson) {
1784
+ let config;
1785
+ const request = (operation, path, input, timeoutMs) => Effect3.suspend(() => {
1786
+ const current = config;
1787
+ if (current === undefined)
1788
+ return Effect3.die(new Error("Cloud sync transport used before preparation."));
1789
+ return Effect3.tryPromise({
1790
+ try: (signal) => postJson(options.fetcher ?? fetch, `${current.baseUrl}${path}`, input, current.token, timeoutMs, signal),
1791
+ catch: (cause) => new CloudSyncError({ operation, cause })
1792
+ });
1793
+ });
1794
+ return {
1795
+ prepare: syncValue("projection", () => {
1796
+ config = cloudConfiguration(options.configuration);
1797
+ const contacts = projectCloudContacts(database);
1798
+ return {
1799
+ contacts,
1800
+ pageSize: Math.max(1, Math.min(200, options.pageSize ?? 200)),
1801
+ start: syncStartSchema.parse({
1802
+ contactCount: contacts.length,
1803
+ databaseFingerprint: localDatabaseFingerprint(database),
1804
+ schemaVersion: localSchemaVersion(database),
1805
+ resumeVersion: 1
1806
+ })
1807
+ };
1808
+ }),
1809
+ start: (input) => request("start", "/api/cli/sync/start", input, 285000).pipe(Effect3.flatMap((value) => syncValue("start", () => syncStartResponse.parse(value)))),
1810
+ page: (input) => request("page", "/api/cli/sync/page", input, SYNC_PAGE_TIMEOUT_MS).pipe(Effect3.asVoid),
1811
+ finish: (input) => request("finish", "/api/cli/sync/finish", input).pipe(Effect3.asVoid),
1812
+ commit: (snapshotId) => syncValue("local-commit", () => {
1813
+ if (config === undefined)
1814
+ throw new Error("Cloud sync commit used before preparation.");
1815
+ database.query(`INSERT INTO cloud_sync_state(singleton,base_url,device_id,last_snapshot_id,last_synced_at)
1816
+ VALUES (1,?,?,?,CURRENT_TIMESTAMP)
1817
+ ON CONFLICT(singleton) DO UPDATE SET base_url=excluded.base_url, device_id=excluded.device_id,
1818
+ last_snapshot_id=excluded.last_snapshot_id, last_synced_at=excluded.last_synced_at`).run(config.baseUrl, config.deviceId, snapshotId);
1819
+ })
1820
+ };
1821
+ }
1822
+ function startCloudSync(database, options, transport) {
1823
+ return runCloudSync(cloudSyncPorts(database, options, transport), options.signal);
1824
+ }
1825
+ export {
1826
+ startCloudSync
1827
+ };