@jini-ai/cms 0.4.1 → 0.5.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/content-types/lifecycle.d.ts +1 -1
  3. package/dist/content-types/lifecycle.js +6 -2
  4. package/dist/content-types/types.d.ts +11 -0
  5. package/dist/content-types/write-service.d.ts +6 -0
  6. package/dist/content-types/write-service.js +7 -0
  7. package/dist/entries/errors.d.ts +7 -0
  8. package/dist/entries/errors.js +6 -0
  9. package/dist/entries/field-validation.d.ts +3 -0
  10. package/dist/entries/index.d.ts +2 -2
  11. package/dist/entries/index.js +1 -1
  12. package/dist/entries/repo.memory.d.ts +2 -2
  13. package/dist/entries/repo.memory.js +10 -1
  14. package/dist/entries/types.d.ts +2 -0
  15. package/dist/entries/write-service.d.ts +19 -8
  16. package/dist/entries/write-service.js +88 -54
  17. package/dist/media/blob-store.fs.d.ts +4 -0
  18. package/dist/media/blob-store.fs.js +27 -0
  19. package/dist/media/blob-store.memory.d.ts +3 -0
  20. package/dist/media/blob-store.memory.js +3 -0
  21. package/dist/media/created-by.d.ts +15 -0
  22. package/dist/media/created-by.js +20 -0
  23. package/dist/media/index.d.ts +1 -0
  24. package/dist/media/index.js +1 -0
  25. package/dist/media/media-service.d.ts +1 -1
  26. package/dist/media/media-service.js +1 -0
  27. package/dist/media/ports.d.ts +12 -2
  28. package/dist/media/repo.memory.js +2 -1
  29. package/dist/media/tool-registrations.js +1 -0
  30. package/dist/media/types.d.ts +4 -0
  31. package/dist/navigation/index.d.ts +2 -2
  32. package/dist/navigation/index.js +2 -2
  33. package/dist/navigation/menu-service.d.ts +28 -16
  34. package/dist/navigation/menu-service.js +90 -65
  35. package/dist/navigation/repo.memory.d.ts +46 -2
  36. package/dist/navigation/repo.memory.js +28 -1
  37. package/dist/navigation/types.d.ts +5 -0
  38. package/dist/taxonomy/write-service.d.ts +4 -2
  39. package/dist/taxonomy/write-service.js +8 -3
  40. package/dist/trash/write-service.js +19 -12
  41. package/package.json +2 -2
@@ -408,7 +408,7 @@ export async function assignTerms(required, _optional = {}) {
408
408
  * authorize -> validateAssignmentTarget -> write -> stamp -> outbox chokepoint as `assignTerms`,
409
409
  * and — like `assignTerms` — never produces a `taxonomy_revisions` row ( disclosed
410
410
  * narrowing applies equally to removal). Idempotent: unassigning a row that isn't there is a no-op,
411
- * not an error (`remove`'s 0-vs-1 return is intentionally not surfaced to the caller). Chosen over
411
+ * not an error. Only removed ids are returned, stamped, and emitted; an entirely absent set is a no-op. Chosen over
412
412
  * a "set terms" replace call — see the taxonomy plan's "What the docs settle" #3 for why replace
413
413
  * risks silently erasing a concurrent assignment.
414
414
  * See docs/decisions/DR-005-ordered-taxonomy-validation.md.
@@ -417,19 +417,24 @@ export async function unassignTerms(required, _optional = {}) {
417
417
  const { deps, principalId, contentType, contentId, termIds } = required;
418
418
  await authorizeTaxonomyManage(deps, principalId);
419
419
  await validateAssignmentTarget(deps, contentType, contentId, termIds);
420
+ const removedTermIds = [];
420
421
  for (const termId of termIds) {
421
- await deps.entryTerms.remove({ contentType, contentId, termId });
422
+ if (await deps.entryTerms.remove({ contentType, contentId, termId }) > 0)
423
+ removedTermIds.push(termId);
422
424
  }
425
+ if (removedTermIds.length === 0)
426
+ return { removedTermIds };
423
427
  const now = kernelNowIso({ clock: deps.clock });
424
428
  await deps.stampWatermark({});
425
429
  await deps.outbox.enqueue({ event: {
426
430
  name: "taxonomy.terms_unassigned",
427
431
  contentType,
428
432
  contentId,
429
- termIds,
433
+ termIds: removedTermIds,
430
434
  actorId: principalId,
431
435
  occurredAt: now,
432
436
  } });
437
+ return { removedTermIds };
433
438
  }
434
439
  /** Content-deletion event subscriber. Best-effort (a missed event leaves an orphaned
435
440
  * `entry_terms` row that is inert on read; a periodic/boot reconciliation sweep — modeled by
@@ -95,12 +95,16 @@ export function createTrashService(deps, optional = {}) {
95
95
  priorMarker: marker.priorMarker ?? trashOptional.priorMarker ?? null,
96
96
  };
97
97
  await deps.repo.insert({ row });
98
- await notifyChanged(optional, {
99
- workspaceId: required.workspaceId,
100
- entityType: required.entityType,
101
- entityId: required.entityId,
102
- change: "trash",
103
- });
98
+ // A successful noop may repair a missing index, but did not change the domain marker.
99
+ // Repeated removal must not emit another recovery watermark or outbox event (F1487).
100
+ if (!marker.noop) {
101
+ await notifyChanged(optional, {
102
+ workspaceId: required.workspaceId,
103
+ entityType: required.entityType,
104
+ entityId: required.entityId,
105
+ change: "trash",
106
+ });
107
+ }
104
108
  return marker;
105
109
  }
106
110
  });
@@ -135,12 +139,15 @@ export function createTrashService(deps, optional = {}) {
135
139
  if (!marker.ok)
136
140
  return marker.reason === "not-found" ? "not-found" : "version-changed";
137
141
  await deps.repo.deleteById({ workspaceId: required.workspaceId, id: row.id });
138
- await notifyChanged(optional, {
139
- workspaceId: required.workspaceId,
140
- entityType: required.entityType,
141
- entityId: required.entityId,
142
- change: "restore",
143
- });
142
+ // An already-clear marker still needs index cleanup, without a duplicate notification.
143
+ if (!marker.noop) {
144
+ await notifyChanged(optional, {
145
+ workspaceId: required.workspaceId,
146
+ entityType: required.entityType,
147
+ entityId: required.entityId,
148
+ change: "restore",
149
+ });
150
+ }
144
151
  return "restored";
145
152
  }
146
153
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jini-ai/cms",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Content-model capability for Jini-hosted products: framework-free contracts, ports, and domain services in `/core` and per-domain subpaths; Node-bound adapters in domain-specific subpaths.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -132,7 +132,7 @@
132
132
  "@jini-ai/http-kit": "0.4.0"
133
133
  },
134
134
  "dependencies": {
135
- "@jini-ai/core": "0.4.0"
135
+ "@jini-ai/core": "^0.4.1"
136
136
  },
137
137
  "peerDependencies": {
138
138
  "sharp": "^0.35.3",