@fmaplabs/meta-manifest 0.3.0 → 0.4.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.
@@ -266,6 +266,27 @@ function refTarget(field) {
266
266
  }
267
267
  return void 0;
268
268
  }
269
+ function refTargets(field) {
270
+ const many = v(field.validations, "metaobject_definition_types");
271
+ if (many) {
272
+ try {
273
+ const arr = JSON.parse(many);
274
+ if (Array.isArray(arr)) return arr.map(String);
275
+ } catch {
276
+ }
277
+ }
278
+ const single = v(field.validations, "metaobject_definition_type");
279
+ return single ? [single] : [];
280
+ }
281
+ function mixedTargetsLiteral(field, typeToIdent, warnings) {
282
+ const targets = refTargets(field);
283
+ const idents = targets.map((t) => typeToIdent.get(t));
284
+ if (!targets.length || idents.some((i) => !i)) {
285
+ warnings.push(`unresolved mixed reference on field "${field.key}"`);
286
+ return void 0;
287
+ }
288
+ return `[${idents.map((i) => `() => ${i}`).join(", ")}]`;
289
+ }
269
290
  function optsLiteral(entries) {
270
291
  return entries.length ? `{ ${entries.join(", ")} }` : "";
271
292
  }
@@ -332,6 +353,15 @@ function fieldCall(field, typeToIdent, warnings) {
332
353
  const opts = optsLiteral(refEntries);
333
354
  return opts ? `m.ref(() => ${ident}, ${opts})` : `m.ref(() => ${ident})`;
334
355
  }
356
+ if (type === "mixed_reference") {
357
+ const arr = mixedTargetsLiteral(field, typeToIdent, warnings);
358
+ if (!arr) return `m.json() /* TODO: unmapped mixed reference */`;
359
+ const refEntries = [];
360
+ if (field.required) refEntries.push("required: true");
361
+ refEntries.push(...filterableEntry(field));
362
+ const opts = optsLiteral(refEntries);
363
+ return opts ? `m.mixedRef(${arr}, ${opts})` : `m.mixedRef(${arr})`;
364
+ }
335
365
  if (type.startsWith("list.")) {
336
366
  const inner = type.slice("list.".length);
337
367
  const listEntries = [];
@@ -351,6 +381,10 @@ function fieldCall(field, typeToIdent, warnings) {
351
381
  return `m.json() /* TODO: unmapped list reference */`;
352
382
  }
353
383
  innerCall = `m.ref(() => ${ident})`;
384
+ } else if (inner === "mixed_reference") {
385
+ const arr = mixedTargetsLiteral(field, typeToIdent, warnings);
386
+ if (!arr) return `m.json() /* TODO: unmapped list mixed reference */`;
387
+ innerCall = `m.mixedRef(${arr})`;
354
388
  } else if (SIMPLE[inner]) {
355
389
  innerCall = scalarCall(SIMPLE[inner], { ...field, required: false }, warnings);
356
390
  } else {
@@ -417,6 +451,8 @@ function referencedTypes(def) {
417
451
  if (f.type === "metaobject_reference" || f.type === "list.metaobject_reference") {
418
452
  const t = refTarget(f);
419
453
  if (t) out.add(t);
454
+ } else if (f.type === "mixed_reference" || f.type === "list.mixed_reference") {
455
+ for (const t of refTargets(f)) out.add(t);
420
456
  }
421
457
  }
422
458
  return out;
@@ -752,25 +788,35 @@ function definitionUpdateFor(def, changes) {
752
788
  function fieldInputFor(defByType, type, key) {
753
789
  return defByType.get(type)?.fieldDefinitions.find((f) => f.key === key);
754
790
  }
755
- function referenceEdges(def) {
791
+ function fieldRefTargets(field) {
756
792
  const out = [];
757
- for (const field of def.fieldDefinitions) {
758
- for (const v2 of field.validations) {
759
- if (v2.name === "metaobject_definition_type") {
760
- out.push(v2.value);
761
- } else if (v2.name === "metaobject_definition_types") {
762
- try {
763
- const parsed = JSON.parse(v2.value);
764
- if (Array.isArray(parsed)) {
765
- for (const t of parsed) if (typeof t === "string") out.push(t);
766
- }
767
- } catch {
793
+ for (const v2 of field.validations) {
794
+ if (v2.name === "metaobject_definition_type") {
795
+ out.push(v2.value);
796
+ } else if (v2.name === "metaobject_definition_types") {
797
+ try {
798
+ const parsed = JSON.parse(v2.value);
799
+ if (Array.isArray(parsed)) {
800
+ for (const t of parsed) if (typeof t === "string") out.push(t);
768
801
  }
802
+ } catch {
769
803
  }
770
804
  }
771
805
  }
772
806
  return out;
773
807
  }
808
+ function referenceEdges(def) {
809
+ return def.fieldDefinitions.flatMap(fieldRefTargets);
810
+ }
811
+ function splitCyclicFields(def, cyclicTypes) {
812
+ const pass1 = [];
813
+ const deferred = [];
814
+ for (const f of def.fieldDefinitions) {
815
+ const breaksCycle = fieldRefTargets(f).some((t) => cyclicTypes.has(t) && t !== def.type);
816
+ (breaksCycle ? deferred : pass1).push(f);
817
+ }
818
+ return { pass1, deferred };
819
+ }
774
820
  function topoSortCreates(types, deps) {
775
821
  const remaining = /* @__PURE__ */ new Map();
776
822
  const dependents = /* @__PURE__ */ new Map();
@@ -813,42 +859,35 @@ async function push(client, plan, sources, options) {
813
859
  deps.set(op.type, new Set(targets.filter((t) => createTypes.has(t) && t !== op.type)));
814
860
  }
815
861
  const { ordered, unordered } = topoSortCreates(createTypes, deps);
816
- const orderedSet = new Set(ordered);
817
862
  const cyclicTypes = new Set(unordered);
818
863
  const createByType = new Map(createOps.map((x) => [x.op.type, x]));
819
- const execOrder = [
820
- ...ordered.map((t) => createByType.get(t)),
821
- ...createOps.filter((x) => !orderedSet.has(x.op.type)),
822
- ...otherOps
823
- ];
824
864
  const failedTypes = /* @__PURE__ */ new Set();
825
- async function applyOp(op) {
826
- if (op.kind === "createDefinition") {
827
- if (cyclicTypes.has(op.type)) {
828
- failedTypes.add(op.type);
829
- return { op, status: "blocked", reason: "reference cycle \u2014 two-pass create deferred" };
830
- }
831
- for (const dep of deps.get(op.type) ?? []) {
832
- if (failedTypes.has(dep)) {
833
- failedTypes.add(op.type);
834
- return { op, status: "blocked", reason: `blocked: dependency "${dep}" was not created` };
835
- }
836
- }
837
- const def = defByType.get(op.type);
838
- if (!def) {
839
- failedTypes.add(op.type);
840
- return { op, status: "blocked", reason: `no definition input for "${op.type}"` };
841
- }
842
- const data2 = await execute(client, CREATE_DEFINITION_MUTATION, { definition: def });
843
- const payload2 = data2.metaobjectDefinitionCreate;
844
- if (payload2.userErrors.length) {
865
+ async function createDefinition(op, definition) {
866
+ const data = await execute(client, CREATE_DEFINITION_MUTATION, { definition });
867
+ const payload = data.metaobjectDefinitionCreate;
868
+ if (payload.userErrors.length) {
869
+ failedTypes.add(op.type);
870
+ return { op, status: "failed", userErrors: payload.userErrors };
871
+ }
872
+ const id = payload.metaobjectDefinition?.id;
873
+ if (id) idByType.set(op.type, id);
874
+ return { op, status: "applied", id };
875
+ }
876
+ async function applyAcyclicCreate(op) {
877
+ for (const dep of deps.get(op.type) ?? []) {
878
+ if (failedTypes.has(dep)) {
845
879
  failedTypes.add(op.type);
846
- return { op, status: "failed", userErrors: payload2.userErrors };
880
+ return { op, status: "blocked", reason: `blocked: dependency "${dep}" was not created` };
847
881
  }
848
- const id2 = payload2.metaobjectDefinition?.id;
849
- if (id2) idByType.set(op.type, id2);
850
- return { op, status: "applied", id: id2 };
851
882
  }
883
+ const def = defByType.get(op.type);
884
+ if (!def) {
885
+ failedTypes.add(op.type);
886
+ return { op, status: "blocked", reason: `no definition input for "${op.type}"` };
887
+ }
888
+ return createDefinition(op, def);
889
+ }
890
+ async function applyFieldOp(op) {
852
891
  const destructive = "destructive" in op && op.destructive === true;
853
892
  if (destructive && !allowDestructive) return { op, status: "skipped", reason: "destructive" };
854
893
  if (failedTypes.has(op.type)) return { op, status: "blocked", reason: `blocked: definition "${op.type}" was not created` };
@@ -901,7 +940,53 @@ async function push(client, plan, sources, options) {
901
940
  }
902
941
  }
903
942
  const results = new Array(plan.length);
904
- for (const { op, index } of execOrder) results[index] = await applyOp(op);
943
+ for (const type of ordered) {
944
+ const entry = createByType.get(type);
945
+ results[entry.index] = await applyAcyclicCreate(entry.op);
946
+ }
947
+ const cyclicCreates = createOps.filter((x) => cyclicTypes.has(x.op.type));
948
+ const deferredByType = /* @__PURE__ */ new Map();
949
+ for (const { op, index } of cyclicCreates) {
950
+ const def = defByType.get(op.type);
951
+ if (!def) {
952
+ failedTypes.add(op.type);
953
+ results[index] = { op, status: "blocked", reason: `no definition input for "${op.type}"` };
954
+ continue;
955
+ }
956
+ const failedDep = [...deps.get(op.type) ?? []].find((d) => !cyclicTypes.has(d) && failedTypes.has(d));
957
+ if (failedDep) {
958
+ failedTypes.add(op.type);
959
+ results[index] = { op, status: "blocked", reason: `blocked: dependency "${failedDep}" was not created` };
960
+ continue;
961
+ }
962
+ const { pass1, deferred } = splitCyclicFields(def, cyclicTypes);
963
+ deferredByType.set(op.type, deferred);
964
+ results[index] = await createDefinition(op, { ...def, fieldDefinitions: pass1 });
965
+ }
966
+ for (const { op, index } of cyclicCreates) {
967
+ if (results[index]?.status !== "applied") continue;
968
+ const deferred = deferredByType.get(op.type) ?? [];
969
+ if (!deferred.length) continue;
970
+ const failedTarget = deferred.flatMap(fieldRefTargets).find((t) => failedTypes.has(t));
971
+ if (failedTarget) {
972
+ failedTypes.add(op.type);
973
+ results[index] = { op, status: "blocked", reason: `blocked: dependency "${failedTarget}" was not created` };
974
+ continue;
975
+ }
976
+ const id = idByType.get(op.type);
977
+ if (id == null) {
978
+ results[index] = { op, status: "blocked", reason: `no definition id for "${op.type}"` };
979
+ continue;
980
+ }
981
+ const definition = { fieldDefinitions: deferred.map((f) => ({ create: f })) };
982
+ const data = await execute(client, UPDATE_DEFINITION_MUTATION, { id, definition });
983
+ const payload = data.metaobjectDefinitionUpdate;
984
+ if (payload.userErrors.length) {
985
+ failedTypes.add(op.type);
986
+ results[index] = { op, status: "failed", userErrors: payload.userErrors };
987
+ }
988
+ }
989
+ for (const { op, index } of otherOps) results[index] = await applyFieldOp(op);
905
990
  const counts = { applied: 0, skipped: 0, blocked: 0, failed: 0 };
906
991
  for (const r of results) counts[r.status]++;
907
992
  return { results, counts, ok: counts.failed === 0 && counts.blocked === 0 };