@qkix/strapi-plugin-ferry 0.1.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 (35) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +262 -0
  3. package/dist/_chunks/TransferPage-Bhp6vkZE.js +1 -0
  4. package/dist/_chunks/TransferPage-c8hdFQDQ.mjs +398 -0
  5. package/dist/admin/index.js +1 -0
  6. package/dist/admin/index.mjs +43 -0
  7. package/dist/admin/src/api.d.ts +114 -0
  8. package/dist/admin/src/components/ExportPanel.d.ts +7 -0
  9. package/dist/admin/src/components/ImportPanel.d.ts +15 -0
  10. package/dist/admin/src/components/ReportView.d.ts +6 -0
  11. package/dist/admin/src/index.d.ts +11 -0
  12. package/dist/admin/src/pages/TransferPage.d.ts +10 -0
  13. package/dist/admin/src/pluginId.d.ts +7 -0
  14. package/dist/server/index.js +6 -0
  15. package/dist/server/index.mjs +996 -0
  16. package/dist/server/src/config/index.d.ts +30 -0
  17. package/dist/server/src/controllers/index.d.ts +12 -0
  18. package/dist/server/src/controllers/transfer.d.ts +14 -0
  19. package/dist/server/src/index.d.ts +97 -0
  20. package/dist/server/src/policies/index.d.ts +5 -0
  21. package/dist/server/src/register.d.ts +12 -0
  22. package/dist/server/src/routes/admin.d.ts +12 -0
  23. package/dist/server/src/routes/index.d.ts +14 -0
  24. package/dist/server/src/services/csv.d.ts +59 -0
  25. package/dist/server/src/services/export.d.ts +24 -0
  26. package/dist/server/src/services/import.d.ts +78 -0
  27. package/dist/server/src/services/index.d.ts +54 -0
  28. package/dist/server/src/services/nested.d.ts +38 -0
  29. package/dist/server/src/services/parse.d.ts +25 -0
  30. package/dist/server/src/services/populate.d.ts +20 -0
  31. package/dist/server/src/services/schema.d.ts +81 -0
  32. package/dist/server/src/services/serialise.d.ts +14 -0
  33. package/dist/server/src/types.d.ts +145 -0
  34. package/dist/server/src/uids.d.ts +9 -0
  35. package/package.json +118 -0
@@ -0,0 +1,6 @@
1
+ "use strict";const B=require("node:fs/promises"),J={default:{exclude:[],maxExport:1e4},validator(n){if(n.exclude!==void 0&&!Array.isArray(n.exclude))throw new Error("[ferry] `exclude` must be an array of content-type uids.");if(n.maxExport!==void 0&&(typeof n.maxExport!="number"||n.maxExport<=0))throw new Error("[ferry] `maxExport` must be a positive number.")}},T="\uFEFF",Z=[",",";"," ","|"],U=n=>{const d=n.replace(T,"").split(/\r?\n/,1)[0]??"";let t=",",r=0;for(const s of Z){let o=0,a=!1;for(let e=0;e<d.length;e+=1){const c=d[e];if(c==='"'){a=!a;continue}!a&&c===s&&(o+=1)}o>r&&(t=s,r=o)}return t},W=(n,d)=>n.includes(d)||n.includes('"')||n.includes(`
2
+ `)||n.includes("\r")||n!==n.trim(),O=(n,d)=>{if(n==null)return"";const t=typeof n=="string"?n:n instanceof Date?n.toISOString():typeof n=="object"?JSON.stringify(n):String(n);return W(t,d)?`"${t.replace(/"/g,'""')}"`:t},X=(n,d,t={})=>{const{delimiter:r=",",bom:s=!0,newline:o=`\r
3
+ `}=t,a=[d.map(e=>O(e,r)).join(r)];for(const e of n)a.push(d.map(c=>O(e[c],r)).join(r));return(s?T:"")+a.join(o)+o},V=(n,d)=>{const t=[];let r=[],s="",o=!1,a=!1;const e=()=>{r.push(s),s="",a=!0},c=()=>{e(),(r.length>1||r[0]!=="")&&t.push(r),r=[],a=!1},u=n.startsWith(T)?n.slice(T.length):n;for(let l=0;l<u.length;l+=1){const i=u[l];if(o){if(i==='"'){if(u[l+1]==='"'){s+='"',l+=1;continue}o=!1;continue}s+=i;continue}if(i==='"'&&s===""){o=!0;continue}if(i===d){e();continue}if(i==="\r"){u[l+1]===`
4
+ `&&(l+=1),c();continue}if(i===`
5
+ `){c();continue}s+=i}return(s!==""||o||a||r.length>0)&&c(),t},G=(n,d)=>{const t=U(n),r=V(n,t);if(r.length===0)return{columns:[],rows:[]};const[s,...o]=r,a=s.map(c=>c.trim()),e=o.map(c=>{const u={};return a.forEach((l,i)=>{u[l]=c[i]??""}),u});return{columns:a,rows:e}},H=(n,d)=>{const t=n.trim();switch(d){case"integer":case"biginteger":{const r=Number(t);return Number.isFinite(r)?Math.trunc(r):void 0}case"decimal":case"float":{const r=Number(t);return Number.isFinite(r)?r:void 0}case"boolean":{const r=t.toLowerCase();return["true","1","yes"].includes(r)?!0:["false","0","no"].includes(r)?!1:void 0}case"json":try{return JSON.parse(t)}catch{return}default:return n}},x=n=>typeof n=="object"&&n!==null&&!Array.isArray(n),K=(n,d,t)=>d==="csv"?Y(n,t):Q(n),Q=n=>{let d;try{d=JSON.parse(n)}catch(s){throw new Error(`[ferry] the file is not valid JSON: ${s.message}`,{cause:s})}const t=[];if(Array.isArray(d))return t.push("Read as a plain list of documents, with no header to check against."),{documents:d.filter(x),warnings:t};if(!x(d))throw new Error("[ferry] expected an object or an array of documents.");const r=d;if(r.ferry!==void 0&&r.ferry!==1)throw new Error(`[ferry] this file was written by a newer format (version ${r.ferry}) than this plugin can read.`);if(!Array.isArray(r.documents))throw new Error("[ferry] the file has no `documents` array.");return{contentType:r.contentType,status:r.status,locale:r.locale,documents:r.documents.filter(x),warnings:t}},Y=(n,d)=>{const{columns:t,rows:r}=G(n),s=[],o=new Map(d.scalars.map(l=>[l.name,l.type])),a=new Set(d.relations.map(l=>l.name)),e=new Set(["documentId","locale",...o.keys(),...a,...d.media.map(l=>l.name)]),c=t.filter(l=>l&&!e.has(l));return c.length>0&&s.push(`Ignored columns not in the schema: ${c.join(", ")}.`),t.includes("documentId")||s.push("No documentId column, so every row will be created as a new document rather than matched against an existing one."),{documents:r.map(l=>{const i={};for(const m of t){if(!e.has(m))continue;const p=l[m]??"";if(p==="")continue;if(m==="documentId"||m==="locale"){i[m]=p.trim();continue}if(a.has(m)){i[m]=p.split(",").map(g=>g.trim()).filter(Boolean);continue}const f=o.get(m),h=f?H(p,f):p;h!==void 0&&(i[m]=h)}return i}),warnings:s}},S="ferry",L="plugin::upload.file",I=n=>n.plugin(S),ee=async n=>{const d=n.request.body?.content;if(typeof d=="string"&&d.length>0)return d;const t=n.request.files??{},r=t.file??Object.values(t)[0],s=Array.isArray(r)?r[0]:r;s||n.throw(400,"Attach a file, or post its content as `content`.");const o=s.filepath??s.path;return o||n.throw(400,"The upload arrived without a readable path."),B.readFile(o,"utf8")},ne=(n,d)=>{const t=n.request.body?.format;return t==="csv"||t==="json"?t:d?.toLowerCase().endsWith(".csv")?"csv":"json"},te=n=>{const d=n.request.files??{},t=d.file??Object.values(d)[0],r=Array.isArray(t)?t[0]:t;return r?.originalFilename??r?.name},E=(n,d)=>n==null||n===""?d:typeof n=="boolean"?n:String(n).toLowerCase()==="true",oe=({strapi:n})=>({async catalogue(d){d.body={contentTypes:I(n).service("schema").catalogue()}},async plan(d){const{uid:t}=d.params;try{d.body=I(n).service("schema").plan(t)}catch(r){d.throw(404,r.message)}},async export(d){const t=d.request.body??{};t.uid||d.throw(400,"Name the content type to export as `uid`.");const r={uid:t.uid,format:t.format==="csv"?"csv":"json",status:t.status==="published"?"published":"draft",locale:t.locale||void 0,documentIds:Array.isArray(t.documentIds)?t.documentIds:void 0,filters:t.filters,sort:t.sort,limit:t.limit?Number(t.limit):void 0,relations:E(t.relations,!0),media:E(t.media,!0)};try{const s=await I(n).service("exporter").run(r);if(E(t.envelope,!1)){d.body={filename:s.filename,mime:s.mime,body:s.body,count:s.count,warnings:s.warnings};return}s.warnings.length>0&&d.set("X-Ferry-Warnings",encodeURIComponent(JSON.stringify(s.warnings))),d.set("X-Ferry-Count",String(s.count)),d.set("Access-Control-Expose-Headers","X-Ferry-Warnings, X-Ferry-Count"),d.attachment(s.filename),d.type=s.mime,d.body=s.body}catch(s){d.throw(400,s.message)}},async preview(d){await M(d,n,!1)},async apply(d){await M(d,n,!0)}}),M=async(n,d,t)=>{const r=n.request.body??{},s=r.uid;s||n.throw(400,"Name the content type to import into as `uid`.");let o;try{o=I(d).service("schema").plan(s)}catch(u){return n.throw(404,u.message)}const a=await ee(n),e=ne(n,te(n)),c={uid:s,status:r.status==="published"?"published":"draft",locale:r.locale||void 0,onExisting:r.onExisting==="skip"?"skip":"update",onMissingRelation:r.onMissingRelation==="fail"?"fail":"skip",publish:E(r.publish,!0),continueOnError:E(r.continueOnError,!1)};try{const u=K(a,e,o),l=I(d).service("importer");n.body=t?await l.apply(c,u):await l.preview(c,u)}catch(u){n.throw(400,u.message)}},se={transfer:oe},v=n=>d=>{const t=d.state?.userAbility;return t?t.can(`plugin::${S}.${n}`):!1},re={canExport:v("export"),canImport:v("import")},ie=({strapi:n})=>{const d=[{section:"plugins",displayName:"Export content",uid:"export",pluginName:S},{section:"plugins",displayName:"Import content",uid:"import",pluginName:S}];n.admin?.services?.permission?.actionProvider?.registerMany?.(d)},P=["plugin::ferry.canExport"],N=["plugin::ferry.canImport"],ae={type:"admin",routes:[{method:"GET",path:"/content-types",handler:"transfer.catalogue",config:{policies:P}},{method:"GET",path:"/plan/:uid",handler:"transfer.plan",config:{policies:P}},{method:"POST",path:"/export",handler:"transfer.export",config:{policies:P}},{method:"POST",path:"/import/preview",handler:"transfer.preview",config:{policies:N}},{method:"POST",path:"/import",handler:"transfer.apply",config:{policies:N}}]},ce={admin:ae},de=10,le=["name","url","hash","ext","mime","size"],j=(n,d,t,r=0)=>{const s={};if(r>de)return s;const o=n.plugin("ferry").service("schema");if(t.relations)for(const a of d.relations)s[a.name]={fields:["documentId"]};if(t.media)for(const a of d.media)s[a.name]={fields:le};for(const a of d.components){const e=j(n,o.componentPlan(a.component),t,r+1);s[a.name]=Object.keys(e).length>0?{populate:e}:{populate:{}}}for(const a of d.dynamicZones){const e={};for(const c of a.components){const u=j(n,o.componentPlan(c),t,r+1);e[c]=Object.keys(u).length>0?{populate:u}:{}}s[a.name]={on:e}}return s},z=n=>n==null?null:typeof n=="string"?n:typeof n=="object"&&"documentId"in n?String(n.documentId):null,ue=n=>{const d=()=>n.plugin("ferry").service("schema"),t=(e,c,u)=>{if(e==null)return null;const l={};for(const i of c.scalars)i.name in e&&(l[i.name]=e[i.name]);for(const i of c.components){if(!(i.name in e))continue;const m=d().componentPlan(i.component);l[i.name]=i.repeatable?(e[i.name]??[]).map(p=>t(p,m,u)):t(e[i.name],m,u)}for(const i of c.dynamicZones)i.name in e&&(l[i.name]=r(e[i.name],u));if(u.relations)for(const i of c.relations)i.name in e&&(l[i.name]=s(e[i.name],i.many));if(u.media)for(const i of c.media)i.name in e&&(l[i.name]=o(e[i.name],i.many));return l},r=(e,c)=>Array.isArray(e)?e.map(u=>{const l=u?.__component;if(!l)return{};const i=d().componentPlan(l);return{__component:l,...t(u,i,c)}}):[],s=(e,c)=>c?(Array.isArray(e)?e:e?[e]:[]).map(z).filter(l=>l!==null).sort():z(Array.isArray(e)?e[0]:e),o=(e,c)=>{const u=l=>l?{name:l.name??null,url:l.url??null,hash:l.hash??null,ext:l.ext??null,mime:l.mime??null,size:l.size??null}:null;return c?(Array.isArray(e)?e:e?[e]:[]).map(u):u(Array.isArray(e)?e[0]:e)};return{document:(e,c,u)=>{const l={documentId:e.documentId};for(const i of c.scalars)i.name in e&&(l[i.name]=e[i.name]);for(const i of c.components){if(!(i.name in e))continue;const m=d().componentPlan(i.component);l[i.name]=i.repeatable?(e[i.name]??[]).map(p=>t(p,m,u)):t(e[i.name],m,u)}for(const i of c.dynamicZones)i.name in e&&(l[i.name]=r(e[i.name],u));if(u.relations)for(const i of c.relations)i.name in e&&(l[i.name]=s(e[i.name],i.many));if(u.media)for(const i of c.media)i.name in e&&(l[i.name]=o(e[i.name],i.many));return c.localized&&e.locale&&(l.locale=e.locale),l},componentValue:t,zoneValue:r,relationValue:s,mediaValue:o}},me={format:"json",status:"draft",relations:!0,media:!0},R=n=>n.split(".").pop()??n,fe=(n,d)=>["documentId",...n.localized?["locale"]:[],...n.scalars.map(t=>t.name),...d.relations?n.relations.map(t=>t.name):[]],pe=(n,d)=>{const t={};for(const r of d){const s=n[r];t[r]=Array.isArray(s)?s.join(","):s}return t},he=({strapi:n})=>{const d=ue(n),t=()=>n.plugin("ferry").service("schema"),r={async run(s){const o={...me,...s};if(!t().allowed(o.uid))throw new Error(`[ferry] ${o.uid} is not available for transfer.`);const a=t().plan(o.uid),e=[],c=Number(n.plugin("ferry").config("maxExport"))||1e4,l={populate:j(n,a,{relations:o.relations,media:o.media})};a.draftAndPublish&&(l.status=o.status),o.locale&&(l.locale=o.locale);const i={...o.filters??{}};o.documentIds?.length&&(i.documentId={$in:o.documentIds}),Object.keys(i).length>0&&(l.filters=i),o.sort&&(l.sort=o.sort);const m=Math.min(o.limit??c,c);l.limit=m;const f=(a.kind==="singleType"?await r.readSingle(o.uid,l):await n.documents(o.uid).findMany(l)).map(g=>d.document(g,a,{relations:o.relations,media:o.media}));if(o.sort||f.sort((g,A)=>g.documentId.localeCompare(A.documentId)),f.length>=m&&e.push(`Stopped at ${m} documents, which is this environment's export limit. There may be more - narrow the export with filters, or raise \`maxExport\` in the plugin config.`),a.media.length>0&&o.media&&e.push(`Media fields (${a.media.map(g=>g.name).join(", ")}) travel as a reference, not as the file. The import reconnects them when the same upload already exists.`),o.format==="csv"){const g=[...a.components.map(w=>w.name),...a.dynamicZones.map(w=>w.name),...o.media?a.media.map(w=>w.name):[]];g.length>0&&e.push(`CSV carries flat fields only, so ${g.join(", ")} ${g.length===1?"is":"are"} not in this file. Export as JSON to keep them.`);const A=fe(a,o);return{filename:`${R(o.uid)}.csv`,mime:"text/csv; charset=utf-8",body:X(f.map(w=>pe(w,A)),A),count:f.length,warnings:e}}const h={ferry:1,contentType:o.uid,status:o.status,...o.locale?{locale:o.locale}:{},count:f.length,documents:f};return{filename:`${R(o.uid)}.json`,mime:"application/json",body:`${JSON.stringify(h,null,2)}
6
+ `,count:f.length,warnings:e}},async readSingle(s,o){const a=await n.documents(s).findFirst(o);return a?[a]:[]}};return r},F=n=>n==null?[]:(Array.isArray(n)?n:[n]).map(t=>typeof t=="string"?t:t&&typeof t=="object"&&"documentId"in t?String(t.documentId):null).filter(t=>!!t),_=(n,d,t,r,s)=>{for(const o of d.components){const a=n?.[o.name];if(!a)continue;const e=o.repeatable?Array.isArray(a)?a:[]:[a],c=t(o.component);e.forEach((u,l)=>{if(!u||typeof u!="object")return;const i=o.repeatable?`${r}${o.name}[${l}].`:`${r}${o.name}.`;s(u,c,i),_(u,c,t,i,s)})}for(const o of d.dynamicZones){const a=n?.[o.name];Array.isArray(a)&&a.forEach((e,c)=>{if(!e?.__component)return;const u=t(e.__component),l=`${r}${o.name}[${c}].`;s(e,u,l),_(e,u,t,l,s)})}},C=(n,d,t)=>{const r=[],s=(o,a,e)=>{for(const c of a.relations){if(!(c.name in o))continue;const u=F(o[c.name]);u.length!==0&&r.push({path:`${e}${c.name}`,target:c.target,keys:u})}};return s(n,d,""),_(n,d,t,"",s),r},k=(n,d,t)=>{const r={};for(const[s,o]of Object.entries(n))d.relations.some(a=>a.name===s)||(r[s]=o);for(const s of d.components){if(!(s.name in r))continue;const o=t(s.component),a=r[s.name];r[s.name]=s.repeatable?(Array.isArray(a)?a:[]).map(e=>e&&typeof e=="object"?k(e,o,t):e):a&&typeof a=="object"?k(a,o,t):a}for(const s of d.dynamicZones){if(!(s.name in r))continue;const o=r[s.name];r[s.name]=(Array.isArray(o)?o:[]).map(a=>a?.__component?{__component:a.__component,...k(a,t(a.__component),t)}:a)}return r},$=(n,d,t,r)=>{const s={...n};for(const o of d.relations){if(!(o.name in s))continue;const a=F(s[o.name]).filter(e=>r(o.target,e));o.many?s[o.name]=a:s[o.name]=a[0]??null}for(const o of d.components){if(!(o.name in s))continue;const a=t(o.component),e=s[o.name];s[o.name]=o.repeatable?(Array.isArray(e)?e:[]).map(c=>c&&typeof c=="object"?$(c,a,t,r):c):e&&typeof e=="object"?$(e,a,t,r):e}for(const o of d.dynamicZones){if(!(o.name in s))continue;const a=s[o.name];s[o.name]=(Array.isArray(a)?a:[]).map(e=>e?.__component?{__component:e.__component,...$(e,t(e.__component),t,r)}:e)}return s},ye={status:"draft",onExisting:"update",onMissingRelation:"skip",publish:!0,continueOnError:!1},ge=10;class q extends Error{}const we=({strapi:n})=>{const d=()=>n.plugin("ferry").service("schema"),t=s=>d().componentPlan(s),r={preview(s,o){return r.run(s,o,!1)},apply(s,o){return r.run(s,o,!0)},async run(s,o,a){const e={...ye,...s};if(!d().allowed(e.uid))throw new Error(`[ferry] ${e.uid} is not available for transfer.`);const c=d().plan(e.uid),u=[...o.warnings];o.contentType&&o.contentType!==e.uid&&u.push(`This file was exported from ${o.contentType} and is being imported into ${e.uid}.`);const l=o.documents.map((y,b)=>({row:b+1,documentId:typeof y.documentId=="string"?y.documentId:void 0,data:y})),i={contentType:e.uid,applied:!1,total:l.length,created:0,updated:0,skipped:0,errored:0,rows:[],unresolved:[],warnings:u};if(l.length===0)return u.push("The file holds no documents."),i;const m=await r.existingDocuments(c,e,l),p=await r.resolveRelations(c,e,l,i),f=await r.resolveMedia(c,l,i),h=new Set(l.map(y=>y.documentId).filter(Boolean)),g=(y,b)=>p.get(y)?.has(b)===!0||y===e.uid&&h.has(b),A=l.some(y=>C(y.data,c,t).length>0),w=l.map(y=>({entry:y,outcome:r.decide(y,c,m,e)}));for(const{entry:y,outcome:b}of w)b==="create"?i.created+=1:b==="update"?i.updated+=1:i.skipped+=1,i.rows.push({row:y.row,documentId:y.documentId,outcome:b,...b==="skip"?{message:"already present, and existing rows are kept"}:{}});if(e.onMissingRelation==="fail"&&i.unresolved.length>0)return u.push("Nothing was written: a relation points at a document that is not in the file and not in this environment."),i;if(!a)return i;try{await n.db.transaction(async()=>{if(await r.write(w,c,e,{media:f,exists:g,hasRelations:A},i),i.errored>0&&!e.continueOnError)throw new q}),i.applied=!0}catch(y){if(!(y instanceof q))throw y;i.applied=!1,u.push(`Rolled back: ${i.errored} row(s) failed and the import is set to all or nothing.`)}return i},decide(s,o,a,e){return o.kind==="singleType"?a.size>0?"update":"create":!s.documentId||!a.has(s.documentId)?"create":e.onExisting==="skip"?"skip":"update"},async existingDocuments(s,o,a){const e=new Set,c={fields:["documentId"]};if(s.draftAndPublish&&(c.status="draft"),s.localized&&o.locale&&(c.locale=o.locale),s.kind==="singleType"){const i=await n.documents(s.uid).findFirst(c);return i&&e.add(i.documentId),e}const u=a.map(i=>i.documentId).filter(Boolean);if(u.length===0)return e;const l=await n.documents(s.uid).findMany({...c,filters:{documentId:{$in:u}}});for(const i of l)e.add(i.documentId);return e},async resolveRelations(s,o,a,e){const c=new Map,u=[];for(const m of a)for(const p of C(m.data,s,t)){u.push({entry:m,...p});const f=c.get(p.target)??new Set;p.keys.forEach(h=>f.add(h)),c.set(p.target,f)}const l=new Map;for(const[m,p]of c){const f=d().model(m);if(!f){e.warnings.push(`Relations point at ${m}, which does not exist here.`),l.set(m,new Set);continue}const h={fields:["documentId"],filters:{documentId:{$in:[...p]}}};f.options?.draftAndPublish&&(h.status="draft");const g=await n.documents(m).findMany(h);l.set(m,new Set(g.map(A=>A.documentId)))}const i=new Set(a.map(m=>m.documentId).filter(Boolean));for(const m of u){const p=l.get(m.target)??new Set,f=m.keys.filter(h=>!p.has(h)&&!(m.target===s.uid&&i.has(h)));f.length!==0&&e.unresolved.push({row:m.entry.row,documentId:m.entry.documentId,field:m.path,target:m.target,keys:f.slice(0,ge)})}return l},async resolveMedia(s,o,a){const e=new Map;if(s.media.length===0)return e;const c=new Set;for(const i of o)for(const m of s.media){const p=i.data[m.name],f=Array.isArray(p)?p:p?[p]:[];for(const h of f){const g=h?.hash;typeof g=="string"&&c.add(g)}}if(c.size===0)return e;const u=await n.db.query(L).findMany({select:["id","hash"],where:{hash:{$in:[...c]}}});for(const i of u)e.set(i.hash,i.id);const l=[...c].filter(i=>!e.has(i));return l.length>0&&a.warnings.push(`${l.length} media file(s) referenced in this file are not in this Media Library, so those fields are left empty. Upload them first and import again to attach them.`),e},content(s,o,a){const e=k(s.data,o,t);return delete e.documentId,delete e.locale,r.withMedia(e,o,a)},wiring(s,o,a,e){const c=$(s.data,o,t,e);return delete c.documentId,delete c.locale,r.withMedia(c,o,a)},withMedia(s,o,a){for(const e of o.media){if(!(e.name in s))continue;const c=s[e.name],l=(Array.isArray(c)?c:c?[c]:[]).map(i=>typeof i?.hash=="string"?a.get(i.hash):void 0).filter(i=>typeof i=="number");l.length===0?delete s[e.name]:s[e.name]=e.many?l:l[0]}return s},async write(s,o,a,e,c){const u=[],l=o.localized&&a.locale?{locale:a.locale}:{},i=(m,p)=>{const f=c.rows.find(h=>h.row===m);f&&(f.outcome==="create"&&(c.created-=1),f.outcome==="update"&&(c.updated-=1),f.outcome="error",f.message=p),c.errored+=1};for(const{entry:m,outcome:p}of s)if(p!=="skip")try{const f=r.content(m,o,e.media);if(p==="create"){const h=await n.documents(o.uid).create({data:{...f,...m.documentId?{documentId:m.documentId}:{}},...l});u.push({entry:m,documentId:h.documentId})}else{const h=m.documentId??await r.singleTypeId(o,a);if(!h)throw new Error("no document to update");await n.documents(o.uid).update({documentId:h,data:f,...l}),u.push({entry:m,documentId:h})}}catch(f){i(m.row,f.message)}if(e.hasRelations)for(const{entry:m,documentId:p}of u)try{await n.documents(o.uid).update({documentId:p,data:r.wiring(m,o,e.media,e.exists),...l})}catch(f){i(m.row,`relations: ${f.message}`)}if(a.publish&&o.draftAndPublish&&a.status==="published")for(const{entry:m,documentId:p}of u)try{await n.documents(o.uid).publish({documentId:p,...l})}catch(f){i(m.row,`publish: ${f.message}`)}},async singleTypeId(s,o){const a={fields:["documentId"]};return s.draftAndPublish&&(a.status="draft"),s.localized&&o.locale&&(a.locale=o.locale),(await n.documents(s.uid).findFirst(a))?.documentId}};return r},be=new Set(["id","createdAt","updatedAt","publishedAt","createdBy","updatedBy","locale","localizations","strapi_stage","strapi_assignee"]),Ae=new Set(["password"]),Ie=n=>n.info?.displayName??n.info?.singularName??n.uid,Ee=n=>n.type==="media"||n.type==="relation"&&n.target===L,D=n=>{const d=[],t=[],r=[],s=[],o=[],a=[];for(const[e,c]of Object.entries(n)){if(be.has(e)){a.push({name:e,reason:"managed by Strapi"});continue}if(Ae.has(c.type)){a.push({name:e,reason:`${c.type} fields are never exported`});continue}if(Ee(c)){o.push({name:e,many:c.multiple===!0||(c.relation??"").endsWith("Many")});continue}if(c.type==="relation"){if(!c.target){a.push({name:e,reason:"relation without a target"});continue}t.push({name:e,target:c.target,many:(c.relation??"").endsWith("Many")});continue}if(c.type==="component"){if(!c.component){a.push({name:e,reason:"component without a target"});continue}r.push({name:e,component:c.component,repeatable:c.repeatable===!0});continue}if(c.type==="dynamiczone"){s.push({name:e,components:c.components??[]});continue}d.push({name:e,type:c.type})}return{scalars:d,relations:t,components:r,dynamicZones:s,media:o,skipped:a}},Se=({strapi:n})=>{const d={model(t){return n.contentTypes[t]},component(t){return n.components?.[t]},plan(t){const r=d.model(t);if(!r)throw new Error(`[ferry] unknown content type: ${t}`);return{uid:t,kind:r.kind==="singleType"?"singleType":"collectionType",draftAndPublish:r.options?.draftAndPublish===!0,localized:r.pluginOptions?.i18n?.localized===!0,...D(r.attributes??{})}},componentPlan(t){const r=d.component(t);if(!r)throw new Error(`[ferry] unknown component: ${t}`);return{uid:t,kind:"collectionType",draftAndPublish:!1,localized:!1,...D(r.attributes??{})}},excluded(){const t=n.plugin(S)?.config("exclude");return new Set(Array.isArray(t)?t:[])},allowed(t){return t.startsWith("api::")&&!d.excluded().has(t)},catalogue(){return Object.values(n.contentTypes).filter(t=>d.allowed(t.uid)).map(t=>({uid:t.uid,label:Ie(t),kind:t.kind==="singleType"?"singleType":"collectionType"})).sort((t,r)=>t.label.localeCompare(r.label))}};return d},ke={schema:Se,exporter:he,importer:we},$e={register:ie,config:J,controllers:se,policies:re,routes:ce,services:ke};module.exports=$e;