fundus 0.16.0 → 0.17.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 (194) hide show
  1. package/README.md +37 -1
  2. package/build/client/_app/immutable/assets/{AssetSourceMenu.B5fzYfym.css → AssetSourceMenu.Buj2UxUg.css} +1 -1
  3. package/build/client/_app/immutable/assets/AssetSourceMenu.Buj2UxUg.css.br +0 -0
  4. package/build/client/_app/immutable/assets/{AssetSourceMenu.B5fzYfym.css.gz → AssetSourceMenu.Buj2UxUg.css.gz} +0 -0
  5. package/build/client/_app/immutable/chunks/{D6c2mfMB.js → 5fq8ySvR.js} +1 -1
  6. package/build/client/_app/immutable/chunks/5fq8ySvR.js.br +0 -0
  7. package/build/client/_app/immutable/chunks/5fq8ySvR.js.gz +0 -0
  8. package/build/client/_app/immutable/chunks/BcVZHIBD.js +1 -0
  9. package/build/client/_app/immutable/chunks/BcVZHIBD.js.br +0 -0
  10. package/build/client/_app/immutable/chunks/BcVZHIBD.js.gz +0 -0
  11. package/build/client/_app/immutable/chunks/DXiUbZ9S.js +2 -0
  12. package/build/client/_app/immutable/chunks/DXiUbZ9S.js.br +0 -0
  13. package/build/client/_app/immutable/chunks/DXiUbZ9S.js.gz +0 -0
  14. package/build/client/_app/immutable/chunks/H5KnxQLQ.js +108 -0
  15. package/build/client/_app/immutable/chunks/H5KnxQLQ.js.br +0 -0
  16. package/build/client/_app/immutable/chunks/H5KnxQLQ.js.gz +0 -0
  17. package/build/client/_app/immutable/chunks/{B97y-eFA.js → jEVVu5G9.js} +1 -1
  18. package/build/client/_app/immutable/chunks/jEVVu5G9.js.br +0 -0
  19. package/build/client/_app/immutable/chunks/jEVVu5G9.js.gz +0 -0
  20. package/build/client/_app/immutable/entry/{app.V1MmmICK.js → app.Z9n2k33I.js} +2 -2
  21. package/build/client/_app/immutable/entry/app.Z9n2k33I.js.br +0 -0
  22. package/build/client/_app/immutable/entry/app.Z9n2k33I.js.gz +0 -0
  23. package/build/client/_app/immutable/entry/start.DGo1uhI2.js +1 -0
  24. package/build/client/_app/immutable/entry/start.DGo1uhI2.js.br +0 -0
  25. package/build/client/_app/immutable/entry/start.DGo1uhI2.js.gz +0 -0
  26. package/build/client/_app/immutable/nodes/0.DiZpkhxP.js +1 -0
  27. package/build/client/_app/immutable/nodes/0.DiZpkhxP.js.br +0 -0
  28. package/build/client/_app/immutable/nodes/0.DiZpkhxP.js.gz +0 -0
  29. package/build/client/_app/immutable/nodes/{1.BqoPRvUU.js → 1.rgoSlmPu.js} +1 -1
  30. package/build/client/_app/immutable/nodes/1.rgoSlmPu.js.br +2 -0
  31. package/build/client/_app/immutable/nodes/1.rgoSlmPu.js.gz +0 -0
  32. package/build/client/_app/immutable/nodes/2.Dzp3a3be.js +3 -0
  33. package/build/client/_app/immutable/nodes/2.Dzp3a3be.js.br +0 -0
  34. package/build/client/_app/immutable/nodes/2.Dzp3a3be.js.gz +0 -0
  35. package/build/client/_app/immutable/nodes/3.mSsa2TDI.js +1 -0
  36. package/build/client/_app/immutable/nodes/3.mSsa2TDI.js.br +0 -0
  37. package/build/client/_app/immutable/nodes/3.mSsa2TDI.js.gz +0 -0
  38. package/build/client/_app/immutable/nodes/4.4oRGPYr4.js +2 -0
  39. package/build/client/_app/immutable/nodes/4.4oRGPYr4.js.br +0 -0
  40. package/build/client/_app/immutable/nodes/4.4oRGPYr4.js.gz +0 -0
  41. package/build/client/_app/version.json +1 -1
  42. package/build/client/_app/version.json.br +0 -0
  43. package/build/client/_app/version.json.gz +0 -0
  44. package/build/handler.js +7 -7
  45. package/build/index.js +7 -7
  46. package/build/server/chunks/chunks/{context.js-BXaBgcZT.js → context.js-Dh0ibRIF.js} +196 -7
  47. package/build/server/chunks/chunks/context.js-Dh0ibRIF.js.map +1 -0
  48. package/build/server/chunks/chunks/{contract.js-IY1F-HE_.js → contract.js-_LTMZBo2.js} +68 -6
  49. package/build/server/chunks/chunks/contract.js-_LTMZBo2.js.map +1 -0
  50. package/build/server/chunks/chunks/{core.js-D1sYC_78.js → core.js-CTyKR4bu.js} +2 -14
  51. package/build/server/chunks/chunks/{core.js-D1sYC_78.js.map → core.js-CTyKR4bu.js.map} +1 -1
  52. package/build/server/chunks/chunks/{exports.js-B5DLeNXr.js → exports.js-gz2aNXzz.js} +2 -2
  53. package/build/server/chunks/chunks/{exports.js-B5DLeNXr.js.map → exports.js-gz2aNXzz.js.map} +1 -1
  54. package/build/server/chunks/chunks/{http.js-CzB9Igb0.js → http.js-BiqUcAuu.js} +3 -2
  55. package/build/server/chunks/chunks/http.js-BiqUcAuu.js.map +1 -0
  56. package/build/server/chunks/chunks/{internal.js-HE35Pm1C.js → internal.js-DoH0cN5N.js} +3 -3
  57. package/build/server/chunks/chunks/{internal.js-HE35Pm1C.js.map → internal.js-DoH0cN5N.js.map} +1 -1
  58. package/build/server/chunks/chunks/internal2.js-BlXt2rfE.js.map +1 -1
  59. package/build/server/chunks/chunks/{manifest-membership.js-Cm_2fU-Z.js → manifest-membership.js-at1ixQdW.js} +10 -3
  60. package/build/server/chunks/chunks/manifest-membership.js-at1ixQdW.js.map +1 -0
  61. package/build/server/chunks/chunks/{raw-reconciliation.js-1_IwPhKb.js → raw-reconciliation.js-CUH8IIJx.js} +3 -3
  62. package/build/server/chunks/chunks/raw-reconciliation.js-CUH8IIJx.js.map +1 -0
  63. package/build/server/chunks/chunks/{raw-reconciliation2.js-CcO9li5n.js → raw-reconciliation2.js-z4xl5zw2.js} +2 -2
  64. package/build/server/chunks/chunks/{raw-reconciliation2.js-CcO9li5n.js.map → raw-reconciliation2.js-z4xl5zw2.js.map} +1 -1
  65. package/build/server/chunks/chunks/{shared.js-MR8sPPTu.js → shared.js-CI-ZkPQn.js} +2 -2
  66. package/build/server/chunks/chunks/{shared.js-MR8sPPTu.js.map → shared.js-CI-ZkPQn.js.map} +1 -1
  67. package/build/server/chunks/chunks/{utils.js-C2UFBITa.js → utils.js-gXYLxRG9.js} +3 -3
  68. package/build/server/chunks/chunks/{utils.js-C2UFBITa.js.map → utils.js-gXYLxRG9.js.map} +1 -1
  69. package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-BnRJ5Qnd.js → _server.ts.js-CdDjuW0r.js} +6 -6
  70. package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-BnRJ5Qnd.js.map → _server.ts.js-CdDjuW0r.js.map} +1 -1
  71. package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-BZPR6n5n.js → _server.ts.js-OWM1eORG.js} +7 -7
  72. package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-BZPR6n5n.js.map → _server.ts.js-OWM1eORG.js.map} +1 -1
  73. package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-DiBS3A0C.js → _server.ts.js-DoG-7ZpZ.js} +7 -7
  74. package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-DiBS3A0C.js.map → _server.ts.js-DoG-7ZpZ.js.map} +1 -1
  75. package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/{_server.ts.js-LOO6qm2-.js → _server.ts.js-CAWjVeGd.js} +7 -7
  76. package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/{_server.ts.js-LOO6qm2-.js.map → _server.ts.js-CAWjVeGd.js.map} +1 -1
  77. package/build/server/chunks/entries/endpoints/api/assets/_id_/retag/_server.ts.js-CqS4aALY.js +38 -0
  78. package/build/server/chunks/entries/endpoints/api/assets/_id_/retag/_server.ts.js-CqS4aALY.js.map +1 -0
  79. package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-CRtJJoSs.js → _server.ts.js-Bo_EsYXJ.js} +7 -7
  80. package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-CRtJJoSs.js.map → _server.ts.js-Bo_EsYXJ.js.map} +1 -1
  81. package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-Ds4zCZ9H.js → _server.ts.js-DW4w0XAr.js} +7 -7
  82. package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-Ds4zCZ9H.js.map → _server.ts.js-DW4w0XAr.js.map} +1 -1
  83. package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-DD4N8pcj.js → _server.ts.js-_MiNPnSy.js} +7 -7
  84. package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-DD4N8pcj.js.map → _server.ts.js-_MiNPnSy.js.map} +1 -1
  85. package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-CZev9aYz.js → _server.ts.js-DwbrQnBc.js} +7 -7
  86. package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-CZev9aYz.js.map → _server.ts.js-DwbrQnBc.js.map} +1 -1
  87. package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-MBDVW0q1.js → _server.ts.js-CEnHQf3b.js} +7 -7
  88. package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-MBDVW0q1.js.map → _server.ts.js-CEnHQf3b.js.map} +1 -1
  89. package/build/server/chunks/entries/endpoints/api/imports/{_server.ts.js-C7naXnsR.js → _server.ts.js-BZDuJZtF.js} +7 -7
  90. package/build/server/chunks/entries/endpoints/api/imports/{_server.ts.js-C7naXnsR.js.map → _server.ts.js-BZDuJZtF.js.map} +1 -1
  91. package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-DuGyRdf3.js → _server.ts.js-DRgdpKKd.js} +7 -7
  92. package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-DuGyRdf3.js.map → _server.ts.js-DRgdpKKd.js.map} +1 -1
  93. package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-0-ZCt0qH.js → _server.ts.js-CP5QyIhz.js} +7 -7
  94. package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-0-ZCt0qH.js.map → _server.ts.js-CP5QyIhz.js.map} +1 -1
  95. package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-DFYU_sZL.js → _server.ts.js-BKEvt065.js} +7 -7
  96. package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-DFYU_sZL.js.map → _server.ts.js-BKEvt065.js.map} +1 -1
  97. package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-BqJpK0sJ.js → _server.ts.js-C_zihkDS.js} +7 -7
  98. package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-BqJpK0sJ.js.map → _server.ts.js-C_zihkDS.js.map} +1 -1
  99. package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js--LSqADJp.js → _server.ts.js-watkBmy6.js} +7 -7
  100. package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js--LSqADJp.js.map → _server.ts.js-watkBmy6.js.map} +1 -1
  101. package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-DcL8nYDM.js → _server.ts.js-YtumLIou.js} +7 -7
  102. package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-DcL8nYDM.js.map → _server.ts.js-YtumLIou.js.map} +1 -1
  103. package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-72OcyLfP.js → _server.ts.js-Dkouuu-M.js} +7 -7
  104. package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-72OcyLfP.js.map → _server.ts.js-Dkouuu-M.js.map} +1 -1
  105. package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-D0bOxmG0.js → _server.ts.js-DwsEQeuf.js} +7 -7
  106. package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-D0bOxmG0.js.map → _server.ts.js-DwsEQeuf.js.map} +1 -1
  107. package/build/server/chunks/entries/endpoints/api/usage/{_server.ts.js-BjDVCRNV.js → _server.ts.js-CdQxfhV3.js} +7 -7
  108. package/build/server/chunks/entries/endpoints/api/usage/{_server.ts.js-BjDVCRNV.js.map → _server.ts.js-CdQxfhV3.js.map} +1 -1
  109. package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-CndfoXXd.js → _server.ts.js-DDdMocWj.js} +6 -6
  110. package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-CndfoXXd.js.map → _server.ts.js-DDdMocWj.js.map} +1 -1
  111. package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-Ck1mgbk5.js → _server.ts.js-DbzjbH32.js} +7 -7
  112. package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-Ck1mgbk5.js.map → _server.ts.js-DbzjbH32.js.map} +1 -1
  113. package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-lZ9oP8tz.js → _server.ts.js-K_JbXZsU.js} +7 -7
  114. package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-lZ9oP8tz.js.map → _server.ts.js-K_JbXZsU.js.map} +1 -1
  115. package/build/server/chunks/entries/{hooks.server.js-DQ6l2RSK.js → hooks.server.js-DgZAVFbQ.js} +6 -6
  116. package/build/server/chunks/entries/{hooks.server.js-DQ6l2RSK.js.map → hooks.server.js-DgZAVFbQ.js.map} +1 -1
  117. package/build/server/chunks/{handler-Dfk_4a-r.js → handler-CFkKak8t.js} +5 -5
  118. package/build/server/chunks/{handler-Dfk_4a-r.js.map → handler-CFkKak8t.js.map} +1 -1
  119. package/build/server/chunks/{index.js-O3Wl1zok.js → index.js-B1IroJH5.js} +5 -5
  120. package/build/server/chunks/{index.js-O3Wl1zok.js.map → index.js-B1IroJH5.js.map} +1 -1
  121. package/build/server/chunks/{manifest.js-51nEZnKQ.js → manifest.js-DNDBsrEU.js} +36 -29
  122. package/build/server/chunks/manifest.js-DNDBsrEU.js.map +1 -0
  123. package/build/server/chunks/nodes/{0.js-DjqMfDtB.js → 0.js-CZ3mZUZl.js} +2 -2
  124. package/build/server/chunks/nodes/{0.js-DjqMfDtB.js.map → 0.js-CZ3mZUZl.js.map} +1 -1
  125. package/build/server/chunks/nodes/1.js-BmDzLKxV.js +7 -0
  126. package/build/server/chunks/nodes/{1.js-BwfSLYJH.js.map → 1.js-BmDzLKxV.js.map} +1 -1
  127. package/build/server/chunks/nodes/2.js-Dirm-bY-.js +7 -0
  128. package/build/server/chunks/nodes/2.js-Dirm-bY-.js.map +1 -0
  129. package/build/server/chunks/nodes/3.js-DOqHmSWt.js +7 -0
  130. package/build/server/chunks/nodes/{3.js-D7_hF0qp.js.map → 3.js-DOqHmSWt.js.map} +1 -1
  131. package/build/server/chunks/nodes/4.js-C8pmF8IP.js +7 -0
  132. package/build/server/chunks/nodes/{4.js-D58M2chZ.js.map → 4.js-C8pmF8IP.js.map} +1 -1
  133. package/dist/core/api-types.d.ts +2 -1
  134. package/dist/core/config.d.ts +2 -0
  135. package/dist/core/ingest.d.ts +1 -6
  136. package/dist/core/ingest.js +4 -21
  137. package/dist/core/propose-asset-id.d.ts +10 -0
  138. package/dist/core/propose-asset-id.js +25 -0
  139. package/dist/server/cli-commands.d.ts +2 -2
  140. package/dist/server/cli-commands.js +213 -29
  141. package/dist/server/config-loader.d.ts +1 -0
  142. package/dist/server/config-loader.js +5 -0
  143. package/dist/server/http.js +1 -0
  144. package/dist/server/importers/figma.d.ts +26 -3
  145. package/dist/server/importers/figma.js +114 -1
  146. package/dist/server/library-store.d.ts +9 -0
  147. package/dist/server/library-store.js +79 -0
  148. package/dist/server/operations.d.ts +12 -0
  149. package/dist/server/operations.js +121 -1
  150. package/package.json +1 -1
  151. package/build/client/_app/immutable/assets/AssetSourceMenu.B5fzYfym.css.br +0 -0
  152. package/build/client/_app/immutable/chunks/B06eqbax.js +0 -108
  153. package/build/client/_app/immutable/chunks/B06eqbax.js.br +0 -0
  154. package/build/client/_app/immutable/chunks/B06eqbax.js.gz +0 -0
  155. package/build/client/_app/immutable/chunks/B97y-eFA.js.br +0 -0
  156. package/build/client/_app/immutable/chunks/B97y-eFA.js.gz +0 -0
  157. package/build/client/_app/immutable/chunks/C9MzfQz6.js +0 -2
  158. package/build/client/_app/immutable/chunks/C9MzfQz6.js.br +0 -0
  159. package/build/client/_app/immutable/chunks/C9MzfQz6.js.gz +0 -0
  160. package/build/client/_app/immutable/chunks/CcuyGBqj.js +0 -1
  161. package/build/client/_app/immutable/chunks/CcuyGBqj.js.br +0 -0
  162. package/build/client/_app/immutable/chunks/CcuyGBqj.js.gz +0 -0
  163. package/build/client/_app/immutable/chunks/D6c2mfMB.js.br +0 -2
  164. package/build/client/_app/immutable/chunks/D6c2mfMB.js.gz +0 -0
  165. package/build/client/_app/immutable/entry/app.V1MmmICK.js.br +0 -0
  166. package/build/client/_app/immutable/entry/app.V1MmmICK.js.gz +0 -0
  167. package/build/client/_app/immutable/entry/start.BbyflZUw.js +0 -1
  168. package/build/client/_app/immutable/entry/start.BbyflZUw.js.br +0 -0
  169. package/build/client/_app/immutable/entry/start.BbyflZUw.js.gz +0 -0
  170. package/build/client/_app/immutable/nodes/0.BJOUVkow.js +0 -1
  171. package/build/client/_app/immutable/nodes/0.BJOUVkow.js.br +0 -0
  172. package/build/client/_app/immutable/nodes/0.BJOUVkow.js.gz +0 -0
  173. package/build/client/_app/immutable/nodes/1.BqoPRvUU.js.br +0 -0
  174. package/build/client/_app/immutable/nodes/1.BqoPRvUU.js.gz +0 -0
  175. package/build/client/_app/immutable/nodes/2.BSUVFDfW.js +0 -3
  176. package/build/client/_app/immutable/nodes/2.BSUVFDfW.js.br +0 -0
  177. package/build/client/_app/immutable/nodes/2.BSUVFDfW.js.gz +0 -0
  178. package/build/client/_app/immutable/nodes/3.B6beFiTQ.js +0 -1
  179. package/build/client/_app/immutable/nodes/3.B6beFiTQ.js.br +0 -0
  180. package/build/client/_app/immutable/nodes/3.B6beFiTQ.js.gz +0 -0
  181. package/build/client/_app/immutable/nodes/4.Dt96q8V4.js +0 -2
  182. package/build/client/_app/immutable/nodes/4.Dt96q8V4.js.br +0 -0
  183. package/build/client/_app/immutable/nodes/4.Dt96q8V4.js.gz +0 -0
  184. package/build/server/chunks/chunks/context.js-BXaBgcZT.js.map +0 -1
  185. package/build/server/chunks/chunks/contract.js-IY1F-HE_.js.map +0 -1
  186. package/build/server/chunks/chunks/http.js-CzB9Igb0.js.map +0 -1
  187. package/build/server/chunks/chunks/manifest-membership.js-Cm_2fU-Z.js.map +0 -1
  188. package/build/server/chunks/chunks/raw-reconciliation.js-1_IwPhKb.js.map +0 -1
  189. package/build/server/chunks/manifest.js-51nEZnKQ.js.map +0 -1
  190. package/build/server/chunks/nodes/1.js-BwfSLYJH.js +0 -7
  191. package/build/server/chunks/nodes/2.js-buHbEytf.js +0 -7
  192. package/build/server/chunks/nodes/2.js-buHbEytf.js.map +0 -1
  193. package/build/server/chunks/nodes/3.js-D7_hF0qp.js +0 -7
  194. package/build/server/chunks/nodes/4.js-D58M2chZ.js +0 -7
@@ -92,6 +92,15 @@ export declare class LibraryStore {
92
92
  replaceRawFileFromSource(id: string, options: ReplaceRawFromSourceOptions): Promise<Library>;
93
93
  /** CAS-protected replacement used only by a repeatable import source. */
94
94
  replaceRawFromImportSource(id: string, bytes: Uint8Array, expected: ImportSourceExpectation, nextSource?: ImportSourceRecord, updateParameters?: (parameters: unknown) => unknown): Promise<Library>;
95
+ /**
96
+ * CAS-protected raw swap for a Figma tag source that may also rename the
97
+ * asset. In tag mode the tag IS the id, so changing the tag moves the library
98
+ * key. The raw swap and the (optional) key move happen in ONE queued critical
99
+ * section and ONE `write`, so no inconsistent intermediate state is ever
100
+ * persisted; on any error the record and raw file are rolled back and the key
101
+ * move (in-memory only until `write`) is discarded.
102
+ */
103
+ repointTagAsset(oldId: string, newId: string, bytes: Uint8Array, expected: ImportSourceExpectation, nextSource?: ImportSourceRecord, updateParameters?: (parameters: unknown) => unknown): Promise<Library>;
95
104
  /** Replace an asset's parameters (validated by the owning plugin). */
96
105
  updateParameters(id: string, parameters: unknown): Promise<Library>;
97
106
  /** Replace an asset's manifest membership (every name must exist). */
@@ -348,6 +348,85 @@ export class LibraryStore {
348
348
  return library;
349
349
  });
350
350
  }
351
+ /**
352
+ * CAS-protected raw swap for a Figma tag source that may also rename the
353
+ * asset. In tag mode the tag IS the id, so changing the tag moves the library
354
+ * key. The raw swap and the (optional) key move happen in ONE queued critical
355
+ * section and ONE `write`, so no inconsistent intermediate state is ever
356
+ * persisted; on any error the record and raw file are rolled back and the key
357
+ * move (in-memory only until `write`) is discarded.
358
+ */
359
+ async repointTagAsset(oldId, newId, bytes, expected, nextSource, updateParameters) {
360
+ return this.#enqueue(async () => {
361
+ const library = await this.read();
362
+ const record = this.#requireAsset(library, oldId);
363
+ if (record.raw !== expected.raw ||
364
+ record.rawHash !== expected.rawHash ||
365
+ record.type !== expected.type ||
366
+ !record.importSource ||
367
+ canonicalizeParameters(record.importSource) !== expected.canonicalSource) {
368
+ throw new Error('stale-import');
369
+ }
370
+ const renamed = newId !== oldId;
371
+ if (renamed && Object.hasOwn(library.assets, newId)) {
372
+ throw new Error(`An asset with id "${newId}" already exists.`);
373
+ }
374
+ const absolutePath = join(this.#config.rawDir, record.raw);
375
+ const { verifyStableRawFile } = await import('./raw-reconciliation.js');
376
+ try {
377
+ await verifyStableRawFile(absolutePath, expected.rawHash);
378
+ }
379
+ catch {
380
+ throw new Error('stale-import');
381
+ }
382
+ const token = crypto.randomUUID();
383
+ const temporaryPath = `${absolutePath}.fundus-retag-${token}.tmp`;
384
+ const backupPath = `${absolutePath}.fundus-retag-${token}`;
385
+ const previousHash = record.rawHash;
386
+ const previousSource = structuredClone(record.importSource);
387
+ const previousParameters = structuredClone(record.parameters);
388
+ let staged = false;
389
+ let installed = false;
390
+ try {
391
+ await writeFile(temporaryPath, bytes);
392
+ await rename(absolutePath, backupPath);
393
+ staged = true;
394
+ await rename(temporaryPath, absolutePath);
395
+ installed = true;
396
+ record.rawHash = sha256Hex(bytes);
397
+ if (nextSource)
398
+ record.importSource = structuredClone(nextSource);
399
+ if (updateParameters)
400
+ record.parameters = updateParameters(record.parameters);
401
+ if (renamed) {
402
+ delete library.assets[oldId];
403
+ library.assets[newId] = record;
404
+ }
405
+ await this.write(library);
406
+ }
407
+ catch (cause) {
408
+ record.rawHash = previousHash;
409
+ record.importSource = previousSource;
410
+ record.parameters = previousParameters;
411
+ if (renamed) {
412
+ delete library.assets[newId];
413
+ library.assets[oldId] = record;
414
+ }
415
+ if (installed)
416
+ await unlink(absolutePath).catch(() => { });
417
+ if (staged)
418
+ await rename(backupPath, absolutePath);
419
+ await unlink(temporaryPath).catch(() => { });
420
+ throw cause;
421
+ }
422
+ if (staged)
423
+ await unlink(backupPath).catch(() => { });
424
+ // The old id's proxies are orphans after a rename (pruning also covers it).
425
+ if (renamed)
426
+ await this.#deleteProxiesOf(oldId);
427
+ return library;
428
+ });
429
+ }
351
430
  /** Replace an asset's parameters (validated by the owning plugin). */
352
431
  async updateParameters(id, parameters) {
353
432
  return this.#enqueue(() => this.#updateParameters(id, parameters));
@@ -66,6 +66,18 @@ export declare class FundusOperations {
66
66
  reimportAsset(id: string, input?: unknown): Promise<FundusStateDto>;
67
67
  /** Materialize a new source, then atomically replace an existing asset's raw file. */
68
68
  replaceRawFromSource(id: string, options: ReplaceRawFromSourceOptions): Promise<FundusStateDto>;
69
+ /**
70
+ * Edit a Figma tag asset's tag, fileKey, and/or scale in place. Because in tag
71
+ * mode the tag IS the asset id, changing the tag renames the asset (moves the
72
+ * library key); changing only fileKey/scale is an in-place re-point. Mirrors
73
+ * `reimportAsset`'s three phases: preflight in the queue, materialize outside
74
+ * it, then commit atomically through `store.repointTagAsset`.
75
+ */
76
+ retagFigmaAsset(id: string, input: {
77
+ tag?: string;
78
+ fileKey?: string;
79
+ scale?: 1 | 2 | 3 | 4;
80
+ }): Promise<FundusStateDto>;
69
81
  /** Apply a PATCH body: parameters and/or manifests and/or a rename — in that order. */
70
82
  patchAsset(id: string, body: PatchAssetRequestDto): Promise<FundusStateDto>;
71
83
  moveAsset(id: string, folder: string): Promise<FundusStateDto>;
@@ -53,6 +53,18 @@ function figmaSlicePixelRatio(raw, type) {
53
53
  return undefined;
54
54
  return validateFigmaSourceParameters(raw.importSource.parameters).scale;
55
55
  }
56
+ /**
57
+ * The Fundus tag of a Figma tag-mode import source, else null. In tag mode the
58
+ * tag IS the asset id, so this is the single seam that ties provenance to
59
+ * identity — used to assert id equality on import/reimport and to forbid
60
+ * renaming a tag-referenced asset. Only Figma sources are inspected.
61
+ */
62
+ function figmaTagOf(importSource) {
63
+ if (importSource?.importer !== 'figma')
64
+ return null;
65
+ const params = validateFigmaSourceParameters(importSource.parameters);
66
+ return params.mode === 'tag' ? params.fundusTag : null;
67
+ }
56
68
  export class FundusOperations {
57
69
  #config;
58
70
  #registry;
@@ -359,6 +371,10 @@ export class FundusOperations {
359
371
  });
360
372
  return this.#enqueueOperation(async () => {
361
373
  this.#validateImportedRaw(raw, options.importer);
374
+ const tag = figmaTagOf(raw.importSource);
375
+ if (tag !== null && tag !== options.id) {
376
+ throw new SourceImportError('invalid-input', `A Figma tag import must use its tag as the asset id: the tag is "${tag}", not "${options.id}".`);
377
+ }
362
378
  return this.#createImportedAsset(raw, options);
363
379
  });
364
380
  }
@@ -399,6 +415,10 @@ export class FundusOperations {
399
415
  }
400
416
  return this.#enqueueOperation(async () => {
401
417
  this.#validateImportedRaw(raw, snapshot.importer.kind);
418
+ const tag = figmaTagOf(raw.importSource);
419
+ if (tag !== null && tag !== id) {
420
+ throw new SourceImportError('invalid-input', `A Figma tag reimport may not retarget asset "${id}" to a different tag "${tag}".`);
421
+ }
402
422
  const plugin = serverPluginForType(snapshot.expected.type, this.#registry);
403
423
  if (!plugin.descriptor.accepts.includes(raw.extension)) {
404
424
  throw new SourceImportError('invalid-response', `The ${snapshot.expected.type} type does not accept ".${raw.extension}" files.`);
@@ -458,6 +478,10 @@ export class FundusOperations {
458
478
  const importSource = raw.importSource;
459
479
  return this.#enqueueOperation(async () => {
460
480
  this.#validateImportedRaw(raw, preflight.importer.kind);
481
+ const tag = figmaTagOf(importSource);
482
+ if (tag !== null && tag !== id) {
483
+ throw new SourceImportError('invalid-input', `A Figma tag replacement must use its own tag as the asset id: the tag is "${tag}", not "${id}".`);
484
+ }
461
485
  const plugin = serverPluginForType(preflight.expected.type, this.#registry);
462
486
  if (!plugin.descriptor.accepts.includes(raw.extension)) {
463
487
  throw new SourceImportError('invalid-response', `The ${preflight.expected.type} type does not accept ".${raw.extension}" files.`);
@@ -486,6 +510,92 @@ export class FundusOperations {
486
510
  }
487
511
  });
488
512
  }
513
+ /**
514
+ * Edit a Figma tag asset's tag, fileKey, and/or scale in place. Because in tag
515
+ * mode the tag IS the asset id, changing the tag renames the asset (moves the
516
+ * library key); changing only fileKey/scale is an in-place re-point. Mirrors
517
+ * `reimportAsset`'s three phases: preflight in the queue, materialize outside
518
+ * it, then commit atomically through `store.repointTagAsset`.
519
+ */
520
+ async retagFigmaAsset(id, input) {
521
+ const snapshot = await this.#enqueueOperation(async () => {
522
+ const library = await this.#store.read();
523
+ this.#requireAsset(library, id);
524
+ const record = library.assets[id];
525
+ const currentTag = figmaTagOf(record.importSource);
526
+ if (currentTag === null || !record.importSource) {
527
+ throw new SourceImportError('invalid-input', `Asset "${id}" is not a Figma tag import.`);
528
+ }
529
+ const current = validateFigmaSourceParameters(record.importSource.parameters);
530
+ const newTag = input.tag ?? currentTag;
531
+ const fileKey = input.fileKey ?? current.fileKey;
532
+ const scale = input.scale ?? current.scale;
533
+ if (newTag !== id) {
534
+ const problem = assetIdError(newTag);
535
+ if (problem)
536
+ throw new SourceImportError('invalid-input', problem);
537
+ if (Object.hasOwn(library.assets, newTag)) {
538
+ throw new SourceImportError('invalid-input', `An asset with id "${newTag}" already exists.`);
539
+ }
540
+ // A tag change renames the asset; reject one that would dangle references.
541
+ const candidate = structuredClone(library);
542
+ candidate.assets[newTag] = candidate.assets[id];
543
+ delete candidate.assets[id];
544
+ await this.#assertNoIntroducedIssues(library, candidate);
545
+ }
546
+ try {
547
+ await verifyStableRawFile(join(this.#config.rawDir, record.raw), record.rawHash);
548
+ }
549
+ catch {
550
+ throw new SourceImportError('stale-import', 'The local raw file changed. Apply or discard that edit before retagging.');
551
+ }
552
+ const importer = this.#importers.require('figma');
553
+ return {
554
+ importer,
555
+ input: importer.validateInput({ mode: 'tag', tag: newTag, fileKey, scale, format: 'png' }),
556
+ newTag,
557
+ expected: {
558
+ raw: record.raw,
559
+ rawHash: record.rawHash,
560
+ type: record.type,
561
+ canonicalSource: canonicalizeParameters(record.importSource)
562
+ }
563
+ };
564
+ });
565
+ const raw = await snapshot.importer.importRaw(snapshot.input, {
566
+ config: this.#config,
567
+ fetch: globalThis.fetch
568
+ });
569
+ if (!raw.importSource) {
570
+ throw new SourceImportError('invalid-response', 'The Figma importer did not return provenance for the retagged source.');
571
+ }
572
+ const importSource = raw.importSource;
573
+ return this.#enqueueOperation(async () => {
574
+ this.#validateImportedRaw(raw, snapshot.importer.kind);
575
+ if (figmaTagOf(importSource) !== snapshot.newTag) {
576
+ throw new SourceImportError('invalid-response', `The Figma importer resolved a different tag than "${snapshot.newTag}".`);
577
+ }
578
+ const plugin = serverPluginForType(snapshot.expected.type, this.#registry);
579
+ if (!plugin.descriptor.accepts.includes(raw.extension)) {
580
+ throw new SourceImportError('invalid-response', `The ${snapshot.expected.type} type does not accept ".${raw.extension}" files.`);
581
+ }
582
+ // A retag may change the export scale, so always sync a slice's pixel ratio.
583
+ const pixelRatio = figmaSlicePixelRatio(raw, snapshot.expected.type);
584
+ let library;
585
+ try {
586
+ library = await this.#store.repointTagAsset(id, snapshot.newTag, raw.bytes, snapshot.expected, importSource, pixelRatio === undefined
587
+ ? undefined
588
+ : (parameters) => plugin.descriptor.validateParameters(mergeParameterPatch(parameters, { pixelRatio })));
589
+ }
590
+ catch (cause) {
591
+ if (cause instanceof Error && cause.message === 'stale-import') {
592
+ throw new SourceImportError('stale-import', 'The asset or local raw file changed while Figma was exporting. Nothing was changed.');
593
+ }
594
+ throw cause;
595
+ }
596
+ return this.#afterMutation(library);
597
+ });
598
+ }
489
599
  async #createImportedAsset(raw, options) {
490
600
  this.#validateImportedRaw(raw);
491
601
  const fileName = raw.suggestedFileName ?? `${options.id}.${raw.extension}`;
@@ -723,6 +833,9 @@ export class FundusOperations {
723
833
  async #renameAsset(id, newId) {
724
834
  const library = await this.#store.read();
725
835
  this.#requireAsset(library, id);
836
+ if (figmaTagOf(library.assets[id].importSource) !== null) {
837
+ throw new SourceImportError('invalid-input', 'This asset is referenced by a Figma tag and cannot be renamed; re-import with a new tag instead.');
838
+ }
726
839
  // A collision or invalid new id is the store's own (clearer) error; the
727
840
  // candidate check covers what the store cannot see: broken references.
728
841
  if (newId !== id && !Object.hasOwn(library.assets, newId)) {
@@ -986,7 +1099,14 @@ export class FundusOperations {
986
1099
  gitBranch,
987
1100
  library,
988
1101
  assets,
989
- importDefaults: { figma: { scale: this.#config.import.figma.defaults.scale } },
1102
+ importDefaults: {
1103
+ figma: {
1104
+ scale: this.#config.import.figma.defaults.scale,
1105
+ ...(this.#config.import.figma.defaultFileKey !== undefined
1106
+ ? { defaultFileKey: this.#config.import.figma.defaultFileKey }
1107
+ : {})
1108
+ }
1109
+ },
990
1110
  issues,
991
1111
  presetNames,
992
1112
  presetCapabilities,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fundus",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",