@odla-ai/chapter 0.47.5 → 0.47.7
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/dist/{copy-context-20WFI5TI.d.ts → copy-context-Dorxp-WK.d.ts} +2 -0
- package/dist/index.cjs +29 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -1
- package/dist/index.d.ts +20 -1
- package/dist/index.js +26 -2
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +303 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +105 -0
- package/dist/testing.d.ts +105 -0
- package/dist/testing.js +280 -0
- package/dist/testing.js.map +1 -0
- package/dist/ui/admin/index.d.ts +2 -2
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/member/index.d.ts +2 -2
- package/dist/worker/index.cjs +287 -5
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +6 -0
- package/dist/worker/index.d.ts +6 -0
- package/dist/worker/index.js +287 -5
- package/dist/worker/index.js.map +1 -1
- package/package.json +6 -1
package/dist/worker/index.cjs
CHANGED
|
@@ -351,6 +351,8 @@ function joinConfig(group, paymentsReady, tiers = [], conditions = {}) {
|
|
|
351
351
|
name: group.name,
|
|
352
352
|
standardPriceCents: group.standardPriceCents ?? 0,
|
|
353
353
|
foundingDiscountCents: group.foundingDiscountCents ?? 0,
|
|
354
|
+
currency: group.currency ?? "usd",
|
|
355
|
+
interval: group.interval ?? "year",
|
|
354
356
|
tiers: [...tiers],
|
|
355
357
|
// The browser evaluates the same conditions the server enforces.
|
|
356
358
|
conditions,
|
|
@@ -383,6 +385,8 @@ function resolveTiers(group, rows5 = []) {
|
|
|
383
385
|
groupId: String(group.id ?? ""),
|
|
384
386
|
name: "Membership",
|
|
385
387
|
priceCents: group.standardPriceCents ?? 0,
|
|
388
|
+
currency: group.currency ?? "usd",
|
|
389
|
+
interval: group.interval ?? "year",
|
|
386
390
|
...group.stripePriceId ? { stripePriceId: group.stripePriceId } : {},
|
|
387
391
|
sortOrder: 0,
|
|
388
392
|
active: true
|
|
@@ -395,11 +399,13 @@ function findTier(tiers, tierId) {
|
|
|
395
399
|
if (!tierId) return tiers.length === 1 ? tiers[0] : null;
|
|
396
400
|
return tiers.find((tier) => tier.id === tierId) ?? null;
|
|
397
401
|
}
|
|
398
|
-
function joinConfigTiers(tiers) {
|
|
402
|
+
function joinConfigTiers(tiers, defaults = {}) {
|
|
399
403
|
return tiers.map((tier) => ({
|
|
400
404
|
id: tier.id,
|
|
401
405
|
name: tier.name,
|
|
402
406
|
priceCents: tier.priceCents,
|
|
407
|
+
currency: tier.currency ?? defaults.currency ?? "usd",
|
|
408
|
+
interval: tier.interval ?? defaults.interval ?? "year",
|
|
403
409
|
blurb: tier.blurb ?? "",
|
|
404
410
|
free: tierIsFree(tier)
|
|
405
411
|
}));
|
|
@@ -412,6 +418,8 @@ function rowToTier(row, groupId) {
|
|
|
412
418
|
groupId: String(row.groupId ?? groupId),
|
|
413
419
|
name: String(row.name ?? ""),
|
|
414
420
|
priceCents: typeof row.priceCents === "number" ? row.priceCents : 0,
|
|
421
|
+
...typeof row.currency === "string" && row.currency ? { currency: row.currency } : {},
|
|
422
|
+
...row.interval === "year" || row.interval === "month" ? { interval: row.interval } : {},
|
|
415
423
|
...typeof row.stripePriceId === "string" && row.stripePriceId ? { stripePriceId: row.stripePriceId } : {},
|
|
416
424
|
...typeof row.blurb === "string" ? { blurb: row.blurb } : {},
|
|
417
425
|
sortOrder: typeof row.sortOrder === "number" ? row.sortOrder : 0,
|
|
@@ -1521,6 +1529,8 @@ function groupAttrs(document) {
|
|
|
1521
1529
|
const firstPaid = [...document.tiers].find((tier) => tier.available && tier.priceCents > 0);
|
|
1522
1530
|
return {
|
|
1523
1531
|
...first4 ? { standardPriceCents: first4.priceCents } : {},
|
|
1532
|
+
currency: first4?.currency ?? "usd",
|
|
1533
|
+
interval: first4?.interval ?? "year",
|
|
1524
1534
|
foundingDiscountCents: 0,
|
|
1525
1535
|
...firstPaid?.stripePriceId ? { stripePriceId: firstPaid.stripePriceId } : {},
|
|
1526
1536
|
...document.policy ?? {},
|
|
@@ -1554,6 +1564,8 @@ async function materializeSignupControl(db, envelope, previous, receivedAt) {
|
|
|
1554
1564
|
groupId: document.target.chapterId,
|
|
1555
1565
|
name: tier.name,
|
|
1556
1566
|
priceCents: tier.priceCents,
|
|
1567
|
+
currency: tier.currency ?? "usd",
|
|
1568
|
+
interval: tier.interval ?? "year",
|
|
1557
1569
|
...tier.stripePriceId ? { stripePriceId: tier.stripePriceId } : {},
|
|
1558
1570
|
...tier.blurb ? { blurb: tier.blurb } : {},
|
|
1559
1571
|
sortOrder: tier.sortOrder,
|
|
@@ -1970,7 +1982,7 @@ var handleMember = async (req, url, env, ctx) => {
|
|
|
1970
1982
|
const activeControl = chapter.signupControl ? await loadActiveSignupControl(db, chapter.id, env.ODLA_ENV) : null;
|
|
1971
1983
|
const application = controlledApplication(chapter.application, activeControl?.document);
|
|
1972
1984
|
return json({
|
|
1973
|
-
...joinConfig(group, paymentsReady, joinConfigTiers(offered), application.conditions),
|
|
1985
|
+
...joinConfig(group, paymentsReady, joinConfigTiers(offered, { currency: typeof group.currency === "string" ? group.currency : void 0, interval: group.interval === "month" ? "month" : "year" }), application.conditions),
|
|
1974
1986
|
fieldContract: chapterFlowFieldContract(chapter, "join", application),
|
|
1975
1987
|
copy: { ...chapter.copy.join, ...activeControl?.document.application?.copy ?? {} },
|
|
1976
1988
|
...activeControl ? { signupControl: {
|
|
@@ -2327,7 +2339,7 @@ var ROOT_KEYS2 = /* @__PURE__ */ new Set([
|
|
|
2327
2339
|
"theme",
|
|
2328
2340
|
"giftOffers"
|
|
2329
2341
|
]);
|
|
2330
|
-
var TIER_KEYS2 = /* @__PURE__ */ new Set(["id", "name", "available", "sortOrder", "priceCents", "stripePriceId", "blurb"]);
|
|
2342
|
+
var TIER_KEYS2 = /* @__PURE__ */ new Set(["id", "name", "available", "sortOrder", "priceCents", "currency", "interval", "stripePriceId", "blurb"]);
|
|
2331
2343
|
var recordOf = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
2332
2344
|
function unknownKeys(value, allowed2, path, errors) {
|
|
2333
2345
|
for (const key of Object.keys(value)) if (!allowed2.has(key)) errors.push(`${path}.${key} is not supported`);
|
|
@@ -2371,6 +2383,12 @@ function tiersOf(value, errors) {
|
|
|
2371
2383
|
if (item.available === true && Number(item.priceCents) > 0 && !item.stripePriceId) {
|
|
2372
2384
|
errors.push(`${path}.stripePriceId is required for an available paid tier`);
|
|
2373
2385
|
}
|
|
2386
|
+
if (item.currency !== void 0 && (typeof item.currency !== "string" || !/^[a-z]{3}$/.test(item.currency))) {
|
|
2387
|
+
errors.push(`${path}.currency must be a lowercase ISO currency code`);
|
|
2388
|
+
}
|
|
2389
|
+
if (item.interval !== void 0 && item.interval !== "year" && item.interval !== "month") {
|
|
2390
|
+
errors.push(`${path}.interval must be year or month`);
|
|
2391
|
+
}
|
|
2374
2392
|
const blurb = item.blurb === void 0 ? void 0 : text2(item.blurb, `${path}.blurb`, errors, 4e3);
|
|
2375
2393
|
return [{
|
|
2376
2394
|
id,
|
|
@@ -2378,6 +2396,8 @@ function tiersOf(value, errors) {
|
|
|
2378
2396
|
available: item.available === true,
|
|
2379
2397
|
sortOrder: Number(item.sortOrder) || 0,
|
|
2380
2398
|
priceCents: Number(item.priceCents) || 0,
|
|
2399
|
+
...typeof item.currency === "string" ? { currency: item.currency } : {},
|
|
2400
|
+
...item.interval === "year" || item.interval === "month" ? { interval: item.interval } : {},
|
|
2381
2401
|
...typeof item.stripePriceId === "string" ? { stripePriceId: item.stripePriceId } : {},
|
|
2382
2402
|
...blurb ? { blurb } : {}
|
|
2383
2403
|
}];
|
|
@@ -6894,6 +6914,7 @@ var handleAdminNetworkSharedNotes = async (req, url, env, ctx) => {
|
|
|
6894
6914
|
// src/network-person-registry.ts
|
|
6895
6915
|
var TOKEN = /^[a-z0-9][a-z0-9._:-]{0,127}$/;
|
|
6896
6916
|
var CHAPTER_ID = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
6917
|
+
var FIXTURE_MARKER = /fixture|(?:^|[\s._:-])(dummy|sample|test(?:case)?)(?:$|[\s._:-])/i;
|
|
6897
6918
|
function normalizeNetworkIdentity(kind, value) {
|
|
6898
6919
|
const trimmed = value.trim();
|
|
6899
6920
|
if (kind === "email") return trimmed.toLowerCase();
|
|
@@ -6920,6 +6941,9 @@ function validateNetworkPersonImport(input, allowedChapterIds) {
|
|
|
6920
6941
|
if (!sourceRecordId || sourceRecordId.length > 200) errors.push("sourceRecordId is required and bounded");
|
|
6921
6942
|
if (!provenance || provenance.length > 500) errors.push("provenance is required and bounded");
|
|
6922
6943
|
if (!displayName || displayName.length > 200) errors.push("displayName is required and bounded");
|
|
6944
|
+
if (FIXTURE_MARKER.test(sourceRecordId) || FIXTURE_MARKER.test(displayName)) {
|
|
6945
|
+
errors.push("fixture, test, sample, and dummy rows cannot become network Persons");
|
|
6946
|
+
}
|
|
6923
6947
|
const rawConsent = raw2.consent && typeof raw2.consent === "object" && !Array.isArray(raw2.consent) ? raw2.consent : {};
|
|
6924
6948
|
const consentStates = ["unknown", "granted", "denied"];
|
|
6925
6949
|
const marketing = typeof rawConsent.marketing === "string" ? rawConsent.marketing : "";
|
|
@@ -7332,6 +7356,247 @@ async function suppressNetworkPerson(db, input, mutationId, actor, now = Date.no
|
|
|
7332
7356
|
return next;
|
|
7333
7357
|
}
|
|
7334
7358
|
|
|
7359
|
+
// src/network-person-bulk-preview.ts
|
|
7360
|
+
var NETWORK_PERSON_BULK_IMPORT_MAX_ROWS = 5e3;
|
|
7361
|
+
var NETWORK_PERSON_BULK_IMPORT_VERSION = 1;
|
|
7362
|
+
var networkPersonBulkEmptyCounts = () => ({
|
|
7363
|
+
new: 0,
|
|
7364
|
+
replay: 0,
|
|
7365
|
+
exactMatchReview: 0,
|
|
7366
|
+
invalid: 0,
|
|
7367
|
+
rejected: 0,
|
|
7368
|
+
failed: 0
|
|
7369
|
+
});
|
|
7370
|
+
var incrementNetworkPersonBulkCount = (counts, status) => {
|
|
7371
|
+
if (status === "exact_match_review") counts.exactMatchReview += 1;
|
|
7372
|
+
else counts[status] += 1;
|
|
7373
|
+
};
|
|
7374
|
+
function stableValue(value) {
|
|
7375
|
+
if (Array.isArray(value)) return value.map(stableValue);
|
|
7376
|
+
if (value && typeof value === "object") {
|
|
7377
|
+
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, stableValue(item)]));
|
|
7378
|
+
}
|
|
7379
|
+
return value;
|
|
7380
|
+
}
|
|
7381
|
+
async function networkPersonBulkSha256(value) {
|
|
7382
|
+
const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
|
|
7383
|
+
return [...new Uint8Array(bytes)].map((part) => part.toString(16).padStart(2, "0")).join("");
|
|
7384
|
+
}
|
|
7385
|
+
function assertNetworkPersonBulkRows(rows5) {
|
|
7386
|
+
if (!Array.isArray(rows5) || rows5.length < 1) {
|
|
7387
|
+
throw new NetworkPersonRegistryError("bulk_rows_required", "bulk import requires at least one row");
|
|
7388
|
+
}
|
|
7389
|
+
if (rows5.length > NETWORK_PERSON_BULK_IMPORT_MAX_ROWS) {
|
|
7390
|
+
throw new NetworkPersonRegistryError(
|
|
7391
|
+
"bulk_rows_exceeded",
|
|
7392
|
+
`bulk import is limited to ${NETWORK_PERSON_BULK_IMPORT_MAX_ROWS.toLocaleString()} rows`
|
|
7393
|
+
);
|
|
7394
|
+
}
|
|
7395
|
+
return rows5;
|
|
7396
|
+
}
|
|
7397
|
+
async function networkPersonBulkBatchDigest(rows5) {
|
|
7398
|
+
const bounded = assertNetworkPersonBulkRows(rows5);
|
|
7399
|
+
return networkPersonBulkSha256(JSON.stringify(stableValue({ version: NETWORK_PERSON_BULK_IMPORT_VERSION, rows: bounded })));
|
|
7400
|
+
}
|
|
7401
|
+
async function networkPersonRegistryStateDigest(state) {
|
|
7402
|
+
const snapshot = {
|
|
7403
|
+
persons: state.persons.map(({ id, revision, state: personState, mergedIntoId }) => ({ id, revision, state: personState, mergedIntoId })).sort((a, b) => a.id.localeCompare(b.id)),
|
|
7404
|
+
claims: state.claims.map(({ id, personId }) => ({ id, personId })).sort((a, b) => a.id.localeCompare(b.id)),
|
|
7405
|
+
aliases: state.aliases.map(({ id, revision, status, personId }) => ({ id, revision, status, personId })).sort((a, b) => a.id.localeCompare(b.id)),
|
|
7406
|
+
links: state.links.map(({ id, personId, state: linkState }) => ({ id, personId, state: linkState })).sort((a, b) => a.id.localeCompare(b.id))
|
|
7407
|
+
};
|
|
7408
|
+
return networkPersonBulkSha256(JSON.stringify(stableValue(snapshot)));
|
|
7409
|
+
}
|
|
7410
|
+
function rejectedValidation(errors) {
|
|
7411
|
+
return errors.some((error) => /approved|real person|non-development|development or invalid email|fixture, test, sample/i.test(error));
|
|
7412
|
+
}
|
|
7413
|
+
async function classifyNetworkPersonBulkRows(rows5, allowedChapterIds, state) {
|
|
7414
|
+
const aliasIds = new Set(state.aliases.map((alias) => alias.id));
|
|
7415
|
+
const batchAliases = /* @__PURE__ */ new Set();
|
|
7416
|
+
const claimOwners = /* @__PURE__ */ new Map();
|
|
7417
|
+
for (const claim of state.claims) {
|
|
7418
|
+
const owners = claimOwners.get(claim.id) ?? /* @__PURE__ */ new Set();
|
|
7419
|
+
owners.add(claim.personId);
|
|
7420
|
+
claimOwners.set(claim.id, owners);
|
|
7421
|
+
}
|
|
7422
|
+
const results = [];
|
|
7423
|
+
const values = [];
|
|
7424
|
+
for (let index2 = 0; index2 < rows5.length; index2 += 1) {
|
|
7425
|
+
const validation = validateNetworkPersonImport(rows5[index2], allowedChapterIds);
|
|
7426
|
+
if (!validation.ok || !validation.value) {
|
|
7427
|
+
const errors = validation.errors.slice(0, 8);
|
|
7428
|
+
results.push({ rowNumber: index2 + 2, status: rejectedValidation(errors) ? "rejected" : "invalid", errors });
|
|
7429
|
+
values.push(null);
|
|
7430
|
+
continue;
|
|
7431
|
+
}
|
|
7432
|
+
const value = validation.value;
|
|
7433
|
+
const aliasId = await networkSourceKey(value.sourceSystem, value.sourceRecordId);
|
|
7434
|
+
if (batchAliases.has(aliasId)) {
|
|
7435
|
+
results.push({ rowNumber: index2 + 2, status: "rejected", errors: ["duplicate source record in this batch"] });
|
|
7436
|
+
values.push(null);
|
|
7437
|
+
continue;
|
|
7438
|
+
}
|
|
7439
|
+
batchAliases.add(aliasId);
|
|
7440
|
+
if (aliasIds.has(aliasId)) {
|
|
7441
|
+
results.push({ rowNumber: index2 + 2, status: "replay" });
|
|
7442
|
+
values.push(value);
|
|
7443
|
+
continue;
|
|
7444
|
+
}
|
|
7445
|
+
const claims = await Promise.all(value.identities.map(networkIdentityClaim));
|
|
7446
|
+
const candidates = new Set(claims.flatMap((claim) => [...claimOwners.get(claim.id) ?? []]));
|
|
7447
|
+
results.push(candidates.size ? { rowNumber: index2 + 2, status: "exact_match_review", candidateCount: candidates.size } : { rowNumber: index2 + 2, status: "new" });
|
|
7448
|
+
const plannedOwner = `bulk_row_${index2 + 2}`;
|
|
7449
|
+
for (const claim of claims) {
|
|
7450
|
+
const owners = claimOwners.get(claim.id) ?? /* @__PURE__ */ new Set();
|
|
7451
|
+
owners.add(plannedOwner);
|
|
7452
|
+
claimOwners.set(claim.id, owners);
|
|
7453
|
+
}
|
|
7454
|
+
values.push(value);
|
|
7455
|
+
}
|
|
7456
|
+
return { rows: results, values };
|
|
7457
|
+
}
|
|
7458
|
+
async function previewNetworkPersonBulkImport(db, rows5, allowedChapterIds) {
|
|
7459
|
+
const bounded = assertNetworkPersonBulkRows(rows5);
|
|
7460
|
+
const state = await loadNetworkPersonRegistry(db);
|
|
7461
|
+
const classified = await classifyNetworkPersonBulkRows(bounded, allowedChapterIds, state);
|
|
7462
|
+
const counts = networkPersonBulkEmptyCounts();
|
|
7463
|
+
for (const row of classified.rows) incrementNetworkPersonBulkCount(counts, row.status);
|
|
7464
|
+
return {
|
|
7465
|
+
version: NETWORK_PERSON_BULK_IMPORT_VERSION,
|
|
7466
|
+
batchDigest: await networkPersonBulkBatchDigest(bounded),
|
|
7467
|
+
stateDigest: await networkPersonRegistryStateDigest(state),
|
|
7468
|
+
rowCount: bounded.length,
|
|
7469
|
+
counts,
|
|
7470
|
+
rows: classified.rows
|
|
7471
|
+
};
|
|
7472
|
+
}
|
|
7473
|
+
|
|
7474
|
+
// src/network-person-bulk-import.ts
|
|
7475
|
+
async function derivedMutationId(batchMutationId, part) {
|
|
7476
|
+
return `bulk_${await networkPersonBulkSha256(`${batchMutationId}\0${part}`)}`;
|
|
7477
|
+
}
|
|
7478
|
+
function matchingEvent(events, mutationId, kind) {
|
|
7479
|
+
return events.find((event) => event.mutationId === mutationId && event.kind === kind);
|
|
7480
|
+
}
|
|
7481
|
+
function eventString(event, key) {
|
|
7482
|
+
return typeof event.detail[key] === "string" ? String(event.detail[key]) : "";
|
|
7483
|
+
}
|
|
7484
|
+
function eventCounts(event) {
|
|
7485
|
+
const value = event.detail.counts;
|
|
7486
|
+
return value && typeof value === "object" && !Array.isArray(value) ? { ...networkPersonBulkEmptyCounts(), ...value } : networkPersonBulkEmptyCounts();
|
|
7487
|
+
}
|
|
7488
|
+
async function applyNetworkPersonBulkImport(db, input, batchMutationId, actor, allowedChapterIds, now = Date.now()) {
|
|
7489
|
+
if (input.reviewed !== true) throw new NetworkPersonRegistryError("bulk_review_required", "the exact preview must be explicitly reviewed before apply");
|
|
7490
|
+
const rows5 = assertNetworkPersonBulkRows(input.rows);
|
|
7491
|
+
const batchDigest = await networkPersonBulkBatchDigest(rows5);
|
|
7492
|
+
if (!/^[a-f0-9]{64}$/.test(input.expectedBatchDigest) || batchDigest !== input.expectedBatchDigest) {
|
|
7493
|
+
throw new NetworkPersonRegistryError("bulk_digest_changed", "bulk import rows changed after preview; preview again");
|
|
7494
|
+
}
|
|
7495
|
+
if (!/^[a-f0-9]{64}$/.test(input.expectedStateDigest)) {
|
|
7496
|
+
throw new NetworkPersonRegistryError("bulk_state_digest_required", "a reviewed registry state digest is required");
|
|
7497
|
+
}
|
|
7498
|
+
const startedMutationId = await derivedMutationId(batchMutationId, "started");
|
|
7499
|
+
const completedMutationId = await derivedMutationId(batchMutationId, "completed");
|
|
7500
|
+
let state = await loadNetworkPersonRegistry(db);
|
|
7501
|
+
const completed = matchingEvent(state.events, completedMutationId, "bulk_import_completed");
|
|
7502
|
+
if (completed) {
|
|
7503
|
+
if (eventString(completed, "batchDigest") !== batchDigest) {
|
|
7504
|
+
throw new NetworkPersonRegistryError("bulk_mutation_conflict", "batch mutation id was already used for different rows");
|
|
7505
|
+
}
|
|
7506
|
+
return {
|
|
7507
|
+
version: NETWORK_PERSON_BULK_IMPORT_VERSION,
|
|
7508
|
+
batchDigest,
|
|
7509
|
+
stateDigest: eventString(completed, "stateDigest"),
|
|
7510
|
+
rowCount: Number(completed.detail.rowCount) || rows5.length,
|
|
7511
|
+
counts: eventCounts(completed),
|
|
7512
|
+
rows: [],
|
|
7513
|
+
replayed: true
|
|
7514
|
+
};
|
|
7515
|
+
}
|
|
7516
|
+
let started = matchingEvent(state.events, startedMutationId, "bulk_import_started");
|
|
7517
|
+
if (!started) {
|
|
7518
|
+
const preview = await previewNetworkPersonBulkImport(db, rows5, allowedChapterIds);
|
|
7519
|
+
if (preview.stateDigest !== input.expectedStateDigest) {
|
|
7520
|
+
throw new NetworkPersonRegistryError("bulk_state_changed", "network Person registry changed after preview; preview again");
|
|
7521
|
+
}
|
|
7522
|
+
try {
|
|
7523
|
+
await db.transact([
|
|
7524
|
+
networkPersonEventOp("bulk_import_started", startedMutationId, actor, now, {
|
|
7525
|
+
batchDigest,
|
|
7526
|
+
stateDigest: input.expectedStateDigest,
|
|
7527
|
+
rowCount: rows5.length,
|
|
7528
|
+
previewCounts: preview.counts
|
|
7529
|
+
})
|
|
7530
|
+
], {
|
|
7531
|
+
mutationId: startedMutationId,
|
|
7532
|
+
guards: [{ ns: "networkPersonEvents", id: `network_person_event:${startedMutationId}`, exists: false }]
|
|
7533
|
+
});
|
|
7534
|
+
} catch (error) {
|
|
7535
|
+
if (!(error instanceof Error) || error.code !== "transact_guard_failed") throw error;
|
|
7536
|
+
}
|
|
7537
|
+
state = await loadNetworkPersonRegistry(db);
|
|
7538
|
+
started = matchingEvent(state.events, startedMutationId, "bulk_import_started");
|
|
7539
|
+
}
|
|
7540
|
+
if (!started || eventString(started, "batchDigest") !== batchDigest || eventString(started, "stateDigest") !== input.expectedStateDigest) {
|
|
7541
|
+
throw new NetworkPersonRegistryError("bulk_mutation_conflict", "batch mutation id does not match the reviewed preview");
|
|
7542
|
+
}
|
|
7543
|
+
const classified = await classifyNetworkPersonBulkRows(rows5, allowedChapterIds, state);
|
|
7544
|
+
const results = [];
|
|
7545
|
+
const counts = networkPersonBulkEmptyCounts();
|
|
7546
|
+
for (let index2 = 0; index2 < classified.rows.length; index2 += 1) {
|
|
7547
|
+
const row = classified.rows[index2];
|
|
7548
|
+
const value = classified.values[index2];
|
|
7549
|
+
if (!value) {
|
|
7550
|
+
results.push(row);
|
|
7551
|
+
incrementNetworkPersonBulkCount(counts, row.status);
|
|
7552
|
+
continue;
|
|
7553
|
+
}
|
|
7554
|
+
try {
|
|
7555
|
+
const result = await importNetworkPerson(
|
|
7556
|
+
db,
|
|
7557
|
+
value,
|
|
7558
|
+
await derivedMutationId(batchMutationId, `row_${index2 + 2}`),
|
|
7559
|
+
actor,
|
|
7560
|
+
now + index2 + 1
|
|
7561
|
+
);
|
|
7562
|
+
const status = "replayed" in result && result.replayed ? "replay" : result.status === "ambiguous" ? "exact_match_review" : "new";
|
|
7563
|
+
const applied = {
|
|
7564
|
+
rowNumber: index2 + 2,
|
|
7565
|
+
status,
|
|
7566
|
+
...result.status === "ambiguous" ? { candidateCount: result.alias.candidatePersonIds?.length ?? 0 } : {}
|
|
7567
|
+
};
|
|
7568
|
+
results.push(applied);
|
|
7569
|
+
incrementNetworkPersonBulkCount(counts, status);
|
|
7570
|
+
} catch (error) {
|
|
7571
|
+
const failed = {
|
|
7572
|
+
rowNumber: index2 + 2,
|
|
7573
|
+
status: "failed",
|
|
7574
|
+
errors: [error instanceof Error ? error.message : "row failed"]
|
|
7575
|
+
};
|
|
7576
|
+
results.push(failed);
|
|
7577
|
+
incrementNetworkPersonBulkCount(counts, failed.status);
|
|
7578
|
+
}
|
|
7579
|
+
}
|
|
7580
|
+
if (counts.failed === 0) {
|
|
7581
|
+
await db.transact([
|
|
7582
|
+
networkPersonEventOp("bulk_import_completed", completedMutationId, actor, now + rows5.length + 1, {
|
|
7583
|
+
batchDigest,
|
|
7584
|
+
stateDigest: input.expectedStateDigest,
|
|
7585
|
+
rowCount: rows5.length,
|
|
7586
|
+
counts
|
|
7587
|
+
})
|
|
7588
|
+
], { mutationId: completedMutationId });
|
|
7589
|
+
}
|
|
7590
|
+
return {
|
|
7591
|
+
version: NETWORK_PERSON_BULK_IMPORT_VERSION,
|
|
7592
|
+
batchDigest,
|
|
7593
|
+
stateDigest: input.expectedStateDigest,
|
|
7594
|
+
rowCount: rows5.length,
|
|
7595
|
+
counts,
|
|
7596
|
+
rows: results
|
|
7597
|
+
};
|
|
7598
|
+
}
|
|
7599
|
+
|
|
7335
7600
|
// src/network-person-route-projection.ts
|
|
7336
7601
|
function redactedName(displayName) {
|
|
7337
7602
|
return displayName.split(/\s+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}\u2026`).join(" ");
|
|
@@ -7448,7 +7713,7 @@ async function revealNetworkPersonRegistryRecord(db, input, mutationId, actor, n
|
|
|
7448
7713
|
|
|
7449
7714
|
// src/worker-routes-network-person-registry.ts
|
|
7450
7715
|
var PATH2 = "/api/admin/network-person-registry";
|
|
7451
|
-
var MAX_BODY2 =
|
|
7716
|
+
var MAX_BODY2 = 8 * 1024 * 1024;
|
|
7452
7717
|
var MUTATION_ID4 = /^[A-Za-z0-9_-]{16,128}$/;
|
|
7453
7718
|
var positiveRevision = (value) => {
|
|
7454
7719
|
const parsed = Number(value);
|
|
@@ -7463,7 +7728,7 @@ async function gate10(req, env, ctx) {
|
|
|
7463
7728
|
}
|
|
7464
7729
|
function registryError(error) {
|
|
7465
7730
|
if (error instanceof NetworkPersonRegistryError) {
|
|
7466
|
-
const status = error.code.includes("stale") || error.code === "identity_conflict" ? 409 : error.code.endsWith("not_found") ? 404 : 400;
|
|
7731
|
+
const status = error.code.includes("stale") || error.code === "identity_conflict" || error.code === "bulk_state_changed" || error.code === "bulk_digest_changed" || error.code === "bulk_mutation_conflict" ? 409 : error.code.endsWith("not_found") ? 404 : 400;
|
|
7467
7732
|
return json({ error: error.message, code: error.code }, status);
|
|
7468
7733
|
}
|
|
7469
7734
|
if (error instanceof Error && error.code === "transact_guard_failed") {
|
|
@@ -7498,6 +7763,23 @@ var handleAdminNetworkPersonRegistry = async (req, url, env, ctx) => {
|
|
|
7498
7763
|
if (!MUTATION_ID4.test(mutationId)) return json({ error: "a valid mutationId is required" }, 400);
|
|
7499
7764
|
const actionActor = { id: got.user.userId };
|
|
7500
7765
|
try {
|
|
7766
|
+
if (command === "preview-bulk-import") {
|
|
7767
|
+
const result = await previewNetworkPersonBulkImport(
|
|
7768
|
+
db,
|
|
7769
|
+
input.rows,
|
|
7770
|
+
ctx.chapter.networkPersonRegistry.chapterIds
|
|
7771
|
+
);
|
|
7772
|
+
return json({ result });
|
|
7773
|
+
}
|
|
7774
|
+
if (command === "apply-bulk-import") {
|
|
7775
|
+
const result = await applyNetworkPersonBulkImport(db, {
|
|
7776
|
+
rows: input.rows,
|
|
7777
|
+
expectedBatchDigest: typeof input.expectedBatchDigest === "string" ? input.expectedBatchDigest : "",
|
|
7778
|
+
expectedStateDigest: typeof input.expectedStateDigest === "string" ? input.expectedStateDigest : "",
|
|
7779
|
+
reviewed: input.reviewed === true
|
|
7780
|
+
}, mutationId, actionActor, ctx.chapter.networkPersonRegistry.chapterIds);
|
|
7781
|
+
return json({ result }, result.replayed ? 200 : 201);
|
|
7782
|
+
}
|
|
7501
7783
|
if (command === "import") {
|
|
7502
7784
|
const validated = validateNetworkPersonImport(input, ctx.chapter.networkPersonRegistry.chapterIds);
|
|
7503
7785
|
if (!validated.ok || !validated.value) return json({ error: "invalid import", details: validated.errors }, 400);
|