@sanity/workflow-engine 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -4016,12 +4016,27 @@ function assertInputValueShape(entry, value) {
4016
4016
  function assertGdrShape(value, context) {
4017
4017
  if (typeof value != "object" || value === null) throw new invariants.ContractViolationError(`Invalid GDR for ${context}: expected { id: "<scheme>:...", type: "<schema>" }, got ${typeof value}.`);
4018
4018
  const v2 = value;
4019
- if (typeof v2.id != "string" || !invariants.isGdrUri(v2.id)) throw new invariants.ContractViolationError(`Invalid GDR for ${context}: \`id\` must be a GDR URI ("<scheme>:<...id-parts>" with scheme dataset|canvas|media-library|dashboard). Got ${JSON.stringify(v2.id)}. Construct via \`gdrFromResource\` / \`refDataset\` / \`refCanvas\` etc. — bare document ids are not accepted.`);
4019
+ if (typeof v2.id != "string") throw invalidGdrUriError(context, v2.id);
4020
+ try {
4021
+ invariants.parseGdr(v2.id);
4022
+ } catch (error) {
4023
+ throw error instanceof invariants.VersionSpecificDatasetGdrError ? new invariants.ContractViolationError(`Invalid GDR for ${context}: ${error.message}`) : invalidGdrUriError(context, v2.id);
4024
+ }
4020
4025
  if (typeof v2.type != "string" || v2.type.length === 0) throw new invariants.ContractViolationError(`Invalid GDR for ${context}: \`type\` (schema name) must be a non-empty string. Got ${JSON.stringify(v2.type)}.`);
4021
4026
  }
4022
4027
 
4028
+ function invalidGdrUriError(context, id) {
4029
+ return new invariants.ContractViolationError(`Invalid GDR for ${context}: \`id\` must be a GDR URI ("<scheme>:<...id-parts>" with scheme dataset|canvas|media-library|dashboard). Got ${JSON.stringify(id)}. Construct via \`gdrFromResource\` / \`refDataset\` / \`refCanvas\` etc. — bare document ids are not accepted.`);
4030
+ }
4031
+
4023
4032
  function normalizeQueryResult({entryType: entryType, raw: raw, workflowResource: workflowResource}) {
4024
- return raw == null ? raw : invariants.isSingleDocRefKind(entryType) ? coerceToGdr(raw, workflowResource) : entryType === "doc.refs" ? Array.isArray(raw) ? raw.map(item => coerceToGdr(item, workflowResource)).filter(v2 => v2 !== null) : [] : raw;
4033
+ if (raw == null) return raw;
4034
+ try {
4035
+ return invariants.isSingleDocRefKind(entryType) ? coerceToGdr(raw, workflowResource) : entryType === "doc.refs" ? Array.isArray(raw) ? raw.map(item => coerceToGdr(item, workflowResource)).filter(v2 => v2 !== null) : [] : raw;
4036
+ } catch (error) {
4037
+ if (error instanceof invariants.VersionSpecificDatasetGdrError) return raw;
4038
+ throw error;
4039
+ }
4025
4040
  }
4026
4041
 
4027
4042
  function coerceGdrShape(raw, workflowResource) {
@@ -4477,65 +4492,119 @@ function buildInstanceBase(args) {
4477
4492
  }
4478
4493
 
4479
4494
  async function hydrateSnapshot(args) {
4480
- const {client: client, clientForGdr: clientForGdr, instance: instance, overlay: overlay} = args, loaded = [], visited = /* @__PURE__ */ new Set, loadInto = async (uri, perspective) => {
4481
- if (visited.has(uri)) return;
4482
- const held = overlay?.get(uri);
4483
- if (held !== void 0) {
4484
- loaded.push(held), visited.add(uri);
4485
- return;
4486
- }
4487
- const fetched = await loadByGdr({
4488
- defaultClient: client,
4489
- clientForGdr: clientForGdr,
4490
- defaultResource: instance.workflowResource,
4491
- uri: uri,
4492
- perspective: perspective
4493
- });
4494
- fetched && (loaded.push(fetched), visited.add(uri));
4495
- };
4495
+ const {client: client, clientForGdr: clientForGdr, instance: instance, overlay: overlay} = args, loaded = [], visited = /* @__PURE__ */ new Set;
4496
4496
  loaded.push({
4497
4497
  doc: instance,
4498
4498
  resource: instance.workflowResource
4499
4499
  }), visited.add(invariants.selfGdr(instance));
4500
- for (const ref of collectWatchRefs(instance)) await loadInto(ref.id, readsRaw(ref) ? "raw" : instance.perspective ?? DEFAULT_CONTENT_PERSPECTIVE);
4500
+ const {pending: pending, ordered: ordered} = planReads({
4501
+ client: client,
4502
+ clientForGdr: clientForGdr,
4503
+ instance: instance,
4504
+ overlay: overlay,
4505
+ visited: visited
4506
+ }), fetched = await readPending(pending);
4507
+ for (const entry of ordered) if (!isPendingRead(entry)) loaded.push(entry); else {
4508
+ const doc = fetched.get(entry);
4509
+ doc !== void 0 && loaded.push({
4510
+ doc: doc,
4511
+ resource: entry.resource
4512
+ });
4513
+ }
4501
4514
  return buildSnapshot({
4502
4515
  docs: loaded
4503
4516
  });
4504
4517
  }
4505
4518
 
4506
- async function loadByGdr({defaultClient: defaultClient, clientForGdr: clientForGdr, defaultResource: defaultResource, uri: uri, perspective: perspective}) {
4507
- const parsed = invariants.tryParseGdr(uri);
4508
- if (parsed === void 0) {
4509
- const doc2 = await readDoc({
4510
- client: defaultClient,
4511
- id: uri,
4512
- perspective: perspective
4513
- });
4514
- return doc2 ? {
4515
- doc: doc2,
4516
- resource: defaultResource
4517
- } : null;
4519
+ function planReads(args) {
4520
+ const {client: client, clientForGdr: clientForGdr, instance: instance, overlay: overlay, visited: visited} = args, pending = [], ordered = [];
4521
+ for (const ref of collectWatchRefs(instance)) {
4522
+ if (visited.has(ref.id)) continue;
4523
+ visited.add(ref.id);
4524
+ const held = overlay?.get(ref.id);
4525
+ if (held !== void 0) ordered.push(held); else {
4526
+ const read = routeRead({
4527
+ defaultClient: client,
4528
+ clientForGdr: clientForGdr,
4529
+ defaultResource: instance.workflowResource,
4530
+ uri: ref.id,
4531
+ perspective: readsRaw(ref) ? "raw" : instance.perspective ?? DEFAULT_CONTENT_PERSPECTIVE
4532
+ });
4533
+ pending.push(read), ordered.push(read);
4534
+ }
4518
4535
  }
4519
- const routed = clientForGdr(parsed), doc = await readDoc({
4520
- client: routed,
4536
+ return {
4537
+ pending: pending,
4538
+ ordered: ordered
4539
+ };
4540
+ }
4541
+
4542
+ function isPendingRead(entry) {
4543
+ return "client" in entry;
4544
+ }
4545
+
4546
+ const SNAPSHOT_READ_BATCH_SIZE = 100, SNAPSHOT_READ_CONCURRENCY = 4, snapshotDocsQuery = "*[_id in $ids]";
4547
+
4548
+ function routeRead({defaultClient: defaultClient, clientForGdr: clientForGdr, defaultResource: defaultResource, uri: uri, perspective: perspective}) {
4549
+ const parsed = invariants.tryParseGdr(uri);
4550
+ return parsed === void 0 ? {
4551
+ client: defaultClient,
4552
+ id: uri,
4553
+ perspective: perspective,
4554
+ resource: defaultResource
4555
+ } : {
4556
+ client: clientForGdr(parsed),
4521
4557
  id: parsed.documentId,
4522
- perspective: perspective
4523
- });
4524
- return doc ? {
4525
- doc: doc,
4558
+ perspective: perspective,
4526
4559
  resource: invariants.resourceFromParsed(parsed)
4527
- } : null;
4560
+ };
4528
4561
  }
4529
4562
 
4530
- async function readDoc({client: client, id: id, perspective: perspective}) {
4531
- if (perspective === "raw") {
4532
- const doc2 = await client.getDocument(id) ?? null;
4533
- return doc2 ? doc2._type === invariants.WORKFLOW_INSTANCE_TYPE ? readInstanceDoc(doc2) : invariants.assertReadableModel(doc2) : null;
4563
+ async function readPending(reads) {
4564
+ const batches = groupReads(reads).flatMap(group => chunks(group.reads, SNAPSHOT_READ_BATCH_SIZE).map(batch => ({
4565
+ group: group,
4566
+ batch: batch
4567
+ }))), results = [];
4568
+ for (const wave of chunks(batches, SNAPSHOT_READ_CONCURRENCY)) results.push(...await Promise.all(wave.map(readBatch)));
4569
+ return new Map(results.flat());
4570
+ }
4571
+
4572
+ async function readBatch(args) {
4573
+ const {group: group, batch: batch} = args, ids = batch.map(read => read.id), docs = await group.client.fetch(snapshotDocsQuery, {
4574
+ ids: ids
4575
+ }, {
4576
+ perspective: group.perspective
4577
+ }), byId = new Map(docs.map(doc => [ doc._id, validateRawDoc(doc, group.perspective) ]));
4578
+ return batch.flatMap(read => {
4579
+ const doc = byId.get(read.id);
4580
+ return doc === void 0 ? [] : [ [ read, doc ] ];
4581
+ });
4582
+ }
4583
+
4584
+ function validateRawDoc(doc, perspective) {
4585
+ return perspective !== "raw" ? doc : doc._type === invariants.WORKFLOW_INSTANCE_TYPE ? readInstanceDoc(doc) : invariants.assertReadableModel(doc);
4586
+ }
4587
+
4588
+ function groupReads(reads) {
4589
+ const byClient = /* @__PURE__ */ new Map;
4590
+ for (const read of reads) {
4591
+ let clientGroups = byClient.get(read.client);
4592
+ clientGroups === void 0 && (clientGroups = /* @__PURE__ */ new Map, byClient.set(read.client, clientGroups));
4593
+ const key = JSON.stringify(read.perspective);
4594
+ let group = clientGroups.get(key);
4595
+ group === void 0 && (group = {
4596
+ client: read.client,
4597
+ perspective: read.perspective,
4598
+ reads: []
4599
+ }, clientGroups.set(key, group)), group.reads.push(read);
4534
4600
  }
4535
- const {query: query, params: params} = contentDocQuery(id);
4536
- return await client.fetch(query, params, {
4537
- perspective: perspective
4538
- }) ?? null;
4601
+ return [ ...byClient.values() ].flatMap(groups => [ ...groups.values() ]);
4602
+ }
4603
+
4604
+ function chunks(values, size) {
4605
+ return Array.from({
4606
+ length: Math.ceil(values.length / size)
4607
+ }, (_, index) => values.slice(index * size, (index + 1) * size));
4539
4608
  }
4540
4609
 
4541
4610
  function collectEntryDocUris(resolvedFieldEntries) {
@@ -4939,7 +5008,7 @@ function unwrapPatch(patch) {
4939
5008
  return typeof patch == "object" && patch !== null && RAW_PATCH in patch ? patch[RAW_PATCH] : patch;
4940
5009
  }
4941
5010
 
4942
- const wrapperCache = /* @__PURE__ */ new WeakMap, RAW_CLIENT = /* @__PURE__ */ Symbol("workflow-engine.raw-client");
5011
+ const wrapperCache = /* @__PURE__ */ new WeakMap, effectHandlerClientCache = /* @__PURE__ */ new WeakMap, RAW_CLIENT = /* @__PURE__ */ Symbol("workflow-engine.raw-client");
4943
5012
 
4944
5013
  function unwrapRequestTag(client) {
4945
5014
  let current = client;
@@ -4956,6 +5025,30 @@ function withRequestTag(client, fallback) {
4956
5025
  return byTag.set(fallback, wrapped), wrapped;
4957
5026
  }
4958
5027
 
5028
+ function effectHandlerClient(client) {
5029
+ const cached = effectHandlerClientCache.get(client);
5030
+ if (cached !== void 0) return cached;
5031
+ let tagged;
5032
+ if (client.withConfig === void 0) tagged = withRequestTag(client, REQUEST_TAG.effect); else {
5033
+ const prefix = clientRequestTagPrefix(client), effectTag = composeRequestTag(prefix, REQUEST_TAG.effect);
5034
+ tagged = client.withConfig({
5035
+ requestTagPrefix: effectTag
5036
+ });
5037
+ }
5038
+ return effectHandlerClientCache.set(client, tagged), tagged;
5039
+ }
5040
+
5041
+ function effectHandlerResolver(resolver) {
5042
+ return mapResourceClientResolver(resolver, effectHandlerClient);
5043
+ }
5044
+
5045
+ function mapResourceClientResolver(resolver, mapClient) {
5046
+ if (resolver !== void 0) return parsed => {
5047
+ const client = resolver(parsed);
5048
+ return client === void 0 ? void 0 : mapClient(client);
5049
+ };
5050
+ }
5051
+
4959
5052
  function isWorkflowsFamilyPrefix(prefix) {
4960
5053
  return prefix === void 0 ? !1 : prefix === "sanity.workflows" || prefix.startsWith("sanity.workflows.") || prefix === "sanity.workflows-mcp";
4961
5054
  }
@@ -4965,8 +5058,18 @@ function clientRequestTagPrefix(client) {
4965
5058
  return typeof probe.config == "function" ? probe.config().requestTagPrefix : void 0;
4966
5059
  }
4967
5060
 
5061
+ function composeRequestTag(prefix, tag) {
5062
+ if (prefix === void 0) return tag;
5063
+ const relative = isWorkflowsFamilyPrefix(prefix) ? stripWorkflowRoot(tag) : tag;
5064
+ return `${prefix}.${relative}`;
5065
+ }
5066
+
5067
+ function stripWorkflowRoot(tag) {
5068
+ return tag.startsWith("workflow.") ? tag.slice(9) : tag;
5069
+ }
5070
+
4968
5071
  function buildTaggedClient(client, fallback) {
4969
- const strip = isWorkflowsFamilyPrefix(clientRequestTagPrefix(unwrapRequestTag(client))), normalize = tag => strip && tag.startsWith("workflow.") ? tag.slice(9) : tag, stamp = options => options === void 0 ? {
5072
+ const strip = isWorkflowsFamilyPrefix(clientRequestTagPrefix(unwrapRequestTag(client))), normalize = tag => strip ? stripWorkflowRoot(tag) : tag, stamp = options => options === void 0 ? {
4970
5073
  tag: normalize(fallback)
4971
5074
  } : options.tag === void 0 ? {
4972
5075
  ...options,
@@ -4996,8 +5099,7 @@ function buildTaggedClient(client, fallback) {
4996
5099
  commit: options => target.commit(stamp(options))
4997
5100
  };
4998
5101
  return wrapped;
4999
- };
5000
- return {
5102
+ }, overrides = {
5001
5103
  [RAW_CLIENT]: unwrapRequestTag(client),
5002
5104
  fetch: (query, params, options) => client.fetch(query, params, stamp(options)),
5003
5105
  getDocument: (id, options) => client.getDocument(id, stamp(options)),
@@ -5014,6 +5116,14 @@ function buildTaggedClient(client, fallback) {
5014
5116
  request: opts => client.request(stamp(opts))
5015
5117
  } : {}
5016
5118
  };
5119
+ return new Proxy(client, {
5120
+ has: (target, property) => Object.hasOwn(overrides, property) || property in target,
5121
+ get: (target, property) => {
5122
+ if (Object.hasOwn(overrides, property)) return Reflect.get(overrides, property, overrides);
5123
+ const value = Reflect.get(target, property, target);
5124
+ return typeof value == "function" ? value.bind(target) : value;
5125
+ }
5126
+ });
5017
5127
  }
5018
5128
 
5019
5129
  const pinCache = /* @__PURE__ */ new WeakMap, enginePinned = /* @__PURE__ */ new WeakSet;
@@ -5028,10 +5138,7 @@ function pinApiVersion(client) {
5028
5138
  }
5029
5139
 
5030
5140
  function taggedResolver(resolver, fallback) {
5031
- if (resolver !== void 0) return parsed => {
5032
- const client = resolver(parsed);
5033
- return client === void 0 ? void 0 : withRequestTag(pinApiVersion(client), fallback);
5034
- };
5141
+ return mapResourceClientResolver(resolver, client => withRequestTag(pinApiVersion(client), fallback));
5035
5142
  }
5036
5143
 
5037
5144
  function taggedScope(args, tag) {
@@ -5061,16 +5168,20 @@ function guardsForResource(client) {
5061
5168
  });
5062
5169
  }
5063
5170
 
5064
- function instanceGuardQuery(instanceId) {
5171
+ function instancesGuardQuery(instanceIds) {
5065
5172
  return {
5066
- query: "*[_type == $guardType && sourceInstanceId == $instanceId]",
5173
+ query: "*[_type == $guardType && sourceInstanceId in $instanceIds] | order(_id asc)",
5067
5174
  params: {
5068
5175
  guardType: GUARD_DOC_TYPE,
5069
- instanceId: instanceId
5176
+ instanceIds: [ ...instanceIds ]
5070
5177
  }
5071
5178
  };
5072
5179
  }
5073
5180
 
5181
+ function instanceGuardQuery(instanceId) {
5182
+ return instancesGuardQuery([ instanceId ]);
5183
+ }
5184
+
5074
5185
  function verdictGuardsForInstance(client, instanceId) {
5075
5186
  const {query: query, params: params} = instanceGuardQuery(instanceId);
5076
5187
  return fetchGuards({
@@ -5172,7 +5283,7 @@ function dedupById(guards) {
5172
5283
  return [ ...new Map(guards.map(g => [ g._id, g ])).values() ].sort((a, b) => a._id.localeCompare(b._id));
5173
5284
  }
5174
5285
 
5175
- const GUARD_OWNER = "robot:workflow-engine";
5286
+ const GUARD_OWNER = "robot:workflow-engine", DELETE_CHUNK_SIZE = 200;
5176
5287
 
5177
5288
  function resolveGuardRoute(guard, ctx) {
5178
5289
  const targets = resolveIdRefTargets(guard.match.idRefs, ctx);
@@ -5225,10 +5336,9 @@ function resolveGuard({guard: guard, instance: instance, stageName: stageName, n
5225
5336
  };
5226
5337
  }
5227
5338
 
5228
- async function upsertGuard(client, doc) {
5229
- if (!await client.getDocument(doc._id, {
5230
- tag: REQUEST_TAG.guardDeploy
5231
- })) {
5339
+ async function upsertGuard(args) {
5340
+ const {client: client, doc: doc, exists: exists} = args;
5341
+ if (!exists) {
5232
5342
  await client.create(doc, {
5233
5343
  ...SYNC_COMMIT,
5234
5344
  tag: REQUEST_TAG.guardDeploy
@@ -5254,7 +5364,8 @@ function resolvedStageGuards(args) {
5254
5364
  });
5255
5365
  resolved !== null && out.push({
5256
5366
  client: args.clientForGdr(resolved.routeGdr),
5257
- doc: resolved.doc
5367
+ resourceKey: invariants.resourceGdr(invariants.resourceFromParsed(resolved.routeGdr)),
5368
+ value: resolved.doc
5258
5369
  });
5259
5370
  }
5260
5371
  return out;
@@ -5271,12 +5382,54 @@ function resolvedStageGuardRoutes(args) {
5271
5382
  const route = resolveGuardRoute(guard, ctx);
5272
5383
  route !== null && out.push({
5273
5384
  client: args.clientForGdr(route.routeGdr),
5274
- guardId: route.guardId
5385
+ resourceKey: invariants.resourceGdr(invariants.resourceFromParsed(route.routeGdr)),
5386
+ value: route.guardId
5275
5387
  });
5276
5388
  }
5277
5389
  return out;
5278
5390
  }
5279
5391
 
5392
+ function groupByResource(routed) {
5393
+ const groups = /* @__PURE__ */ new Map;
5394
+ for (const item of routed) {
5395
+ const existing = groups.get(item.resourceKey);
5396
+ if (existing !== void 0) {
5397
+ existing.values.push(item.value);
5398
+ continue;
5399
+ }
5400
+ groups.set(item.resourceKey, {
5401
+ client: item.client,
5402
+ resourceKey: item.resourceKey,
5403
+ values: [ item.value ]
5404
+ });
5405
+ }
5406
+ return [ ...groups.values() ];
5407
+ }
5408
+
5409
+ async function existingGuardIds(groups) {
5410
+ const entries = await Promise.all(groups.map(async ({client: client, resourceKey: resourceKey, values: values}) => {
5411
+ const ids = values.map(doc => doc._id), existing = await client.fetch("*[_id in $ids]._id", {
5412
+ ids: ids
5413
+ }, {
5414
+ tag: REQUEST_TAG.guardDeploy
5415
+ });
5416
+ return [ resourceKey, new Set(existing) ];
5417
+ }));
5418
+ return new Map(entries);
5419
+ }
5420
+
5421
+ async function observedGuards(groups) {
5422
+ const entries = await Promise.all(groups.map(async ({client: client, resourceKey: resourceKey, values: ids}) => {
5423
+ const guards = await client.fetch("*[_id in $ids]{_id, predicate, _rev}", {
5424
+ ids: ids
5425
+ }, {
5426
+ tag: REQUEST_TAG.guardRetract
5427
+ });
5428
+ return [ resourceKey, new Map(guards.map(guard => [ guard._id, guard ])) ];
5429
+ }));
5430
+ return new Map(entries);
5431
+ }
5432
+
5280
5433
  async function committedInstance(args) {
5281
5434
  return getInstanceDocument(args.client, args.instance._id);
5282
5435
  }
@@ -5284,10 +5437,15 @@ async function committedInstance(args) {
5284
5437
  async function deployStageGuards(args) {
5285
5438
  const live = await committedInstance(args);
5286
5439
  if (live === void 0 || live.currentStage !== args.stageName || live.abortedAt !== void 0) return;
5440
+ const routed = resolvedStageGuards(args), existingByResource = await existingGuardIds(groupByResource(routed));
5287
5441
  let deployed = 0;
5288
- for (const {client: client, doc: doc} of resolvedStageGuards(args)) {
5442
+ for (const {client: client, resourceKey: resourceKey, value: doc} of routed) {
5289
5443
  try {
5290
- await upsertGuard(client, doc);
5444
+ await upsertGuard({
5445
+ client: client,
5446
+ doc: doc,
5447
+ exists: existingByResource.get(resourceKey)?.has(doc._id) ?? !1
5448
+ });
5291
5449
  } catch (cause) {
5292
5450
  throw deployed > 0 ? new PartialGuardDeployError({
5293
5451
  stageName: args.stageName,
@@ -5300,13 +5458,11 @@ async function deployStageGuards(args) {
5300
5458
  }
5301
5459
 
5302
5460
  async function retractStageGuards(args) {
5303
- const observed = [];
5304
- for (const {client: client, guardId: guardId} of resolvedStageGuardRoutes(args)) {
5305
- const guard = await client.getDocument(guardId, {
5306
- tag: REQUEST_TAG.guardRetract
5307
- });
5461
+ const observed = [], routed = resolvedStageGuardRoutes(args), guardsByResource = await observedGuards(groupByResource(routed));
5462
+ for (const {client: client, resourceKey: resourceKey, value: guardId} of routed) {
5463
+ const guard = guardsByResource.get(resourceKey)?.get(guardId);
5308
5464
  if (guard) {
5309
- if (guard._rev === void 0) throw new Error(`Cannot retract guard ${guardId}: persisted document has no revision`);
5465
+ if (guard._rev === void 0 || guard._rev === null) throw new Error(`Cannot retract guard ${guardId}: persisted document has no revision`);
5310
5466
  observed.push({
5311
5467
  client: client,
5312
5468
  guardId: guardId,
@@ -5337,10 +5493,11 @@ async function deleteOrphanedDefinitionGuards(args) {
5337
5493
  let count = 0;
5338
5494
  for (const {client: resourceClient, guards: guards} of perClient) {
5339
5495
  const own = guards.filter(guard => guard.sourceInstanceId.startsWith(ownPartitionPrefix));
5340
- if (own.length === 0) continue;
5341
- const tx = resourceClient.transaction();
5342
- for (const guard of own) tx.delete(guard._id);
5343
- await tx.commit(), count += own.length;
5496
+ if (own.length !== 0) for (let start = 0; start < own.length; start += DELETE_CHUNK_SIZE) {
5497
+ const chunk = own.slice(start, start + DELETE_CHUNK_SIZE), tx = resourceClient.transaction();
5498
+ for (const guard of chunk) tx.delete(guard._id);
5499
+ await tx.commit(), count += chunk.length;
5500
+ }
5344
5501
  }
5345
5502
  return count;
5346
5503
  }
@@ -7773,26 +7930,26 @@ async function subjectResourceGrants(args) {
7773
7930
  async function evaluateInstance(args) {
7774
7931
  const {client: client, tag: tag, workflowResource: workflowResource, instanceId: instanceId, resourceClients: resourceClients} = args, now = (args.clock ?? wallClock)();
7775
7932
  invariants.validateTag(tag);
7776
- const {actor: actor, grants: grants} = await resolveAccess(client, {
7933
+ const [access, instance] = await Promise.all([ resolveAccess(client, {
7777
7934
  ...args.grantsFromPath !== void 0 ? {
7778
7935
  grantsFromPath: args.grantsFromPath
7779
7936
  } : {}
7780
- }), instance = await reload({
7937
+ }), reload({
7781
7938
  client: client,
7782
7939
  instanceId: instanceId,
7783
7940
  tag: tag
7784
- }), definition = invariants.parseDefinitionSnapshot(instance), clientForGdr = buildClientForGdr({
7941
+ }) ]), {actor: actor, grants: grants} = access, definition = invariants.parseDefinitionSnapshot(instance), clientForGdr = buildClientForGdr({
7785
7942
  client: client,
7786
7943
  workflowResource: workflowResource,
7787
7944
  resourceClients: resourceClients
7788
- }), snapshot = await hydrateSnapshot({
7945
+ }), [snapshot, guards, resourceGrants] = await Promise.all([ hydrateSnapshot({
7789
7946
  client: client,
7790
7947
  clientForGdr: clientForGdr,
7791
7948
  instance: instance
7792
- }), guards = await verdictGuardsForInstance(client, instance._id), resourceGrants = await subjectResourceGrants({
7949
+ }), verdictGuardsForInstance(client, instance._id), subjectResourceGrants({
7793
7950
  clientForGdr: clientForGdr,
7794
7951
  instance: instance
7795
- });
7952
+ }) ]);
7796
7953
  return evaluateFromSnapshot({
7797
7954
  instance: instance,
7798
7955
  definition: definition,
@@ -8322,14 +8479,14 @@ function documentPrefilter(documents, params) {
8322
8479
 
8323
8480
  function documentArm(filter, params) {
8324
8481
  if (filter.documents === void 0 && filter.document === void 0) return;
8325
- const documents = [ ...filter.documents ?? [], ...filter.document !== void 0 ? [ filter.document ] : [] ];
8482
+ const documents = [ .../* @__PURE__ */ new Set([ ...filter.documents ?? [], ...filter.document !== void 0 ? [ filter.document ] : [] ]) ].sort();
8326
8483
  return documentPrefilter(documents, params);
8327
8484
  }
8328
8485
 
8329
8486
  function idsArm(filter, params) {
8330
8487
  if (filter.ids !== void 0) {
8331
8488
  for (const id of filter.ids) if (invariants.isGdrUri(id)) throw new invariants.ContractViolationError(`instancesQuery: every id must be a bare instance document id (an instance's _id is never resource-qualified); got a GDR URI: ${JSON.stringify(id)}`);
8332
- return params.ids = [ ...filter.ids ], "_id in $ids";
8489
+ return params.ids = [ ...new Set(filter.ids) ].sort(), "_id in $ids";
8333
8490
  }
8334
8491
  }
8335
8492
 
@@ -10540,7 +10697,7 @@ function isCancelledCompletion(data) {
10540
10697
  }
10541
10698
 
10542
10699
  async function drainEffectsInternal(args) {
10543
- const {tag: tag, workflowResource: workflowResource, instanceId: instanceId, effectHandlers: effectHandlers, missingHandler: missingHandler, logger: logger, handlerClient: handlerClient, handlerResourceClients: handlerResourceClients} = args, cascadeTelemetry = drainCascadeTelemetry(args.telemetry), {client: client, resourceClients: resourceClients} = taggedScope(args, REQUEST_TAG.drain), leaseMs = args.leaseMs ?? DEFAULT_EFFECT_LEASE_MS, clock = args.clock ?? wallClock, routeGdr = buildClientForGdr({
10700
+ const {tag: tag, workflowResource: workflowResource, instanceId: instanceId, effectHandlers: effectHandlers, missingHandler: missingHandler, logger: logger, handlerClient: rawHandlerClient, handlerResourceClients: rawHandlerResourceClients} = args, cascadeTelemetry = drainCascadeTelemetry(args.telemetry), {client: client, resourceClients: resourceClients} = taggedScope(args, REQUEST_TAG.drain), leaseMs = args.leaseMs ?? DEFAULT_EFFECT_LEASE_MS, clock = args.clock ?? wallClock, handlerClient = effectHandlerClient(rawHandlerClient), handlerResourceClients = effectHandlerResolver(rawHandlerResourceClients), routeGdr = buildClientForGdr({
10544
10701
  client: handlerClient,
10545
10702
  workflowResource: workflowResource,
10546
10703
  resourceClients: handlerResourceClients
@@ -10906,22 +11063,30 @@ function createInstanceSession(args) {
10906
11063
  }, tickScope = opScope(REQUEST_TAG.tick), fireScope = opScope(REQUEST_TAG.fireAction), editScope = opScope(REQUEST_TAG.editField), evalScope = opScope(REQUEST_TAG.evaluate);
10907
11064
  let overlay = /* @__PURE__ */ new Map;
10908
11065
  const previews = /* @__PURE__ */ new Map;
10909
- let heldGuards = [], committing = !1, buffered, deferredUpdateError;
10910
- const selfUri = () => invariants.gdrFromResource(instance.workflowResource, instance._id), applyUpdate = docs => {
10911
- const next = /* @__PURE__ */ new Map;
10912
- for (const ld of docs) {
10913
- const owned = {
10914
- doc: structuredClone(ld.doc),
10915
- resource: ld.resource
10916
- }, uri = invariants.gdrFromResource(owned.resource, owned.doc._id);
10917
- if (uri === selfUri()) {
10918
- const rawStamp = owned.doc._updatedAt;
10919
- !invariants.isParseableInstant(rawStamp) || owned.doc._type !== invariants.WORKFLOW_INSTANCE_TYPE ? asHeldInstance(owned.doc) : Date.parse(rawStamp) > Date.parse(instance._updatedAt) && (instance = asHeldInstance(owned.doc));
10920
- continue;
10921
- }
10922
- next.set(uri, owned);
11066
+ let heldGuards = [], committing = !1, buffered;
11067
+ const bufferedDocuments = /* @__PURE__ */ new Map;
11068
+ let deferredUpdateError;
11069
+ const flushDeferredError = () => {
11070
+ if (deferredUpdateError === void 0) return;
11071
+ const failure = deferredUpdateError;
11072
+ throw deferredUpdateError = void 0, failure;
11073
+ }, selfUri = () => invariants.gdrFromResource(instance.workflowResource, instance._id), applyDocument = (ld, target) => {
11074
+ const owned = {
11075
+ doc: structuredClone(ld.doc),
11076
+ resource: ld.resource
11077
+ }, uri = invariants.gdrFromResource(owned.resource, owned.doc._id);
11078
+ if (uri === selfUri()) {
11079
+ const rawStamp = owned.doc._updatedAt;
11080
+ !invariants.isParseableInstant(rawStamp) || owned.doc._type !== invariants.WORKFLOW_INSTANCE_TYPE ? asHeldInstance(owned.doc) : Date.parse(rawStamp) > Date.parse(instance._updatedAt) && (instance = asHeldInstance(owned.doc));
11081
+ return;
10923
11082
  }
11083
+ target.set(uri, owned);
11084
+ }, applyUpdate = docs => {
11085
+ const next = /* @__PURE__ */ new Map;
11086
+ for (const ld of docs) applyDocument(ld, next);
10924
11087
  overlay = next;
11088
+ }, applyDocumentUpdate = doc => {
11089
+ applyDocument(doc, overlay);
10925
11090
  }, access = () => resolveAccess(client, {
10926
11091
  ...args.grantsFromPath !== void 0 ? {
10927
11092
  grantsFromPath: args.grantsFromPath
@@ -11047,6 +11212,12 @@ function createInstanceSession(args) {
11047
11212
  deferredUpdateError = err;
11048
11213
  }
11049
11214
  }
11215
+ for (const next of bufferedDocuments.values()) try {
11216
+ applyDocumentUpdate(next);
11217
+ } catch (err) {
11218
+ deferredUpdateError = err;
11219
+ }
11220
+ bufferedDocuments.clear();
11050
11221
  }
11051
11222
  };
11052
11223
  return {
@@ -11054,15 +11225,26 @@ function createInstanceSession(args) {
11054
11225
  return subscriptionDocumentsForInstance(instance);
11055
11226
  },
11056
11227
  update(docs) {
11057
- if (committing) buffered = docs; else try {
11228
+ if (committing) buffered = docs, bufferedDocuments.clear(); else try {
11058
11229
  applyUpdate(docs);
11059
11230
  } catch (err) {
11060
11231
  throw deferredUpdateError = void 0, err;
11061
11232
  }
11062
- if (deferredUpdateError !== void 0) {
11063
- const failure = deferredUpdateError;
11064
- throw deferredUpdateError = void 0, failure;
11233
+ flushDeferredError();
11234
+ },
11235
+ updateDocument(doc) {
11236
+ if (committing) {
11237
+ const uri = invariants.gdrFromResource(doc.resource, doc.doc._id);
11238
+ bufferedDocuments.set(uri, {
11239
+ doc: structuredClone(doc.doc),
11240
+ resource: doc.resource
11241
+ });
11242
+ } else try {
11243
+ applyDocumentUpdate(doc);
11244
+ } catch (err) {
11245
+ throw deferredUpdateError = void 0, err;
11065
11246
  }
11247
+ flushDeferredError();
11066
11248
  },
11067
11249
  updateGuards(guards) {
11068
11250
  heldGuards = guards.map(guard => parseGuardDocument(structuredClone(guard)));
@@ -12376,10 +12558,14 @@ exports.instanceGuardQuery = instanceGuardQuery;
12376
12558
 
12377
12559
  exports.instanceWatchesDocument = instanceWatchesDocument;
12378
12560
 
12561
+ exports.instancesGuardQuery = instancesGuardQuery;
12562
+
12379
12563
  exports.instancesQuery = instancesQuery;
12380
12564
 
12381
12565
  exports.isClaimExpired = isClaimExpired;
12382
12566
 
12567
+ exports.isClientProjectUser = isClientProjectUser;
12568
+
12383
12569
  exports.isDefinitionApplicable = isDefinitionApplicable;
12384
12570
 
12385
12571
  exports.isFilterScopedOut = isFilterScopedOut;