@fmaplabs/meta-manifest 0.2.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.
@@ -4,7 +4,7 @@ import {
4
4
  PULL_DEFINITION_QUERY,
5
5
  UPDATE_DEFINITION_MUTATION,
6
6
  execute
7
- } from "./chunk-3R6VQ3Z3.js";
7
+ } from "./chunk-VSJUGUH7.js";
8
8
 
9
9
  // src/fields/base.ts
10
10
  var Field = class {
@@ -13,6 +13,14 @@ var Field = class {
13
13
  required = false;
14
14
  name;
15
15
  description;
16
+ filterable = false;
17
+ /** Apply the shared field options; call from each concrete constructor. */
18
+ applyCommon(opts) {
19
+ this.required = opts.required ?? false;
20
+ this.name = opts.name;
21
+ this.description = opts.description;
22
+ this.filterable = opts.filterable ?? false;
23
+ }
16
24
  /** Constraint validation on an already-typed value. */
17
25
  check(_value) {
18
26
  return [];
@@ -69,9 +77,7 @@ var BooleanField = class extends Field {
69
77
  shopifyType = "boolean";
70
78
  constructor(opts) {
71
79
  super();
72
- this.required = opts.required ?? false;
73
- this.name = opts.name;
74
- this.description = opts.description;
80
+ this.applyCommon(opts);
75
81
  }
76
82
  validations() {
77
83
  return [];
@@ -97,9 +103,7 @@ var ListField = class extends Field {
97
103
  this.inner = inner;
98
104
  this.o = o;
99
105
  this.shopifyType = `list.${inner.shopifyType}`;
100
- this.required = o.required ?? false;
101
- this.name = o.name;
102
- this.description = o.description;
106
+ this.applyCommon(o);
103
107
  }
104
108
  inner;
105
109
  o;
@@ -135,9 +139,7 @@ var MeasurementField = class extends Field {
135
139
  constructor(opts, shopifyType) {
136
140
  super();
137
141
  this.shopifyType = shopifyType;
138
- this.required = opts.required ?? false;
139
- this.name = opts.name;
140
- this.description = opts.description;
142
+ this.applyCommon(opts);
141
143
  }
142
144
  shopifyType;
143
145
  wireIsJson = true;
@@ -173,9 +175,7 @@ var MoneyField = class extends Field {
173
175
  wireIsJson = true;
174
176
  constructor(opts) {
175
177
  super();
176
- this.required = opts.required ?? false;
177
- this.name = opts.name;
178
- this.description = opts.description;
178
+ this.applyCommon(opts);
179
179
  }
180
180
  validations() {
181
181
  return [];
@@ -201,9 +201,7 @@ var NumericField = class extends Field {
201
201
  constructor(opts) {
202
202
  super();
203
203
  this.opts = opts;
204
- this.required = opts.required ?? false;
205
- this.name = opts.name;
206
- this.description = opts.description;
204
+ this.applyCommon(opts);
207
205
  }
208
206
  opts;
209
207
  toJson(value) {
@@ -268,9 +266,7 @@ var RatingField = class extends Field {
268
266
  constructor(o) {
269
267
  super();
270
268
  this.o = o;
271
- this.required = o.required ?? false;
272
- this.name = o.name;
273
- this.description = o.description;
269
+ this.applyCommon(o);
274
270
  }
275
271
  o;
276
272
  shopifyType = "rating";
@@ -325,9 +321,7 @@ function rating(opts) {
325
321
  var GidField = class extends Field {
326
322
  constructor(opts) {
327
323
  super();
328
- this.required = opts.required ?? false;
329
- this.name = opts.name;
330
- this.description = opts.description;
324
+ this.applyCommon(opts);
331
325
  }
332
326
  toJson(value) {
333
327
  return value;
@@ -372,6 +366,17 @@ var MetaobjectRefField = class extends GidField {
372
366
  return [{ name: "metaobject_definition_type", value: resolveType(this.target) }];
373
367
  }
374
368
  };
369
+ var MixedRefField = class extends GidField {
370
+ constructor(targets, opts) {
371
+ super(opts);
372
+ this.targets = targets;
373
+ }
374
+ targets;
375
+ shopifyType = "mixed_reference";
376
+ validations() {
377
+ return [{ name: "metaobject_definition_types", value: JSON.stringify(this.targets.map(resolveType)) }];
378
+ }
379
+ };
375
380
  function product(opts = {}) {
376
381
  return new SimpleRefField(opts, "product_reference");
377
382
  }
@@ -390,14 +395,15 @@ function file(opts = {}) {
390
395
  function ref(target, opts = {}) {
391
396
  return new MetaobjectRefField(target, opts);
392
397
  }
398
+ function mixedRef(targets, opts = {}) {
399
+ return new MixedRefField(targets, opts);
400
+ }
393
401
 
394
402
  // src/fields/scalar.ts
395
403
  var StringScalarField = class extends Field {
396
404
  constructor(opts) {
397
405
  super();
398
- this.required = opts.required ?? false;
399
- this.name = opts.name;
400
- this.description = opts.description;
406
+ this.applyCommon(opts);
401
407
  }
402
408
  toJson(value) {
403
409
  return value;
@@ -447,9 +453,7 @@ var JsonField = class extends Field {
447
453
  wireIsJson = true;
448
454
  constructor(opts) {
449
455
  super();
450
- this.required = opts.required ?? false;
451
- this.name = opts.name;
452
- this.description = opts.description;
456
+ this.applyCommon(opts);
453
457
  }
454
458
  validations() {
455
459
  return [];
@@ -483,9 +487,7 @@ var TextField = class extends Field {
483
487
  super();
484
488
  this.opts = opts;
485
489
  this.shopifyType = multiline ? "multi_line_text_field" : "single_line_text_field";
486
- this.required = opts.required ?? false;
487
- this.name = opts.name;
488
- this.description = opts.description;
490
+ this.applyCommon(opts);
489
491
  }
490
492
  opts;
491
493
  shopifyType;
@@ -544,6 +546,7 @@ var m = {
544
546
  page,
545
547
  file,
546
548
  ref,
549
+ mixedRef,
547
550
  list
548
551
  };
549
552
 
@@ -553,16 +556,44 @@ function mapAccess(access) {
553
556
  const out = {};
554
557
  if (access.admin) out.admin = access.admin.toUpperCase();
555
558
  if (access.storefront) out.storefront = access.storefront.toUpperCase();
559
+ if (access.customerAccount) out.customerAccount = access.customerAccount.toUpperCase();
556
560
  return out;
557
561
  }
558
562
  function mapCapabilities(caps) {
559
563
  if (!caps) return void 0;
560
564
  const out = {};
561
- for (const [k, v2] of Object.entries(caps)) if (v2 != null) out[k] = { enabled: v2 };
565
+ if (caps.publishable != null) out.publishable = { enabled: caps.publishable };
566
+ if (caps.translatable != null) out.translatable = { enabled: caps.translatable };
567
+ if (caps.renderable != null) {
568
+ if (typeof caps.renderable === "boolean") {
569
+ out.renderable = { enabled: caps.renderable };
570
+ } else {
571
+ const data = {};
572
+ if (caps.renderable.metaTitleKey != null) data.metaTitleKey = caps.renderable.metaTitleKey;
573
+ if (caps.renderable.metaDescriptionKey != null) data.metaDescriptionKey = caps.renderable.metaDescriptionKey;
574
+ out.renderable = Object.keys(data).length ? { enabled: true, data } : { enabled: true };
575
+ }
576
+ }
577
+ if (caps.onlineStore != null) {
578
+ const data = { urlHandle: caps.onlineStore.urlHandle };
579
+ if (caps.onlineStore.createRedirects != null) data.createRedirects = caps.onlineStore.createRedirects;
580
+ out.onlineStore = { enabled: true, data };
581
+ }
562
582
  return Object.keys(out).length ? out : void 0;
563
583
  }
584
+ function validateSeoKeys(caps, fieldKeys, name) {
585
+ const r = caps?.renderable;
586
+ if (!r || typeof r === "boolean") return;
587
+ for (const prop of ["metaTitleKey", "metaDescriptionKey"]) {
588
+ const key = r[prop];
589
+ if (key != null && !fieldKeys.has(key)) {
590
+ throw new Error(`renderable.${prop} "${key}" is not a declared field key on "${name}".`);
591
+ }
592
+ }
593
+ }
564
594
  function toDefinitionInput(schema) {
565
595
  const { config } = schema;
596
+ validateSeoKeys(config.capabilities, new Set(Object.keys(schema.fields)), config.name);
566
597
  const fieldDefinitions = Object.entries(schema.fields).map(([key, field]) => {
567
598
  const def = {
568
599
  key,
@@ -572,6 +603,7 @@ function toDefinitionInput(schema) {
572
603
  validations: field.validations()
573
604
  };
574
605
  if (field.description != null) def.description = field.description;
606
+ if (field.filterable) def.capabilities = { adminFilterable: { enabled: true } };
575
607
  return def;
576
608
  });
577
609
  const out = {
@@ -644,6 +676,12 @@ function defineMetaobject(handle, config) {
644
676
 
645
677
  // src/codegen.ts
646
678
  var APP_PREFIX = "$app:";
679
+ function isMerchant(type) {
680
+ return !type.startsWith(APP_PREFIX);
681
+ }
682
+ function filterableEntry(field) {
683
+ return field.filterable ? ["filterable: true"] : [];
684
+ }
647
685
  var SIMPLE = {
648
686
  single_line_text_field: "text",
649
687
  multi_line_text_field: "multilineText",
@@ -687,6 +725,27 @@ function refTarget(field) {
687
725
  }
688
726
  return void 0;
689
727
  }
728
+ function refTargets(field) {
729
+ const many = v(field.validations, "metaobject_definition_types");
730
+ if (many) {
731
+ try {
732
+ const arr = JSON.parse(many);
733
+ if (Array.isArray(arr)) return arr.map(String);
734
+ } catch {
735
+ }
736
+ }
737
+ const single = v(field.validations, "metaobject_definition_type");
738
+ return single ? [single] : [];
739
+ }
740
+ function mixedTargetsLiteral(field, typeToIdent, warnings) {
741
+ const targets = refTargets(field);
742
+ const idents = targets.map((t) => typeToIdent.get(t));
743
+ if (!targets.length || idents.some((i) => !i)) {
744
+ warnings.push(`unresolved mixed reference on field "${field.key}"`);
745
+ return void 0;
746
+ }
747
+ return `[${idents.map((i) => `() => ${i}`).join(", ")}]`;
748
+ }
690
749
  function optsLiteral(entries) {
691
750
  return entries.length ? `{ ${entries.join(", ")} }` : "";
692
751
  }
@@ -725,8 +784,8 @@ function scalarEntries(field, warnings, builder) {
725
784
  jsonArr("file_type_options", "accept");
726
785
  return e;
727
786
  }
728
- function scalarCall(builder, field, warnings) {
729
- const lit = optsLiteral(scalarEntries(field, warnings, builder));
787
+ function scalarCall(builder, field, warnings, extra = []) {
788
+ const lit = optsLiteral([...scalarEntries(field, warnings, builder), ...extra]);
730
789
  return lit ? `m.${builder}(${lit})` : `m.${builder}()`;
731
790
  }
732
791
  function fieldCall(field, typeToIdent, warnings) {
@@ -736,6 +795,7 @@ function fieldCall(field, typeToIdent, warnings) {
736
795
  const max = v(field.validations, "max");
737
796
  const e = [`min: ${Number(min ?? 1)}`, `max: ${Number(max ?? 5)}`];
738
797
  if (field.required) e.unshift("required: true");
798
+ e.push(...filterableEntry(field));
739
799
  if (min === void 0 || max === void 0) warnings.push(`rating field "${field.key}" missing min/max`);
740
800
  return `m.rating(${optsLiteral(e)})`;
741
801
  }
@@ -746,7 +806,20 @@ function fieldCall(field, typeToIdent, warnings) {
746
806
  warnings.push(`unresolved reference on field "${field.key}"`);
747
807
  return `m.json() /* TODO: unmapped reference */`;
748
808
  }
749
- return field.required ? `m.ref(() => ${ident}, { required: true })` : `m.ref(() => ${ident})`;
809
+ const refEntries = [];
810
+ if (field.required) refEntries.push("required: true");
811
+ refEntries.push(...filterableEntry(field));
812
+ const opts = optsLiteral(refEntries);
813
+ return opts ? `m.ref(() => ${ident}, ${opts})` : `m.ref(() => ${ident})`;
814
+ }
815
+ if (type === "mixed_reference") {
816
+ const arr = mixedTargetsLiteral(field, typeToIdent, warnings);
817
+ if (!arr) return `m.json() /* TODO: unmapped mixed reference */`;
818
+ const refEntries = [];
819
+ if (field.required) refEntries.push("required: true");
820
+ refEntries.push(...filterableEntry(field));
821
+ const opts = optsLiteral(refEntries);
822
+ return opts ? `m.mixedRef(${arr}, ${opts})` : `m.mixedRef(${arr})`;
750
823
  }
751
824
  if (type.startsWith("list.")) {
752
825
  const inner = type.slice("list.".length);
@@ -756,6 +829,7 @@ function fieldCall(field, typeToIdent, warnings) {
756
829
  const max = v(field.validations, "list.max");
757
830
  if (min !== void 0) listEntries.push(`min: ${Number(min)}`);
758
831
  if (max !== void 0) listEntries.push(`max: ${Number(max)}`);
832
+ listEntries.push(...filterableEntry(field));
759
833
  const listOpts = optsLiteral(listEntries);
760
834
  let innerCall;
761
835
  if (inner === "metaobject_reference") {
@@ -766,6 +840,10 @@ function fieldCall(field, typeToIdent, warnings) {
766
840
  return `m.json() /* TODO: unmapped list reference */`;
767
841
  }
768
842
  innerCall = `m.ref(() => ${ident})`;
843
+ } else if (inner === "mixed_reference") {
844
+ const arr = mixedTargetsLiteral(field, typeToIdent, warnings);
845
+ if (!arr) return `m.json() /* TODO: unmapped list mixed reference */`;
846
+ innerCall = `m.mixedRef(${arr})`;
769
847
  } else if (SIMPLE[inner]) {
770
848
  innerCall = scalarCall(SIMPLE[inner], { ...field, required: false }, warnings);
771
849
  } else {
@@ -774,17 +852,53 @@ function fieldCall(field, typeToIdent, warnings) {
774
852
  }
775
853
  return listOpts ? `m.list(${innerCall}, ${listOpts})` : `m.list(${innerCall})`;
776
854
  }
777
- if (SIMPLE[type]) return scalarCall(SIMPLE[type], field, warnings);
855
+ if (SIMPLE[type]) return scalarCall(SIMPLE[type], field, warnings, filterableEntry(field));
778
856
  warnings.push(`unmapped field type "${type}" on field "${field.key}"`);
779
857
  return `m.json() /* TODO: unmapped type ${type} */`;
780
858
  }
859
+ function accessSource(access) {
860
+ if (!access) return "";
861
+ const parts = [];
862
+ if (access.admin === "MERCHANT_READ_WRITE") parts.push(`admin: "merchant_read_write"`);
863
+ if (access.storefront === "PUBLIC_READ") parts.push(`storefront: "public_read"`);
864
+ if (access.customerAccount === "READ") parts.push(`customerAccount: "read"`);
865
+ return parts.length ? `{ ${parts.join(", ")} }` : "";
866
+ }
867
+ function capabilitiesSource(caps) {
868
+ if (!caps) return "";
869
+ const parts = [];
870
+ if (caps.publishable?.enabled) parts.push(`publishable: true`);
871
+ if (caps.translatable?.enabled) parts.push(`translatable: true`);
872
+ if (caps.renderable?.enabled) {
873
+ const d = caps.renderable.data;
874
+ const dparts = [];
875
+ if (d?.metaTitleKey != null) dparts.push(`metaTitleKey: ${JSON.stringify(d.metaTitleKey)}`);
876
+ if (d?.metaDescriptionKey != null) dparts.push(`metaDescriptionKey: ${JSON.stringify(d.metaDescriptionKey)}`);
877
+ parts.push(dparts.length ? `renderable: { ${dparts.join(", ")} }` : `renderable: true`);
878
+ }
879
+ if (caps.onlineStore?.enabled && caps.onlineStore.data?.urlHandle != null) {
880
+ parts.push(`onlineStore: { urlHandle: ${JSON.stringify(caps.onlineStore.data.urlHandle)} }`);
881
+ }
882
+ return parts.length ? `{ ${parts.join(", ")} }` : "";
883
+ }
884
+ function defConfigLines(def) {
885
+ const lines = [];
886
+ if (isMerchant(def.type)) lines.push(` scope: "merchant",`);
887
+ if (def.name) lines.push(` name: ${JSON.stringify(def.name)},`);
888
+ if (def.description != null) lines.push(` description: ${JSON.stringify(def.description)},`);
889
+ if (def.displayNameKey != null) lines.push(` displayName: ${JSON.stringify(def.displayNameKey)},`);
890
+ const access = accessSource(def.access);
891
+ if (access) lines.push(` access: ${access},`);
892
+ const caps = capabilitiesSource(def.capabilities);
893
+ if (caps) lines.push(` capabilities: ${caps},`);
894
+ return lines.length ? `
895
+ ${lines.join("\n")}` : "";
896
+ }
781
897
  function defSource(def, typeToIdent, warnings) {
782
898
  const ident = typeToIdent.get(def.type);
783
899
  const handle = handleOf(def.type);
784
900
  const fields = def.fields.map((f) => ` ${f.key}: ${fieldCall(f, typeToIdent, warnings)},`).join("\n");
785
- const name = def.name ? `
786
- name: ${JSON.stringify(def.name)},` : "";
787
- return `export const ${ident} = defineMetaobject(${JSON.stringify(handle)}, {${name}
901
+ return `export const ${ident} = defineMetaobject(${JSON.stringify(handle)}, {${defConfigLines(def)}
788
902
  fields: {
789
903
  ${fields}
790
904
  },
@@ -796,6 +910,8 @@ function referencedTypes(def) {
796
910
  if (f.type === "metaobject_reference" || f.type === "list.metaobject_reference") {
797
911
  const t = refTarget(f);
798
912
  if (t) out.add(t);
913
+ } else if (f.type === "mixed_reference" || f.type === "list.mixed_reference") {
914
+ for (const t of refTargets(f)) out.add(t);
799
915
  }
800
916
  }
801
917
  return out;
@@ -844,6 +960,42 @@ function sameValidations(a, b) {
844
960
  const norm = (v2) => JSON.stringify([...v2].sort((x, y) => x.name.localeCompare(y.name)));
845
961
  return norm(a) === norm(b);
846
962
  }
963
+ var CAP_KEYS = ["publishable", "translatable", "renderable", "onlineStore"];
964
+ function accessChanged(local, remote) {
965
+ for (const key of ["admin", "storefront", "customerAccount"]) {
966
+ const lv = local[key];
967
+ if (lv != null && remote?.[key] !== lv) return true;
968
+ }
969
+ return false;
970
+ }
971
+ function capabilitiesChanged(local, remote) {
972
+ for (const key of CAP_KEYS) {
973
+ const lc = local[key];
974
+ if (!lc) continue;
975
+ const rc = remote?.[key];
976
+ if ((rc?.enabled ?? false) !== lc.enabled) return true;
977
+ const ldata = lc.data;
978
+ if (ldata) {
979
+ const rdata = rc?.data;
980
+ for (const [k, v2] of Object.entries(ldata)) {
981
+ if (v2 != null && rdata?.[k] !== v2) return true;
982
+ }
983
+ }
984
+ }
985
+ return false;
986
+ }
987
+ function disablesOnlineStore(local, remote) {
988
+ return local.onlineStore?.enabled === false && remote?.onlineStore?.enabled === true;
989
+ }
990
+ function definitionChanges(local, remote) {
991
+ const changes = [];
992
+ if (local.name != null && local.name !== remote.name) changes.push("name");
993
+ if (local.description != null && local.description !== remote.description) changes.push("description");
994
+ if (local.displayNameKey != null && local.displayNameKey !== remote.displayNameKey) changes.push("displayNameKey");
995
+ if (local.access && accessChanged(local.access, remote.access)) changes.push("access");
996
+ if (local.capabilities && capabilitiesChanged(local.capabilities, remote.capabilities)) changes.push("capabilities");
997
+ return changes;
998
+ }
847
999
  function diff(local, remote) {
848
1000
  const ops = [];
849
1001
  const remoteByType = new Map(remote.map((d) => [d.type, d]));
@@ -867,6 +1019,7 @@ function diff(local, remote) {
867
1019
  }
868
1020
  const changes = {};
869
1021
  if (rf.required !== lf.required) changes.required = lf.required;
1022
+ if (rf.filterable !== lf.filterable) changes.filterable = lf.filterable;
870
1023
  if (!sameValidations(rf.validations, lf.validations)) changes.validations = lf.validations;
871
1024
  if (Object.keys(changes).length) {
872
1025
  ops.push({ kind: "updateField", type: localDef.type, key: lf.key, changes });
@@ -877,49 +1030,179 @@ function diff(local, remote) {
877
1030
  ops.push({ kind: "removeField", type: localDef.type, key: rf.key, destructive: true });
878
1031
  }
879
1032
  }
1033
+ const metaChanges = definitionChanges(localDef, remoteDef);
1034
+ if (metaChanges.length) {
1035
+ const destructive = localDef.capabilities ? disablesOnlineStore(localDef.capabilities, remoteDef.capabilities) : false;
1036
+ ops.push({
1037
+ kind: "updateDefinition",
1038
+ type: localDef.type,
1039
+ changes: metaChanges,
1040
+ ...destructive ? { destructive: true } : {}
1041
+ });
1042
+ }
880
1043
  }
881
1044
  return ops;
882
1045
  }
883
1046
 
884
1047
  // src/sync/normalize.ts
885
- function normalizeLocal(schema) {
886
- const def = schema.toDefinitionInput();
887
- return {
1048
+ function localCapabilities(caps) {
1049
+ const out = {};
1050
+ if (caps?.publishable) out.publishable = { enabled: caps.publishable.enabled };
1051
+ if (caps?.translatable) out.translatable = { enabled: caps.translatable.enabled };
1052
+ if (caps?.renderable) {
1053
+ out.renderable = { enabled: caps.renderable.enabled };
1054
+ if (caps.renderable.data) out.renderable.data = { ...caps.renderable.data };
1055
+ }
1056
+ out.onlineStore = caps?.onlineStore ? { enabled: caps.onlineStore.enabled, data: { urlHandle: caps.onlineStore.data?.urlHandle } } : { enabled: false };
1057
+ return out;
1058
+ }
1059
+ function normalizeDefinition(def) {
1060
+ const out = {
888
1061
  type: def.type,
889
1062
  name: def.name,
1063
+ capabilities: localCapabilities(def.capabilities),
890
1064
  fields: def.fieldDefinitions.map((f) => ({
891
1065
  key: f.key,
892
1066
  type: f.type,
893
1067
  required: f.required,
1068
+ filterable: f.capabilities?.adminFilterable?.enabled ?? false,
894
1069
  validations: f.validations
895
1070
  }))
896
1071
  };
1072
+ if (def.description != null) out.description = def.description;
1073
+ if (def.displayNameKey != null) out.displayNameKey = def.displayNameKey;
1074
+ if (def.access) out.access = { ...def.access };
1075
+ return out;
1076
+ }
1077
+ function normalizeLocal(schema) {
1078
+ return normalizeDefinition(schema.toDefinitionInput());
1079
+ }
1080
+ function remoteAccess(access) {
1081
+ if (!access) return void 0;
1082
+ const out = {};
1083
+ if (access.admin != null) out.admin = access.admin;
1084
+ if (access.storefront != null) out.storefront = access.storefront;
1085
+ if (access.customerAccount != null) out.customerAccount = access.customerAccount;
1086
+ return out;
1087
+ }
1088
+ function remoteCapabilities(caps) {
1089
+ const out = {};
1090
+ if (!caps) return out;
1091
+ if (caps.publishable) out.publishable = { enabled: !!caps.publishable.enabled };
1092
+ if (caps.translatable) out.translatable = { enabled: !!caps.translatable.enabled };
1093
+ if (caps.renderable) {
1094
+ out.renderable = { enabled: !!caps.renderable.enabled };
1095
+ const d = caps.renderable.data;
1096
+ if (d) {
1097
+ const data = {};
1098
+ if (d.metaTitleKey != null) data.metaTitleKey = d.metaTitleKey;
1099
+ if (d.metaDescriptionKey != null) data.metaDescriptionKey = d.metaDescriptionKey;
1100
+ if (Object.keys(data).length) out.renderable.data = data;
1101
+ }
1102
+ }
1103
+ if (caps.onlineStore) {
1104
+ out.onlineStore = { enabled: !!caps.onlineStore.enabled };
1105
+ if (caps.onlineStore.data?.urlHandle != null) out.onlineStore.data = { urlHandle: caps.onlineStore.data.urlHandle };
1106
+ }
1107
+ return out;
897
1108
  }
898
1109
  function normalizeRemote(def) {
899
- return {
1110
+ const out = {
900
1111
  type: def.type,
901
1112
  name: def.name,
1113
+ capabilities: remoteCapabilities(def.capabilities),
902
1114
  fields: def.fieldDefinitions.map((f) => ({
903
1115
  key: f.key,
904
1116
  type: typeof f.type === "string" ? f.type : f.type.name,
905
1117
  required: f.required,
1118
+ filterable: f.capabilities?.adminFilterable?.enabled ?? false,
906
1119
  validations: f.validations ?? []
907
1120
  }))
908
1121
  };
1122
+ if (def.description != null) out.description = def.description;
1123
+ if (def.displayNameKey != null) out.displayNameKey = def.displayNameKey;
1124
+ const access = remoteAccess(def.access);
1125
+ if (access) out.access = access;
1126
+ return out;
1127
+ }
1128
+
1129
+ // src/sync/resolve.ts
1130
+ var APP_PREFIX2 = "$app:";
1131
+ function effectiveScope(schema, config) {
1132
+ return schema.config.scope ?? config.scope ?? "app";
1133
+ }
1134
+ function effectiveType(handle, scope) {
1135
+ return scope === "merchant" ? handle : `${APP_PREFIX2}${handle}`;
1136
+ }
1137
+ function rewriteReference(v2, effByCanonical) {
1138
+ if (v2.name === "metaobject_definition_type") {
1139
+ const mapped = effByCanonical.get(v2.value);
1140
+ return mapped ? { ...v2, value: mapped } : v2;
1141
+ }
1142
+ if (v2.name === "metaobject_definition_types") {
1143
+ try {
1144
+ const parsed = JSON.parse(v2.value);
1145
+ if (Array.isArray(parsed)) {
1146
+ const rewritten = parsed.map((t) => typeof t === "string" ? effByCanonical.get(t) ?? t : t);
1147
+ return { ...v2, value: JSON.stringify(rewritten) };
1148
+ }
1149
+ } catch {
1150
+ }
1151
+ }
1152
+ return v2;
1153
+ }
1154
+ function resolveAdmin(out, handle, scope, config) {
1155
+ const explicitAdmin = out.access?.admin;
1156
+ if (scope === "merchant") {
1157
+ if (explicitAdmin != null) {
1158
+ throw new Error(
1159
+ `"${handle}" is merchant-scoped but sets access.admin; admin access is only valid on app-scoped metaobjects.`
1160
+ );
1161
+ }
1162
+ return;
1163
+ }
1164
+ if (explicitAdmin == null) {
1165
+ const admin = config.merchantEditable ? "MERCHANT_READ_WRITE" : "MERCHANT_READ";
1166
+ out.access = { ...out.access, admin };
1167
+ }
1168
+ }
1169
+ function resolveDefinitions(schemas, config = {}) {
1170
+ const effByCanonical = /* @__PURE__ */ new Map();
1171
+ for (const s of schemas) {
1172
+ effByCanonical.set(`${APP_PREFIX2}${s.handle}`, effectiveType(s.handle, effectiveScope(s, config)));
1173
+ }
1174
+ return schemas.map((s) => {
1175
+ const scope = effectiveScope(s, config);
1176
+ const base = s.toDefinitionInput();
1177
+ const fieldDefinitions = base.fieldDefinitions.map((f) => ({
1178
+ ...f,
1179
+ validations: f.validations.map((v2) => rewriteReference(v2, effByCanonical))
1180
+ }));
1181
+ const out = { ...base, type: effectiveType(s.handle, scope), fieldDefinitions };
1182
+ resolveAdmin(out, s.handle, scope, config);
1183
+ return out;
1184
+ });
909
1185
  }
910
1186
 
911
1187
  // src/sync/pull.ts
1188
+ function toDefinition(type, node) {
1189
+ return {
1190
+ type,
1191
+ name: node.name,
1192
+ description: node.description,
1193
+ displayNameKey: node.displayNameKey,
1194
+ access: node.access,
1195
+ capabilities: node.capabilities,
1196
+ fieldDefinitions: node.fieldDefinitions
1197
+ };
1198
+ }
912
1199
  async function pull(client, types) {
913
1200
  const out = [];
914
1201
  for (const type of types) {
915
1202
  const data = await execute(client, PULL_DEFINITION_QUERY, { type });
916
1203
  const node = data.metaobjectDefinitionByType;
917
1204
  if (!node) continue;
918
- out.push({
919
- id: node.id,
920
- type,
921
- definition: { type, name: node.name, fieldDefinitions: node.fieldDefinitions }
922
- });
1205
+ out.push({ id: node.id, type, definition: toDefinition(type, node) });
923
1206
  }
924
1207
  return out;
925
1208
  }
@@ -937,7 +1220,7 @@ async function pullAll(client, opts = {}) {
937
1220
  const canonical = toCanonicalType(node.type);
938
1221
  if (appOwnedOnly && !canonical) continue;
939
1222
  const type = canonical ?? node.type;
940
- out.push({ id: node.id, type, definition: { type, name: node.name, fieldDefinitions: node.fieldDefinitions } });
1223
+ out.push({ id: node.id, type, definition: toDefinition(type, node) });
941
1224
  }
942
1225
  after = data.metaobjectDefinitions.pageInfo.hasNextPage ? data.metaobjectDefinitions.pageInfo.endCursor : null;
943
1226
  } while (after !== null);
@@ -945,28 +1228,57 @@ async function pullAll(client, opts = {}) {
945
1228
  }
946
1229
 
947
1230
  // src/sync/push.ts
1231
+ function definitionUpdateFor(def, changes) {
1232
+ const out = {};
1233
+ for (const c of changes) {
1234
+ if (c === "name") out.name = def.name;
1235
+ else if (c === "description") out.description = def.description;
1236
+ else if (c === "displayNameKey") out.displayNameKey = def.displayNameKey;
1237
+ else if (c === "access") out.access = def.access;
1238
+ else if (c === "capabilities") {
1239
+ const caps = def.capabilities;
1240
+ const payload = {};
1241
+ if (caps?.publishable) payload.publishable = caps.publishable;
1242
+ if (caps?.translatable) payload.translatable = caps.translatable;
1243
+ if (caps?.renderable) payload.renderable = caps.renderable;
1244
+ payload.onlineStore = caps?.onlineStore ?? { enabled: false };
1245
+ out.capabilities = payload;
1246
+ }
1247
+ }
1248
+ return out;
1249
+ }
948
1250
  function fieldInputFor(defByType, type, key) {
949
1251
  return defByType.get(type)?.fieldDefinitions.find((f) => f.key === key);
950
1252
  }
951
- function referenceEdges(def) {
1253
+ function fieldRefTargets(field) {
952
1254
  const out = [];
953
- for (const field of def.fieldDefinitions) {
954
- for (const v2 of field.validations) {
955
- if (v2.name === "metaobject_definition_type") {
956
- out.push(v2.value);
957
- } else if (v2.name === "metaobject_definition_types") {
958
- try {
959
- const parsed = JSON.parse(v2.value);
960
- if (Array.isArray(parsed)) {
961
- for (const t of parsed) if (typeof t === "string") out.push(t);
962
- }
963
- } catch {
1255
+ for (const v2 of field.validations) {
1256
+ if (v2.name === "metaobject_definition_type") {
1257
+ out.push(v2.value);
1258
+ } else if (v2.name === "metaobject_definition_types") {
1259
+ try {
1260
+ const parsed = JSON.parse(v2.value);
1261
+ if (Array.isArray(parsed)) {
1262
+ for (const t of parsed) if (typeof t === "string") out.push(t);
964
1263
  }
1264
+ } catch {
965
1265
  }
966
1266
  }
967
1267
  }
968
1268
  return out;
969
1269
  }
1270
+ function referenceEdges(def) {
1271
+ return def.fieldDefinitions.flatMap(fieldRefTargets);
1272
+ }
1273
+ function splitCyclicFields(def, cyclicTypes) {
1274
+ const pass1 = [];
1275
+ const deferred = [];
1276
+ for (const f of def.fieldDefinitions) {
1277
+ const breaksCycle = fieldRefTargets(f).some((t) => cyclicTypes.has(t) && t !== def.type);
1278
+ (breaksCycle ? deferred : pass1).push(f);
1279
+ }
1280
+ return { pass1, deferred };
1281
+ }
970
1282
  function topoSortCreates(types, deps) {
971
1283
  const remaining = /* @__PURE__ */ new Map();
972
1284
  const dependents = /* @__PURE__ */ new Map();
@@ -1009,50 +1321,51 @@ async function push(client, plan, sources, options) {
1009
1321
  deps.set(op.type, new Set(targets.filter((t) => createTypes.has(t) && t !== op.type)));
1010
1322
  }
1011
1323
  const { ordered, unordered } = topoSortCreates(createTypes, deps);
1012
- const orderedSet = new Set(ordered);
1013
1324
  const cyclicTypes = new Set(unordered);
1014
1325
  const createByType = new Map(createOps.map((x) => [x.op.type, x]));
1015
- const execOrder = [
1016
- ...ordered.map((t) => createByType.get(t)),
1017
- ...createOps.filter((x) => !orderedSet.has(x.op.type)),
1018
- ...otherOps
1019
- ];
1020
1326
  const failedTypes = /* @__PURE__ */ new Set();
1021
- async function applyOp(op) {
1022
- if (op.kind === "createDefinition") {
1023
- if (cyclicTypes.has(op.type)) {
1024
- failedTypes.add(op.type);
1025
- return { op, status: "blocked", reason: "reference cycle \u2014 two-pass create deferred" };
1026
- }
1027
- for (const dep of deps.get(op.type) ?? []) {
1028
- if (failedTypes.has(dep)) {
1029
- failedTypes.add(op.type);
1030
- return { op, status: "blocked", reason: `blocked: dependency "${dep}" was not created` };
1031
- }
1032
- }
1033
- const def = defByType.get(op.type);
1034
- if (!def) {
1035
- failedTypes.add(op.type);
1036
- return { op, status: "blocked", reason: `no definition input for "${op.type}"` };
1037
- }
1038
- const data2 = await execute(client, CREATE_DEFINITION_MUTATION, { definition: def });
1039
- const payload2 = data2.metaobjectDefinitionCreate;
1040
- if (payload2.userErrors.length) {
1327
+ async function createDefinition(op, definition) {
1328
+ const data = await execute(client, CREATE_DEFINITION_MUTATION, { definition });
1329
+ const payload = data.metaobjectDefinitionCreate;
1330
+ if (payload.userErrors.length) {
1331
+ failedTypes.add(op.type);
1332
+ return { op, status: "failed", userErrors: payload.userErrors };
1333
+ }
1334
+ const id = payload.metaobjectDefinition?.id;
1335
+ if (id) idByType.set(op.type, id);
1336
+ return { op, status: "applied", id };
1337
+ }
1338
+ async function applyAcyclicCreate(op) {
1339
+ for (const dep of deps.get(op.type) ?? []) {
1340
+ if (failedTypes.has(dep)) {
1041
1341
  failedTypes.add(op.type);
1042
- return { op, status: "failed", userErrors: payload2.userErrors };
1342
+ return { op, status: "blocked", reason: `blocked: dependency "${dep}" was not created` };
1043
1343
  }
1044
- const id2 = payload2.metaobjectDefinition?.id;
1045
- if (id2) idByType.set(op.type, id2);
1046
- return { op, status: "applied", id: id2 };
1047
1344
  }
1048
- const destructive = op.kind === "removeField" || op.kind === "changeFieldType";
1345
+ const def = defByType.get(op.type);
1346
+ if (!def) {
1347
+ failedTypes.add(op.type);
1348
+ return { op, status: "blocked", reason: `no definition input for "${op.type}"` };
1349
+ }
1350
+ return createDefinition(op, def);
1351
+ }
1352
+ async function applyFieldOp(op) {
1353
+ const destructive = "destructive" in op && op.destructive === true;
1049
1354
  if (destructive && !allowDestructive) return { op, status: "skipped", reason: "destructive" };
1050
1355
  if (failedTypes.has(op.type)) return { op, status: "blocked", reason: `blocked: definition "${op.type}" was not created` };
1051
1356
  const id = idByType.get(op.type);
1052
1357
  if (id == null) return { op, status: "blocked", reason: `no definition id for "${op.type}"` };
1053
- const fieldDefinitions = fieldOpsFor(op);
1054
- if (!fieldDefinitions) return { op, status: "blocked", reason: `no field input for "${op.type}"` };
1055
- const data = await execute(client, UPDATE_DEFINITION_MUTATION, { id, definition: { fieldDefinitions } });
1358
+ let definition;
1359
+ if (op.kind === "updateDefinition") {
1360
+ const def = defByType.get(op.type);
1361
+ if (!def) return { op, status: "blocked", reason: `no definition input for "${op.type}"` };
1362
+ definition = definitionUpdateFor(def, op.changes);
1363
+ } else {
1364
+ const fieldDefinitions = fieldOpsFor(op);
1365
+ if (!fieldDefinitions) return { op, status: "blocked", reason: `no field input for "${op.type}"` };
1366
+ definition = { fieldDefinitions };
1367
+ }
1368
+ const data = await execute(client, UPDATE_DEFINITION_MUTATION, { id, definition });
1056
1369
  const payload = data.metaobjectDefinitionUpdate;
1057
1370
  if (payload.userErrors.length) return { op, status: "failed", userErrors: payload.userErrors };
1058
1371
  return { op, status: "applied", id: payload.metaobjectDefinition?.id ?? id };
@@ -1073,6 +1386,9 @@ async function push(client, plan, sources, options) {
1073
1386
  validations: field.validations
1074
1387
  };
1075
1388
  if (field.description != null) update.description = field.description;
1389
+ if ("filterable" in op.changes) {
1390
+ update.capabilities = { adminFilterable: { enabled: op.changes.filterable ?? false } };
1391
+ }
1076
1392
  return [{ update }];
1077
1393
  }
1078
1394
  case "removeField":
@@ -1086,7 +1402,53 @@ async function push(client, plan, sources, options) {
1086
1402
  }
1087
1403
  }
1088
1404
  const results = new Array(plan.length);
1089
- for (const { op, index } of execOrder) results[index] = await applyOp(op);
1405
+ for (const type of ordered) {
1406
+ const entry = createByType.get(type);
1407
+ results[entry.index] = await applyAcyclicCreate(entry.op);
1408
+ }
1409
+ const cyclicCreates = createOps.filter((x) => cyclicTypes.has(x.op.type));
1410
+ const deferredByType = /* @__PURE__ */ new Map();
1411
+ for (const { op, index } of cyclicCreates) {
1412
+ const def = defByType.get(op.type);
1413
+ if (!def) {
1414
+ failedTypes.add(op.type);
1415
+ results[index] = { op, status: "blocked", reason: `no definition input for "${op.type}"` };
1416
+ continue;
1417
+ }
1418
+ const failedDep = [...deps.get(op.type) ?? []].find((d) => !cyclicTypes.has(d) && failedTypes.has(d));
1419
+ if (failedDep) {
1420
+ failedTypes.add(op.type);
1421
+ results[index] = { op, status: "blocked", reason: `blocked: dependency "${failedDep}" was not created` };
1422
+ continue;
1423
+ }
1424
+ const { pass1, deferred } = splitCyclicFields(def, cyclicTypes);
1425
+ deferredByType.set(op.type, deferred);
1426
+ results[index] = await createDefinition(op, { ...def, fieldDefinitions: pass1 });
1427
+ }
1428
+ for (const { op, index } of cyclicCreates) {
1429
+ if (results[index]?.status !== "applied") continue;
1430
+ const deferred = deferredByType.get(op.type) ?? [];
1431
+ if (!deferred.length) continue;
1432
+ const failedTarget = deferred.flatMap(fieldRefTargets).find((t) => failedTypes.has(t));
1433
+ if (failedTarget) {
1434
+ failedTypes.add(op.type);
1435
+ results[index] = { op, status: "blocked", reason: `blocked: dependency "${failedTarget}" was not created` };
1436
+ continue;
1437
+ }
1438
+ const id = idByType.get(op.type);
1439
+ if (id == null) {
1440
+ results[index] = { op, status: "blocked", reason: `no definition id for "${op.type}"` };
1441
+ continue;
1442
+ }
1443
+ const definition = { fieldDefinitions: deferred.map((f) => ({ create: f })) };
1444
+ const data = await execute(client, UPDATE_DEFINITION_MUTATION, { id, definition });
1445
+ const payload = data.metaobjectDefinitionUpdate;
1446
+ if (payload.userErrors.length) {
1447
+ failedTypes.add(op.type);
1448
+ results[index] = { op, status: "failed", userErrors: payload.userErrors };
1449
+ }
1450
+ }
1451
+ for (const { op, index } of otherOps) results[index] = await applyFieldOp(op);
1090
1452
  const counts = { applied: 0, skipped: 0, blocked: 0, failed: 0 };
1091
1453
  for (const r of results) counts[r.status]++;
1092
1454
  return { results, counts, ok: counts.failed === 0 && counts.blocked === 0 };
@@ -1099,10 +1461,12 @@ export {
1099
1461
  defineMetaobject,
1100
1462
  generateSchemaSource,
1101
1463
  diff,
1464
+ normalizeDefinition,
1102
1465
  normalizeLocal,
1103
1466
  normalizeRemote,
1467
+ resolveDefinitions,
1104
1468
  pull,
1105
1469
  pullAll,
1106
1470
  push
1107
1471
  };
1108
- //# sourceMappingURL=chunk-GH5DXHS5.js.map
1472
+ //# sourceMappingURL=chunk-OEJJXMYC.js.map