@cardinali-studio/adminkit 2.2.13 → 2.2.14

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.
@@ -304,8 +304,10 @@ export class D1ContentRepository {
304
304
  .bind(input.releaseId, input.requestChecksum, input.manifestChecksum, input.schemaPackageVersion, input.materializerVersion, input.authorSubject, input.now, state.current_release_id, ...selected.flatMap((entry) => [entry.documentId, entry.revisionId]));
305
305
  const statements = [candidate];
306
306
  for (const [documentId, revisionId] of manifest)
307
- statements.push(this.db.prepare("INSERT INTO content_release_entries (release_id, document_id, revision_id) SELECT ?, ?, ? WHERE EXISTS (SELECT 1 FROM content_revisions WHERE document_id = ? AND revision_id = ?)")
308
- .bind(input.releaseId, documentId, revisionId, documentId, revisionId));
307
+ statements.push(this.db.prepare(`INSERT INTO content_release_entries (release_id, document_id, revision_id)
308
+ SELECT ?, ?, ? WHERE EXISTS (SELECT 1 FROM content_releases WHERE release_id = ? AND status = 'publishing')
309
+ AND EXISTS (SELECT 1 FROM content_revisions WHERE document_id = ? AND revision_id = ?)`)
310
+ .bind(input.releaseId, documentId, revisionId, input.releaseId, documentId, revisionId));
309
311
  const results = await this.db.batch(statements);
310
312
  if (results[0]?.meta?.changes !== 1)
311
313
  throw new Error("stale_revision");
@@ -362,14 +364,22 @@ export class D1ContentRepository {
362
364
  }
363
365
  const candidateIndex = statements.length;
364
366
  statements.push(candidate);
367
+ /* Every dependent write is guarded by the exact candidate. D1 batches are
368
+ * atomic on SQL errors, but a guarded INSERT that affects zero rows is not
369
+ * an error: without this predicate, a busy lane made these FK inserts
370
+ * reference a candidate that was never created and hid the intended
371
+ * deployment_lane_busy conflict behind a generic persistence failure. */
365
372
  for (const [documentId, revisionId] of manifest)
366
- statements.push(this.db.prepare("INSERT INTO content_release_entries (release_id, document_id, revision_id) SELECT ?, ?, ? WHERE EXISTS (SELECT 1 FROM content_revisions WHERE document_id = ? AND revision_id = ?)")
367
- .bind(input.releaseId, documentId, revisionId, documentId, revisionId));
373
+ statements.push(this.db.prepare(`INSERT INTO content_release_entries (release_id, document_id, revision_id)
374
+ SELECT ?, ?, ? WHERE EXISTS (SELECT 1 FROM content_releases WHERE release_id = ? AND status = 'publishing')
375
+ AND EXISTS (SELECT 1 FROM content_revisions WHERE document_id = ? AND revision_id = ?)`)
376
+ .bind(input.releaseId, documentId, revisionId, input.releaseId, documentId, revisionId));
368
377
  if (input.siteAccessVersionId)
369
378
  statements.push(this.db.prepare(`INSERT INTO content_release_site_access (release_id, version_id)
370
- SELECT ?, ? WHERE EXISTS (SELECT 1 FROM site_access_versions version JOIN site_access_state access_state ON access_state.singleton = 1
371
- WHERE version.version_id = ? AND version.state IN ('draft','active') AND version.version_id IN (access_state.draft_version_id, access_state.active_version_id))`)
372
- .bind(input.releaseId, input.siteAccessVersionId, input.siteAccessVersionId));
379
+ SELECT ?, ? WHERE EXISTS (SELECT 1 FROM content_releases WHERE release_id = ? AND status = 'publishing')
380
+ AND EXISTS (SELECT 1 FROM site_access_versions version JOIN site_access_state access_state ON access_state.singleton = 1
381
+ WHERE version.version_id = ? AND version.state IN ('draft','active') AND version.version_id IN (access_state.draft_version_id, access_state.active_version_id))`)
382
+ .bind(input.releaseId, input.siteAccessVersionId, input.releaseId, input.siteAccessVersionId));
373
383
  for (const operationId of mediaOperationIds) {
374
384
  /* `instr` is intentionally evaluated against the frozen release entries,
375
385
  * not drafts or a mutable current pointer. A URL is JSON-escaped neither
@@ -384,8 +394,9 @@ export class D1ContentRepository {
384
394
  AND revision.payload_json IS NOT NULL AND instr(revision.payload_json, '/media/' || source.source_key) > 0)`)
385
395
  .bind(input.now, operationId, input.releaseId));
386
396
  statements.push(this.db.prepare(`INSERT INTO content_release_media_operations (release_id, operation_id)
387
- SELECT ?, ? WHERE EXISTS (SELECT 1 FROM media_operations WHERE operation_id = ? AND state = 'bound')`)
388
- .bind(input.releaseId, operationId, operationId));
397
+ SELECT ?, ? WHERE EXISTS (SELECT 1 FROM content_releases WHERE release_id = ? AND status = 'publishing')
398
+ AND EXISTS (SELECT 1 FROM media_operations WHERE operation_id = ? AND state = 'bound')`)
399
+ .bind(input.releaseId, operationId, input.releaseId, operationId));
389
400
  }
390
401
  statements.push(this.db.prepare(`INSERT INTO production_deployment_attempts
391
402
  (attempt_id, intent_kind, requested_release_id, requested_code_sha, code_sha, release_id, expected_fingerprint, phase, active_slot, created_at, updated_at)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardinali-studio/adminkit",
3
- "version": "2.2.13",
3
+ "version": "2.2.14",
4
4
  "type": "module",
5
5
  "description": "Motore riusabile di un pannello di amministrazione per siti Hugo su Cloudflare Pages: codec del front matter guidato dallo schema, form React, libreria media su R2. NIENTE di specifico di un sito qui dentro.",
6
6
  "license": "UNLICENSED",