@fmaplabs/meta-manifest 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.
- package/LICENSE +21 -0
- package/README.md +122 -0
- package/dist/chunk-3R6VQ3Z3.js +97 -0
- package/dist/chunk-3R6VQ3Z3.js.map +1 -0
- package/dist/chunk-GH5DXHS5.js +1108 -0
- package/dist/chunk-GH5DXHS5.js.map +1 -0
- package/dist/chunk-PFU5VAO7.js +31 -0
- package/dist/chunk-PFU5VAO7.js.map +1 -0
- package/dist/cli/index.cjs +805 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +12 -0
- package/dist/cli/index.d.ts +12 -0
- package/dist/cli/index.js +239 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/client-CdkKviW2.d.cts +25 -0
- package/dist/client-CdkKviW2.d.ts +25 -0
- package/dist/index.cjs +1229 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +602 -0
- package/dist/index.d.ts +602 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/node/client.cjs +65 -0
- package/dist/node/client.cjs.map +1 -0
- package/dist/node/client.d.cts +14 -0
- package/dist/node/client.d.ts +14 -0
- package/dist/node/client.js +8 -0
- package/dist/node/client.js.map +1 -0
- package/package.json +73 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1229 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
DEFAULT_API_VERSION: () => DEFAULT_API_VERSION,
|
|
24
|
+
Field: () => Field,
|
|
25
|
+
SyncTransportError: () => SyncTransportError,
|
|
26
|
+
defineConfig: () => defineConfig,
|
|
27
|
+
defineMetaobject: () => defineMetaobject,
|
|
28
|
+
diff: () => diff,
|
|
29
|
+
generateSchemaSource: () => generateSchemaSource,
|
|
30
|
+
m: () => m,
|
|
31
|
+
normalizeLocal: () => normalizeLocal,
|
|
32
|
+
normalizeRemote: () => normalizeRemote,
|
|
33
|
+
pull: () => pull,
|
|
34
|
+
pullAll: () => pullAll,
|
|
35
|
+
push: () => push,
|
|
36
|
+
toDefinitionInput: () => toDefinitionInput,
|
|
37
|
+
validateConfig: () => validateConfig
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(src_exports);
|
|
40
|
+
|
|
41
|
+
// src/config.ts
|
|
42
|
+
var DEFAULT_API_VERSION = "2026-07";
|
|
43
|
+
function defineConfig(config) {
|
|
44
|
+
return config;
|
|
45
|
+
}
|
|
46
|
+
function validateConfig(raw) {
|
|
47
|
+
const c = raw;
|
|
48
|
+
for (const key of ["shop", "accessToken", "schema"]) {
|
|
49
|
+
if (!c || typeof c[key] !== "string" || c[key] === "") {
|
|
50
|
+
throw new Error(`Invalid config: missing or empty "${key}".`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return c;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/fields/base.ts
|
|
57
|
+
var Field = class {
|
|
58
|
+
/** True when Shopify stores this type as a JSON string (objects, lists). */
|
|
59
|
+
wireIsJson = false;
|
|
60
|
+
required = false;
|
|
61
|
+
name;
|
|
62
|
+
description;
|
|
63
|
+
/** Constraint validation on an already-typed value. */
|
|
64
|
+
check(_value) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
/** Shopify wire (string or jsonValue) -> typed value (no constraint checking). */
|
|
68
|
+
coerce(wire) {
|
|
69
|
+
let json2 = wire;
|
|
70
|
+
if (this.wireIsJson && typeof wire === "string") {
|
|
71
|
+
try {
|
|
72
|
+
json2 = JSON.parse(wire);
|
|
73
|
+
} catch {
|
|
74
|
+
return { issues: [{ message: "Invalid JSON in field value" }] };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return this.fromJson(json2);
|
|
78
|
+
}
|
|
79
|
+
/** Shopify wire -> typed value, validated. */
|
|
80
|
+
decode(wire) {
|
|
81
|
+
const coerced = this.coerce(wire);
|
|
82
|
+
if (coerced.issues) return coerced;
|
|
83
|
+
const issues = this.check(coerced.value);
|
|
84
|
+
return issues.length ? { issues } : { value: coerced.value };
|
|
85
|
+
}
|
|
86
|
+
/** Typed value -> Shopify wire string. */
|
|
87
|
+
encode(value) {
|
|
88
|
+
const json2 = this.toJson(value);
|
|
89
|
+
return this.wireIsJson ? JSON.stringify(json2) : String(json2);
|
|
90
|
+
}
|
|
91
|
+
/** Element-level JSON form (for embedding inside list values). */
|
|
92
|
+
elementToJson(value) {
|
|
93
|
+
return this.toJson(value);
|
|
94
|
+
}
|
|
95
|
+
/** Element-level decode from a JSON form (coerce + check), for list elements. */
|
|
96
|
+
elementFromJson(json2) {
|
|
97
|
+
const coerced = this.fromJson(json2);
|
|
98
|
+
if (coerced.issues) return coerced;
|
|
99
|
+
const issues = this.check(coerced.value);
|
|
100
|
+
return issues.length ? { issues } : { value: coerced.value };
|
|
101
|
+
}
|
|
102
|
+
get ["~standard"]() {
|
|
103
|
+
return {
|
|
104
|
+
version: 1,
|
|
105
|
+
vendor: "@fmaplabs/meta-manifest",
|
|
106
|
+
validate: (value) => {
|
|
107
|
+
const issues = this.check(value);
|
|
108
|
+
return issues.length ? { issues } : { value };
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// src/fields/boolean.ts
|
|
115
|
+
var BooleanField = class extends Field {
|
|
116
|
+
shopifyType = "boolean";
|
|
117
|
+
constructor(opts) {
|
|
118
|
+
super();
|
|
119
|
+
this.required = opts.required ?? false;
|
|
120
|
+
this.name = opts.name;
|
|
121
|
+
this.description = opts.description;
|
|
122
|
+
}
|
|
123
|
+
validations() {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
toJson(value) {
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
129
|
+
fromJson(json2) {
|
|
130
|
+
if (typeof json2 === "boolean") return { value: json2 };
|
|
131
|
+
if (json2 === "true") return { value: true };
|
|
132
|
+
if (json2 === "false") return { value: false };
|
|
133
|
+
return { issues: [{ message: `Expected a boolean, got ${json2}` }] };
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
function boolean(opts = {}) {
|
|
137
|
+
return new BooleanField(opts);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/fields/list.ts
|
|
141
|
+
var ListField = class extends Field {
|
|
142
|
+
constructor(inner, o) {
|
|
143
|
+
super();
|
|
144
|
+
this.inner = inner;
|
|
145
|
+
this.o = o;
|
|
146
|
+
this.shopifyType = `list.${inner.shopifyType}`;
|
|
147
|
+
this.required = o.required ?? false;
|
|
148
|
+
this.name = o.name;
|
|
149
|
+
this.description = o.description;
|
|
150
|
+
}
|
|
151
|
+
inner;
|
|
152
|
+
o;
|
|
153
|
+
shopifyType;
|
|
154
|
+
wireIsJson = true;
|
|
155
|
+
validations() {
|
|
156
|
+
const v2 = [...this.inner.validations()];
|
|
157
|
+
if (this.o.min != null) v2.push({ name: "list.min", value: String(this.o.min) });
|
|
158
|
+
if (this.o.max != null) v2.push({ name: "list.max", value: String(this.o.max) });
|
|
159
|
+
return v2;
|
|
160
|
+
}
|
|
161
|
+
toJson(value) {
|
|
162
|
+
return value.map((el) => this.inner.elementToJson(el));
|
|
163
|
+
}
|
|
164
|
+
fromJson(json2) {
|
|
165
|
+
if (!Array.isArray(json2)) return { issues: [{ message: "Expected an array" }] };
|
|
166
|
+
const out = [];
|
|
167
|
+
const issues = [];
|
|
168
|
+
json2.forEach((el, index) => {
|
|
169
|
+
const r = this.inner.elementFromJson(el);
|
|
170
|
+
if (r.issues) issues.push(...r.issues.map((i) => ({ ...i, path: [index, ...i.path ?? []] })));
|
|
171
|
+
else out.push(r.value);
|
|
172
|
+
});
|
|
173
|
+
return issues.length ? { issues } : { value: out };
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
function list(inner, opts = {}) {
|
|
177
|
+
return new ListField(inner, opts);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/fields/measurement.ts
|
|
181
|
+
var MeasurementField = class extends Field {
|
|
182
|
+
constructor(opts, shopifyType) {
|
|
183
|
+
super();
|
|
184
|
+
this.shopifyType = shopifyType;
|
|
185
|
+
this.required = opts.required ?? false;
|
|
186
|
+
this.name = opts.name;
|
|
187
|
+
this.description = opts.description;
|
|
188
|
+
}
|
|
189
|
+
shopifyType;
|
|
190
|
+
wireIsJson = true;
|
|
191
|
+
validations() {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
toJson(value) {
|
|
195
|
+
return { value: value.value, unit: value.unit };
|
|
196
|
+
}
|
|
197
|
+
fromJson(json2) {
|
|
198
|
+
if (typeof json2 !== "object" || json2 === null) return { issues: [{ message: "Expected a measurement object" }] };
|
|
199
|
+
const o = json2;
|
|
200
|
+
const value = typeof o.value === "string" ? Number(o.value) : o.value;
|
|
201
|
+
if (typeof value !== "number" || Number.isNaN(value) || typeof o.unit !== "string") {
|
|
202
|
+
return { issues: [{ message: "measurement requires numeric value and string unit" }] };
|
|
203
|
+
}
|
|
204
|
+
return { value: { value, unit: o.unit } };
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
function dimension(opts = {}) {
|
|
208
|
+
return new MeasurementField(opts, "dimension");
|
|
209
|
+
}
|
|
210
|
+
function weight(opts = {}) {
|
|
211
|
+
return new MeasurementField(opts, "weight");
|
|
212
|
+
}
|
|
213
|
+
function volume(opts = {}) {
|
|
214
|
+
return new MeasurementField(opts, "volume");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/fields/money.ts
|
|
218
|
+
var MoneyField = class extends Field {
|
|
219
|
+
shopifyType = "money";
|
|
220
|
+
wireIsJson = true;
|
|
221
|
+
constructor(opts) {
|
|
222
|
+
super();
|
|
223
|
+
this.required = opts.required ?? false;
|
|
224
|
+
this.name = opts.name;
|
|
225
|
+
this.description = opts.description;
|
|
226
|
+
}
|
|
227
|
+
validations() {
|
|
228
|
+
return [];
|
|
229
|
+
}
|
|
230
|
+
toJson(value) {
|
|
231
|
+
return { amount: value.amount, currency_code: value.currencyCode };
|
|
232
|
+
}
|
|
233
|
+
fromJson(json2) {
|
|
234
|
+
if (typeof json2 !== "object" || json2 === null) return { issues: [{ message: "Expected a money object" }] };
|
|
235
|
+
const o = json2;
|
|
236
|
+
if (typeof o.amount !== "string" || typeof o.currency_code !== "string") {
|
|
237
|
+
return { issues: [{ message: "money requires string amount and currency_code" }] };
|
|
238
|
+
}
|
|
239
|
+
return { value: { amount: o.amount, currencyCode: o.currency_code } };
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
function money(opts = {}) {
|
|
243
|
+
return new MoneyField(opts);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/fields/number.ts
|
|
247
|
+
var NumericField = class extends Field {
|
|
248
|
+
constructor(opts) {
|
|
249
|
+
super();
|
|
250
|
+
this.opts = opts;
|
|
251
|
+
this.required = opts.required ?? false;
|
|
252
|
+
this.name = opts.name;
|
|
253
|
+
this.description = opts.description;
|
|
254
|
+
}
|
|
255
|
+
opts;
|
|
256
|
+
toJson(value) {
|
|
257
|
+
return value;
|
|
258
|
+
}
|
|
259
|
+
fromJson(json2) {
|
|
260
|
+
const n = typeof json2 === "string" ? Number(json2) : json2;
|
|
261
|
+
if (typeof n !== "number" || Number.isNaN(n)) return { issues: [{ message: `Expected a number, got ${json2}` }] };
|
|
262
|
+
return { value: n };
|
|
263
|
+
}
|
|
264
|
+
rangeIssues(value) {
|
|
265
|
+
const i = [];
|
|
266
|
+
if (this.opts.min != null && value < this.opts.min) i.push({ message: `Must be at least ${this.opts.min}` });
|
|
267
|
+
if (this.opts.max != null && value > this.opts.max) i.push({ message: `Must be at most ${this.opts.max}` });
|
|
268
|
+
return i;
|
|
269
|
+
}
|
|
270
|
+
baseValidations() {
|
|
271
|
+
const v2 = [];
|
|
272
|
+
if (this.opts.min != null) v2.push({ name: "min", value: String(this.opts.min) });
|
|
273
|
+
if (this.opts.max != null) v2.push({ name: "max", value: String(this.opts.max) });
|
|
274
|
+
return v2;
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
var IntegerField = class extends NumericField {
|
|
278
|
+
shopifyType = "number_integer";
|
|
279
|
+
validations() {
|
|
280
|
+
return this.baseValidations();
|
|
281
|
+
}
|
|
282
|
+
check(value) {
|
|
283
|
+
const i = this.rangeIssues(value);
|
|
284
|
+
if (!Number.isInteger(value)) i.unshift({ message: "Must be an integer" });
|
|
285
|
+
return i;
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
var DecimalField = class extends NumericField {
|
|
289
|
+
constructor(decimalOpts) {
|
|
290
|
+
super(decimalOpts);
|
|
291
|
+
this.decimalOpts = decimalOpts;
|
|
292
|
+
}
|
|
293
|
+
decimalOpts;
|
|
294
|
+
shopifyType = "number_decimal";
|
|
295
|
+
validations() {
|
|
296
|
+
const v2 = this.baseValidations();
|
|
297
|
+
if (this.decimalOpts.maxPrecision != null) {
|
|
298
|
+
v2.push({ name: "max_precision", value: String(this.decimalOpts.maxPrecision) });
|
|
299
|
+
}
|
|
300
|
+
return v2;
|
|
301
|
+
}
|
|
302
|
+
check(value) {
|
|
303
|
+
return this.rangeIssues(value);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
function integer(opts = {}) {
|
|
307
|
+
return new IntegerField(opts);
|
|
308
|
+
}
|
|
309
|
+
function decimal(opts = {}) {
|
|
310
|
+
return new DecimalField(opts);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// src/fields/rating.ts
|
|
314
|
+
var RatingField = class extends Field {
|
|
315
|
+
constructor(o) {
|
|
316
|
+
super();
|
|
317
|
+
this.o = o;
|
|
318
|
+
this.required = o.required ?? false;
|
|
319
|
+
this.name = o.name;
|
|
320
|
+
this.description = o.description;
|
|
321
|
+
}
|
|
322
|
+
o;
|
|
323
|
+
shopifyType = "rating";
|
|
324
|
+
wireIsJson = true;
|
|
325
|
+
validations() {
|
|
326
|
+
return [
|
|
327
|
+
{ name: "min", value: String(this.o.min) },
|
|
328
|
+
{ name: "max", value: String(this.o.max) }
|
|
329
|
+
];
|
|
330
|
+
}
|
|
331
|
+
toJson(value) {
|
|
332
|
+
return { value: String(value.value), scale_min: String(this.o.min), scale_max: String(this.o.max) };
|
|
333
|
+
}
|
|
334
|
+
fromJson(json2) {
|
|
335
|
+
if (typeof json2 !== "object" || json2 === null) return { issues: [{ message: "Expected a rating object" }] };
|
|
336
|
+
const o = json2;
|
|
337
|
+
const value = Number(o.value);
|
|
338
|
+
const scaleMin = Number(o.scale_min);
|
|
339
|
+
const scaleMax = Number(o.scale_max);
|
|
340
|
+
if ([value, scaleMin, scaleMax].some(Number.isNaN)) {
|
|
341
|
+
return { issues: [{ message: "rating requires numeric value, scale_min, scale_max" }] };
|
|
342
|
+
}
|
|
343
|
+
return { value: { value, scaleMin, scaleMax } };
|
|
344
|
+
}
|
|
345
|
+
check(value) {
|
|
346
|
+
return value.value < value.scaleMin || value.value > value.scaleMax ? [{ message: `Rating must be between ${value.scaleMin} and ${value.scaleMax}` }] : [];
|
|
347
|
+
}
|
|
348
|
+
get ["~standard"]() {
|
|
349
|
+
return {
|
|
350
|
+
version: 1,
|
|
351
|
+
vendor: "@fmaplabs/meta-manifest",
|
|
352
|
+
validate: (input) => {
|
|
353
|
+
if (typeof input !== "object" || input === null || typeof input.value !== "number") {
|
|
354
|
+
return { issues: [{ message: "Expected a rating input { value: number }" }] };
|
|
355
|
+
}
|
|
356
|
+
const candidate = {
|
|
357
|
+
value: input.value,
|
|
358
|
+
scaleMin: this.o.min,
|
|
359
|
+
scaleMax: this.o.max
|
|
360
|
+
};
|
|
361
|
+
const issues = this.check(candidate);
|
|
362
|
+
return issues.length ? { issues } : { value: candidate };
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
function rating(opts) {
|
|
368
|
+
return new RatingField(opts);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// src/fields/reference.ts
|
|
372
|
+
var GidField = class extends Field {
|
|
373
|
+
constructor(opts) {
|
|
374
|
+
super();
|
|
375
|
+
this.required = opts.required ?? false;
|
|
376
|
+
this.name = opts.name;
|
|
377
|
+
this.description = opts.description;
|
|
378
|
+
}
|
|
379
|
+
toJson(value) {
|
|
380
|
+
return value;
|
|
381
|
+
}
|
|
382
|
+
fromJson(json2) {
|
|
383
|
+
if (typeof json2 !== "string") return { issues: [{ message: `Expected a GID string, got ${typeof json2}` }] };
|
|
384
|
+
return { value: json2 };
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
var SimpleRefField = class extends GidField {
|
|
388
|
+
constructor(opts, shopifyType) {
|
|
389
|
+
super(opts);
|
|
390
|
+
this.shopifyType = shopifyType;
|
|
391
|
+
}
|
|
392
|
+
shopifyType;
|
|
393
|
+
validations() {
|
|
394
|
+
return [];
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
var FileField = class extends GidField {
|
|
398
|
+
constructor(o) {
|
|
399
|
+
super(o);
|
|
400
|
+
this.o = o;
|
|
401
|
+
}
|
|
402
|
+
o;
|
|
403
|
+
shopifyType = "file_reference";
|
|
404
|
+
validations() {
|
|
405
|
+
return this.o.accept != null ? [{ name: "file_type_options", value: JSON.stringify(this.o.accept) }] : [];
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
function resolveType(target) {
|
|
409
|
+
return typeof target === "function" ? target().type : target.type;
|
|
410
|
+
}
|
|
411
|
+
var MetaobjectRefField = class extends GidField {
|
|
412
|
+
constructor(target, opts) {
|
|
413
|
+
super(opts);
|
|
414
|
+
this.target = target;
|
|
415
|
+
}
|
|
416
|
+
target;
|
|
417
|
+
shopifyType = "metaobject_reference";
|
|
418
|
+
validations() {
|
|
419
|
+
return [{ name: "metaobject_definition_type", value: resolveType(this.target) }];
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
function product(opts = {}) {
|
|
423
|
+
return new SimpleRefField(opts, "product_reference");
|
|
424
|
+
}
|
|
425
|
+
function variant(opts = {}) {
|
|
426
|
+
return new SimpleRefField(opts, "variant_reference");
|
|
427
|
+
}
|
|
428
|
+
function collection(opts = {}) {
|
|
429
|
+
return new SimpleRefField(opts, "collection_reference");
|
|
430
|
+
}
|
|
431
|
+
function page(opts = {}) {
|
|
432
|
+
return new SimpleRefField(opts, "page_reference");
|
|
433
|
+
}
|
|
434
|
+
function file(opts = {}) {
|
|
435
|
+
return new FileField(opts);
|
|
436
|
+
}
|
|
437
|
+
function ref(target, opts = {}) {
|
|
438
|
+
return new MetaobjectRefField(target, opts);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// src/fields/scalar.ts
|
|
442
|
+
var StringScalarField = class extends Field {
|
|
443
|
+
constructor(opts) {
|
|
444
|
+
super();
|
|
445
|
+
this.required = opts.required ?? false;
|
|
446
|
+
this.name = opts.name;
|
|
447
|
+
this.description = opts.description;
|
|
448
|
+
}
|
|
449
|
+
toJson(value) {
|
|
450
|
+
return value;
|
|
451
|
+
}
|
|
452
|
+
fromJson(json2) {
|
|
453
|
+
if (typeof json2 !== "string") return { issues: [{ message: `Expected string, got ${typeof json2}` }] };
|
|
454
|
+
return { value: json2 };
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
var DateField = class extends StringScalarField {
|
|
458
|
+
constructor(o, shopifyType) {
|
|
459
|
+
super(o);
|
|
460
|
+
this.o = o;
|
|
461
|
+
this.shopifyType = shopifyType;
|
|
462
|
+
}
|
|
463
|
+
o;
|
|
464
|
+
shopifyType;
|
|
465
|
+
validations() {
|
|
466
|
+
const v2 = [];
|
|
467
|
+
if (this.o.min != null) v2.push({ name: "min", value: this.o.min });
|
|
468
|
+
if (this.o.max != null) v2.push({ name: "max", value: this.o.max });
|
|
469
|
+
return v2;
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
var UrlField = class extends StringScalarField {
|
|
473
|
+
constructor(o) {
|
|
474
|
+
super(o);
|
|
475
|
+
this.o = o;
|
|
476
|
+
}
|
|
477
|
+
o;
|
|
478
|
+
shopifyType = "url";
|
|
479
|
+
validations() {
|
|
480
|
+
return this.o.allowedDomains != null ? [{ name: "allowed_domains", value: JSON.stringify(this.o.allowedDomains) }] : [];
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
var ColorField = class extends StringScalarField {
|
|
484
|
+
shopifyType = "color";
|
|
485
|
+
validations() {
|
|
486
|
+
return [];
|
|
487
|
+
}
|
|
488
|
+
check(value) {
|
|
489
|
+
return /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(value) ? [] : [{ message: "Must be a hex color like #ff0000" }];
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
var JsonField = class extends Field {
|
|
493
|
+
shopifyType = "json";
|
|
494
|
+
wireIsJson = true;
|
|
495
|
+
constructor(opts) {
|
|
496
|
+
super();
|
|
497
|
+
this.required = opts.required ?? false;
|
|
498
|
+
this.name = opts.name;
|
|
499
|
+
this.description = opts.description;
|
|
500
|
+
}
|
|
501
|
+
validations() {
|
|
502
|
+
return [];
|
|
503
|
+
}
|
|
504
|
+
toJson(value) {
|
|
505
|
+
return value;
|
|
506
|
+
}
|
|
507
|
+
fromJson(json2) {
|
|
508
|
+
return { value: json2 };
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
function date(opts = {}) {
|
|
512
|
+
return new DateField(opts, "date");
|
|
513
|
+
}
|
|
514
|
+
function dateTime(opts = {}) {
|
|
515
|
+
return new DateField(opts, "date_time");
|
|
516
|
+
}
|
|
517
|
+
function url(opts = {}) {
|
|
518
|
+
return new UrlField(opts);
|
|
519
|
+
}
|
|
520
|
+
function color(opts = {}) {
|
|
521
|
+
return new ColorField(opts);
|
|
522
|
+
}
|
|
523
|
+
function json(opts = {}) {
|
|
524
|
+
return new JsonField(opts);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// src/fields/text.ts
|
|
528
|
+
var TextField = class extends Field {
|
|
529
|
+
constructor(opts, multiline) {
|
|
530
|
+
super();
|
|
531
|
+
this.opts = opts;
|
|
532
|
+
this.shopifyType = multiline ? "multi_line_text_field" : "single_line_text_field";
|
|
533
|
+
this.required = opts.required ?? false;
|
|
534
|
+
this.name = opts.name;
|
|
535
|
+
this.description = opts.description;
|
|
536
|
+
}
|
|
537
|
+
opts;
|
|
538
|
+
shopifyType;
|
|
539
|
+
validations() {
|
|
540
|
+
const v2 = [];
|
|
541
|
+
if (this.opts.min != null) v2.push({ name: "min", value: String(this.opts.min) });
|
|
542
|
+
if (this.opts.max != null) v2.push({ name: "max", value: String(this.opts.max) });
|
|
543
|
+
if (this.opts.regex != null) v2.push({ name: "regex", value: this.opts.regex });
|
|
544
|
+
if (this.opts.choices != null) v2.push({ name: "choices", value: JSON.stringify(this.opts.choices) });
|
|
545
|
+
return v2;
|
|
546
|
+
}
|
|
547
|
+
toJson(value) {
|
|
548
|
+
return value;
|
|
549
|
+
}
|
|
550
|
+
fromJson(json2) {
|
|
551
|
+
if (typeof json2 !== "string") return { issues: [{ message: `Expected string, got ${typeof json2}` }] };
|
|
552
|
+
return { value: json2 };
|
|
553
|
+
}
|
|
554
|
+
check(value) {
|
|
555
|
+
const i = [];
|
|
556
|
+
const { min, max, regex, choices } = this.opts;
|
|
557
|
+
if (min != null && value.length < min) i.push({ message: `Must be at least ${min} characters` });
|
|
558
|
+
if (max != null && value.length > max) i.push({ message: `Must be at most ${max} characters` });
|
|
559
|
+
if (regex != null && !new RegExp(regex).test(value)) i.push({ message: `Must match ${regex}` });
|
|
560
|
+
if (choices != null && !choices.includes(value)) i.push({ message: `Must be one of: ${choices.join(", ")}` });
|
|
561
|
+
return i;
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
function text(opts = {}) {
|
|
565
|
+
return new TextField(opts, false);
|
|
566
|
+
}
|
|
567
|
+
function multilineText(opts = {}) {
|
|
568
|
+
return new TextField(opts, true);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// src/fields/index.ts
|
|
572
|
+
var m = {
|
|
573
|
+
text,
|
|
574
|
+
multilineText,
|
|
575
|
+
integer,
|
|
576
|
+
decimal,
|
|
577
|
+
boolean,
|
|
578
|
+
date,
|
|
579
|
+
dateTime,
|
|
580
|
+
url,
|
|
581
|
+
color,
|
|
582
|
+
json,
|
|
583
|
+
money,
|
|
584
|
+
dimension,
|
|
585
|
+
weight,
|
|
586
|
+
volume,
|
|
587
|
+
rating,
|
|
588
|
+
product,
|
|
589
|
+
variant,
|
|
590
|
+
collection,
|
|
591
|
+
page,
|
|
592
|
+
file,
|
|
593
|
+
ref,
|
|
594
|
+
list
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
// src/definition-input.ts
|
|
598
|
+
function mapAccess(access) {
|
|
599
|
+
if (!access) return void 0;
|
|
600
|
+
const out = {};
|
|
601
|
+
if (access.admin) out.admin = access.admin.toUpperCase();
|
|
602
|
+
if (access.storefront) out.storefront = access.storefront.toUpperCase();
|
|
603
|
+
return out;
|
|
604
|
+
}
|
|
605
|
+
function mapCapabilities(caps) {
|
|
606
|
+
if (!caps) return void 0;
|
|
607
|
+
const out = {};
|
|
608
|
+
for (const [k, v2] of Object.entries(caps)) if (v2 != null) out[k] = { enabled: v2 };
|
|
609
|
+
return Object.keys(out).length ? out : void 0;
|
|
610
|
+
}
|
|
611
|
+
function toDefinitionInput(schema) {
|
|
612
|
+
const { config } = schema;
|
|
613
|
+
const fieldDefinitions = Object.entries(schema.fields).map(([key, field]) => {
|
|
614
|
+
const def = {
|
|
615
|
+
key,
|
|
616
|
+
name: field.name ?? key,
|
|
617
|
+
required: field.required,
|
|
618
|
+
type: field.shopifyType,
|
|
619
|
+
validations: field.validations()
|
|
620
|
+
};
|
|
621
|
+
if (field.description != null) def.description = field.description;
|
|
622
|
+
return def;
|
|
623
|
+
});
|
|
624
|
+
const out = {
|
|
625
|
+
type: schema.type,
|
|
626
|
+
name: config.name,
|
|
627
|
+
fieldDefinitions
|
|
628
|
+
};
|
|
629
|
+
if (config.description != null) out.description = config.description;
|
|
630
|
+
if (config.displayName != null) out.displayNameKey = config.displayName;
|
|
631
|
+
const access = mapAccess(config.access);
|
|
632
|
+
if (access) out.access = access;
|
|
633
|
+
const capabilities = mapCapabilities(config.capabilities);
|
|
634
|
+
if (capabilities) out.capabilities = capabilities;
|
|
635
|
+
return out;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// src/define.ts
|
|
639
|
+
function toValueMap(input) {
|
|
640
|
+
const map = /* @__PURE__ */ new Map();
|
|
641
|
+
if (Array.isArray(input)) {
|
|
642
|
+
for (const f of input) map.set(f.key, "jsonValue" in f && f.jsonValue !== void 0 ? f.jsonValue : f.value);
|
|
643
|
+
} else {
|
|
644
|
+
for (const [k, v2] of Object.entries(input)) map.set(k, v2);
|
|
645
|
+
}
|
|
646
|
+
return map;
|
|
647
|
+
}
|
|
648
|
+
function defineMetaobject(handle, config) {
|
|
649
|
+
const type = `$app:${handle}`;
|
|
650
|
+
const entries = Object.entries(config.fields);
|
|
651
|
+
function parse(input) {
|
|
652
|
+
const values = toValueMap(input);
|
|
653
|
+
const out = {};
|
|
654
|
+
const issues = [];
|
|
655
|
+
for (const [key, field] of entries) {
|
|
656
|
+
if (!values.has(key) || values.get(key) === void 0 || values.get(key) === null) {
|
|
657
|
+
if (field.required) issues.push({ message: `Missing required field "${key}"`, path: [key] });
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
const r = field.decode(values.get(key));
|
|
661
|
+
if (r.issues) issues.push(...r.issues.map((i) => ({ ...i, path: [key, ...i.path ?? []] })));
|
|
662
|
+
else out[key] = r.value;
|
|
663
|
+
}
|
|
664
|
+
return issues.length ? { issues } : { value: out };
|
|
665
|
+
}
|
|
666
|
+
function encode(value) {
|
|
667
|
+
const result = [];
|
|
668
|
+
for (const [key, field] of entries) {
|
|
669
|
+
const v2 = value[key];
|
|
670
|
+
if (v2 === void 0) continue;
|
|
671
|
+
result.push({ key, value: field.encode(v2) });
|
|
672
|
+
}
|
|
673
|
+
return result;
|
|
674
|
+
}
|
|
675
|
+
const schemaRef = {
|
|
676
|
+
handle,
|
|
677
|
+
type,
|
|
678
|
+
config,
|
|
679
|
+
fields: config.fields,
|
|
680
|
+
parse,
|
|
681
|
+
encode,
|
|
682
|
+
toDefinitionInput: () => toDefinitionInput(schemaRef),
|
|
683
|
+
["~standard"]: {
|
|
684
|
+
version: 1,
|
|
685
|
+
vendor: "@fmaplabs/meta-manifest",
|
|
686
|
+
validate: (input) => parse(input)
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
return schemaRef;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// src/codegen.ts
|
|
693
|
+
var APP_PREFIX = "$app:";
|
|
694
|
+
var SIMPLE = {
|
|
695
|
+
single_line_text_field: "text",
|
|
696
|
+
multi_line_text_field: "multilineText",
|
|
697
|
+
number_integer: "integer",
|
|
698
|
+
number_decimal: "decimal",
|
|
699
|
+
boolean: "boolean",
|
|
700
|
+
date: "date",
|
|
701
|
+
date_time: "dateTime",
|
|
702
|
+
url: "url",
|
|
703
|
+
color: "color",
|
|
704
|
+
json: "json",
|
|
705
|
+
money: "money",
|
|
706
|
+
dimension: "dimension",
|
|
707
|
+
weight: "weight",
|
|
708
|
+
volume: "volume",
|
|
709
|
+
product_reference: "product",
|
|
710
|
+
variant_reference: "variant",
|
|
711
|
+
collection_reference: "collection",
|
|
712
|
+
page_reference: "page",
|
|
713
|
+
file_reference: "file"
|
|
714
|
+
};
|
|
715
|
+
function handleOf(type) {
|
|
716
|
+
return type.startsWith(APP_PREFIX) ? type.slice(APP_PREFIX.length) : type;
|
|
717
|
+
}
|
|
718
|
+
function identOf(type) {
|
|
719
|
+
return handleOf(type).split(/[^a-zA-Z0-9]+/).filter(Boolean).map((p) => p[0].toUpperCase() + p.slice(1)).join("");
|
|
720
|
+
}
|
|
721
|
+
function v(validations, name) {
|
|
722
|
+
return validations.find((x) => x.name === name)?.value;
|
|
723
|
+
}
|
|
724
|
+
function refTarget(field) {
|
|
725
|
+
const single = v(field.validations, "metaobject_definition_type");
|
|
726
|
+
if (single) return single;
|
|
727
|
+
const many = v(field.validations, "metaobject_definition_types");
|
|
728
|
+
if (many) {
|
|
729
|
+
try {
|
|
730
|
+
const arr = JSON.parse(many);
|
|
731
|
+
if (Array.isArray(arr) && arr.length) return String(arr[0]);
|
|
732
|
+
} catch {
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return void 0;
|
|
736
|
+
}
|
|
737
|
+
function optsLiteral(entries) {
|
|
738
|
+
return entries.length ? `{ ${entries.join(", ")} }` : "";
|
|
739
|
+
}
|
|
740
|
+
function scalarEntries(field, warnings, builder) {
|
|
741
|
+
const e = [];
|
|
742
|
+
if (field.required) e.push("required: true");
|
|
743
|
+
const num = (name, opt) => {
|
|
744
|
+
const val = v(field.validations, name);
|
|
745
|
+
if (val !== void 0) e.push(`${opt}: ${Number(val)}`);
|
|
746
|
+
};
|
|
747
|
+
const str = (name, opt) => {
|
|
748
|
+
const val = v(field.validations, name);
|
|
749
|
+
if (val !== void 0) e.push(`${opt}: ${JSON.stringify(val)}`);
|
|
750
|
+
};
|
|
751
|
+
const jsonArr = (name, opt) => {
|
|
752
|
+
const val = v(field.validations, name);
|
|
753
|
+
if (val !== void 0) {
|
|
754
|
+
try {
|
|
755
|
+
e.push(`${opt}: ${JSON.stringify(JSON.parse(val))}`);
|
|
756
|
+
} catch {
|
|
757
|
+
warnings.push(`could not parse "${name}" on field "${field.key}"`);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
if (builder === "date" || builder === "dateTime") {
|
|
762
|
+
str("min", "min");
|
|
763
|
+
str("max", "max");
|
|
764
|
+
} else {
|
|
765
|
+
num("min", "min");
|
|
766
|
+
num("max", "max");
|
|
767
|
+
}
|
|
768
|
+
str("regex", "regex");
|
|
769
|
+
jsonArr("choices", "choices");
|
|
770
|
+
num("max_precision", "maxPrecision");
|
|
771
|
+
jsonArr("allowed_domains", "allowedDomains");
|
|
772
|
+
jsonArr("file_type_options", "accept");
|
|
773
|
+
return e;
|
|
774
|
+
}
|
|
775
|
+
function scalarCall(builder, field, warnings) {
|
|
776
|
+
const lit = optsLiteral(scalarEntries(field, warnings, builder));
|
|
777
|
+
return lit ? `m.${builder}(${lit})` : `m.${builder}()`;
|
|
778
|
+
}
|
|
779
|
+
function fieldCall(field, typeToIdent, warnings) {
|
|
780
|
+
const type = field.type;
|
|
781
|
+
if (type === "rating") {
|
|
782
|
+
const min = v(field.validations, "min");
|
|
783
|
+
const max = v(field.validations, "max");
|
|
784
|
+
const e = [`min: ${Number(min ?? 1)}`, `max: ${Number(max ?? 5)}`];
|
|
785
|
+
if (field.required) e.unshift("required: true");
|
|
786
|
+
if (min === void 0 || max === void 0) warnings.push(`rating field "${field.key}" missing min/max`);
|
|
787
|
+
return `m.rating(${optsLiteral(e)})`;
|
|
788
|
+
}
|
|
789
|
+
if (type === "metaobject_reference") {
|
|
790
|
+
const target = refTarget(field);
|
|
791
|
+
const ident = target ? typeToIdent.get(target) : void 0;
|
|
792
|
+
if (!ident) {
|
|
793
|
+
warnings.push(`unresolved reference on field "${field.key}"`);
|
|
794
|
+
return `m.json() /* TODO: unmapped reference */`;
|
|
795
|
+
}
|
|
796
|
+
return field.required ? `m.ref(() => ${ident}, { required: true })` : `m.ref(() => ${ident})`;
|
|
797
|
+
}
|
|
798
|
+
if (type.startsWith("list.")) {
|
|
799
|
+
const inner = type.slice("list.".length);
|
|
800
|
+
const listEntries = [];
|
|
801
|
+
if (field.required) listEntries.push("required: true");
|
|
802
|
+
const min = v(field.validations, "list.min");
|
|
803
|
+
const max = v(field.validations, "list.max");
|
|
804
|
+
if (min !== void 0) listEntries.push(`min: ${Number(min)}`);
|
|
805
|
+
if (max !== void 0) listEntries.push(`max: ${Number(max)}`);
|
|
806
|
+
const listOpts = optsLiteral(listEntries);
|
|
807
|
+
let innerCall;
|
|
808
|
+
if (inner === "metaobject_reference") {
|
|
809
|
+
const target = refTarget(field);
|
|
810
|
+
const ident = target ? typeToIdent.get(target) : void 0;
|
|
811
|
+
if (!ident) {
|
|
812
|
+
warnings.push(`unresolved list reference on field "${field.key}"`);
|
|
813
|
+
return `m.json() /* TODO: unmapped list reference */`;
|
|
814
|
+
}
|
|
815
|
+
innerCall = `m.ref(() => ${ident})`;
|
|
816
|
+
} else if (SIMPLE[inner]) {
|
|
817
|
+
innerCall = scalarCall(SIMPLE[inner], { ...field, required: false }, warnings);
|
|
818
|
+
} else {
|
|
819
|
+
warnings.push(`unmapped list element type "${inner}" on field "${field.key}"`);
|
|
820
|
+
return `m.json() /* TODO: unmapped list element ${inner} */`;
|
|
821
|
+
}
|
|
822
|
+
return listOpts ? `m.list(${innerCall}, ${listOpts})` : `m.list(${innerCall})`;
|
|
823
|
+
}
|
|
824
|
+
if (SIMPLE[type]) return scalarCall(SIMPLE[type], field, warnings);
|
|
825
|
+
warnings.push(`unmapped field type "${type}" on field "${field.key}"`);
|
|
826
|
+
return `m.json() /* TODO: unmapped type ${type} */`;
|
|
827
|
+
}
|
|
828
|
+
function defSource(def, typeToIdent, warnings) {
|
|
829
|
+
const ident = typeToIdent.get(def.type);
|
|
830
|
+
const handle = handleOf(def.type);
|
|
831
|
+
const fields = def.fields.map((f) => ` ${f.key}: ${fieldCall(f, typeToIdent, warnings)},`).join("\n");
|
|
832
|
+
const name = def.name ? `
|
|
833
|
+
name: ${JSON.stringify(def.name)},` : "";
|
|
834
|
+
return `export const ${ident} = defineMetaobject(${JSON.stringify(handle)}, {${name}
|
|
835
|
+
fields: {
|
|
836
|
+
${fields}
|
|
837
|
+
},
|
|
838
|
+
});`;
|
|
839
|
+
}
|
|
840
|
+
function referencedTypes(def) {
|
|
841
|
+
const out = /* @__PURE__ */ new Set();
|
|
842
|
+
for (const f of def.fields) {
|
|
843
|
+
if (f.type === "metaobject_reference" || f.type === "list.metaobject_reference") {
|
|
844
|
+
const t = refTarget(f);
|
|
845
|
+
if (t) out.add(t);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
return out;
|
|
849
|
+
}
|
|
850
|
+
function orderDefs(defs) {
|
|
851
|
+
const byType = new Map(defs.map((d) => [d.type, d]));
|
|
852
|
+
const deps = new Map(defs.map((d) => [d.type, referencedTypes(d)]));
|
|
853
|
+
const ordered = [];
|
|
854
|
+
const placed = /* @__PURE__ */ new Set();
|
|
855
|
+
let progress = true;
|
|
856
|
+
while (ordered.length < defs.length && progress) {
|
|
857
|
+
progress = false;
|
|
858
|
+
for (const d of defs) {
|
|
859
|
+
if (placed.has(d.type)) continue;
|
|
860
|
+
const unmet = [...deps.get(d.type) ?? []].filter((t) => byType.has(t) && !placed.has(t) && t !== d.type);
|
|
861
|
+
if (unmet.length === 0) {
|
|
862
|
+
ordered.push(d);
|
|
863
|
+
placed.add(d.type);
|
|
864
|
+
progress = true;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
for (const d of defs) if (!placed.has(d.type)) ordered.push(d);
|
|
869
|
+
return ordered;
|
|
870
|
+
}
|
|
871
|
+
function generateSchemaSource(defs) {
|
|
872
|
+
const ordered = orderDefs(defs);
|
|
873
|
+
const typeToIdent = new Map(ordered.map((d) => [d.type, identOf(d.type)]));
|
|
874
|
+
const warnings = [];
|
|
875
|
+
const blocks = ordered.map((d) => defSource(d, typeToIdent, warnings));
|
|
876
|
+
const idents = ordered.map((d) => typeToIdent.get(d.type));
|
|
877
|
+
const header = `import { defineMetaobject, m } from "@fmaplabs/meta-manifest";`;
|
|
878
|
+
const body = blocks.join("\n\n");
|
|
879
|
+
const footer = `export const schemas = [${idents.join(", ")}];`;
|
|
880
|
+
for (const w of warnings) console.warn(`[meta-manifest] codegen: ${w}`);
|
|
881
|
+
return `${header}
|
|
882
|
+
|
|
883
|
+
${body}
|
|
884
|
+
|
|
885
|
+
${footer}
|
|
886
|
+
`;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// src/sync/diff.ts
|
|
890
|
+
function sameValidations(a, b) {
|
|
891
|
+
const norm = (v2) => JSON.stringify([...v2].sort((x, y) => x.name.localeCompare(y.name)));
|
|
892
|
+
return norm(a) === norm(b);
|
|
893
|
+
}
|
|
894
|
+
function diff(local, remote) {
|
|
895
|
+
const ops = [];
|
|
896
|
+
const remoteByType = new Map(remote.map((d) => [d.type, d]));
|
|
897
|
+
for (const localDef of local) {
|
|
898
|
+
const remoteDef = remoteByType.get(localDef.type);
|
|
899
|
+
if (!remoteDef) {
|
|
900
|
+
ops.push({ kind: "createDefinition", type: localDef.type, definition: localDef });
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
903
|
+
const remoteFields = new Map(remoteDef.fields.map((f) => [f.key, f]));
|
|
904
|
+
const localKeys = new Set(localDef.fields.map((f) => f.key));
|
|
905
|
+
for (const lf of localDef.fields) {
|
|
906
|
+
const rf = remoteFields.get(lf.key);
|
|
907
|
+
if (!rf) {
|
|
908
|
+
ops.push({ kind: "addField", type: localDef.type, field: lf });
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
if (rf.type !== lf.type) {
|
|
912
|
+
ops.push({ kind: "changeFieldType", type: localDef.type, key: lf.key, from: rf.type, to: lf.type, destructive: true });
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
const changes = {};
|
|
916
|
+
if (rf.required !== lf.required) changes.required = lf.required;
|
|
917
|
+
if (!sameValidations(rf.validations, lf.validations)) changes.validations = lf.validations;
|
|
918
|
+
if (Object.keys(changes).length) {
|
|
919
|
+
ops.push({ kind: "updateField", type: localDef.type, key: lf.key, changes });
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
for (const rf of remoteDef.fields) {
|
|
923
|
+
if (!localKeys.has(rf.key)) {
|
|
924
|
+
ops.push({ kind: "removeField", type: localDef.type, key: rf.key, destructive: true });
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
return ops;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// src/sync/normalize.ts
|
|
932
|
+
function normalizeLocal(schema) {
|
|
933
|
+
const def = schema.toDefinitionInput();
|
|
934
|
+
return {
|
|
935
|
+
type: def.type,
|
|
936
|
+
name: def.name,
|
|
937
|
+
fields: def.fieldDefinitions.map((f) => ({
|
|
938
|
+
key: f.key,
|
|
939
|
+
type: f.type,
|
|
940
|
+
required: f.required,
|
|
941
|
+
validations: f.validations
|
|
942
|
+
}))
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
function normalizeRemote(def) {
|
|
946
|
+
return {
|
|
947
|
+
type: def.type,
|
|
948
|
+
name: def.name,
|
|
949
|
+
fields: def.fieldDefinitions.map((f) => ({
|
|
950
|
+
key: f.key,
|
|
951
|
+
type: typeof f.type === "string" ? f.type : f.type.name,
|
|
952
|
+
required: f.required,
|
|
953
|
+
validations: f.validations ?? []
|
|
954
|
+
}))
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// src/sync/client.ts
|
|
959
|
+
var PULL_DEFINITION_QUERY = `query PullMetaobjectDefinition($type: String!) {
|
|
960
|
+
metaobjectDefinitionByType(type: $type) {
|
|
961
|
+
id
|
|
962
|
+
name
|
|
963
|
+
type
|
|
964
|
+
description
|
|
965
|
+
displayNameKey
|
|
966
|
+
fieldDefinitions {
|
|
967
|
+
key
|
|
968
|
+
name
|
|
969
|
+
description
|
|
970
|
+
required
|
|
971
|
+
type { name }
|
|
972
|
+
validations { name value }
|
|
973
|
+
}
|
|
974
|
+
access { admin storefront }
|
|
975
|
+
capabilities {
|
|
976
|
+
publishable { enabled }
|
|
977
|
+
translatable { enabled }
|
|
978
|
+
renderable { enabled }
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
}`;
|
|
982
|
+
var LIST_DEFINITIONS_QUERY = `query ListMetaobjectDefinitions($after: String) {
|
|
983
|
+
metaobjectDefinitions(first: 50, after: $after) {
|
|
984
|
+
nodes {
|
|
985
|
+
id
|
|
986
|
+
name
|
|
987
|
+
type
|
|
988
|
+
fieldDefinitions {
|
|
989
|
+
key
|
|
990
|
+
name
|
|
991
|
+
description
|
|
992
|
+
required
|
|
993
|
+
type { name }
|
|
994
|
+
validations { name value }
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
pageInfo { hasNextPage endCursor }
|
|
998
|
+
}
|
|
999
|
+
}`;
|
|
1000
|
+
var CREATE_DEFINITION_MUTATION = `mutation CreateMetaobjectDefinition($definition: MetaobjectDefinitionCreateInput!) {
|
|
1001
|
+
metaobjectDefinitionCreate(definition: $definition) {
|
|
1002
|
+
metaobjectDefinition { id type }
|
|
1003
|
+
userErrors { field message code }
|
|
1004
|
+
}
|
|
1005
|
+
}`;
|
|
1006
|
+
var UPDATE_DEFINITION_MUTATION = `mutation UpdateMetaobjectDefinition($id: ID!, $definition: MetaobjectDefinitionUpdateInput!) {
|
|
1007
|
+
metaobjectDefinitionUpdate(id: $id, definition: $definition) {
|
|
1008
|
+
metaobjectDefinition { id type }
|
|
1009
|
+
userErrors { field message code }
|
|
1010
|
+
}
|
|
1011
|
+
}`;
|
|
1012
|
+
var SyncTransportError = class extends Error {
|
|
1013
|
+
constructor(message, errors) {
|
|
1014
|
+
super(message);
|
|
1015
|
+
this.errors = errors;
|
|
1016
|
+
this.name = "SyncTransportError";
|
|
1017
|
+
}
|
|
1018
|
+
errors;
|
|
1019
|
+
};
|
|
1020
|
+
async function execute(client, query, variables) {
|
|
1021
|
+
const result = await client(query, variables ? { variables } : void 0);
|
|
1022
|
+
if (Array.isArray(result.errors) ? result.errors.length > 0 : result.errors != null) {
|
|
1023
|
+
throw new SyncTransportError("GraphQL request failed", result.errors);
|
|
1024
|
+
}
|
|
1025
|
+
return result.data;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// src/sync/pull.ts
|
|
1029
|
+
async function pull(client, types) {
|
|
1030
|
+
const out = [];
|
|
1031
|
+
for (const type of types) {
|
|
1032
|
+
const data = await execute(client, PULL_DEFINITION_QUERY, { type });
|
|
1033
|
+
const node = data.metaobjectDefinitionByType;
|
|
1034
|
+
if (!node) continue;
|
|
1035
|
+
out.push({
|
|
1036
|
+
id: node.id,
|
|
1037
|
+
type,
|
|
1038
|
+
definition: { type, name: node.name, fieldDefinitions: node.fieldDefinitions }
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
return out;
|
|
1042
|
+
}
|
|
1043
|
+
function toCanonicalType(resolved) {
|
|
1044
|
+
const m2 = /^app--\d+--(.+)$/.exec(resolved);
|
|
1045
|
+
return m2 ? `$app:${m2[1]}` : null;
|
|
1046
|
+
}
|
|
1047
|
+
async function pullAll(client, opts = {}) {
|
|
1048
|
+
const appOwnedOnly = opts.appOwnedOnly ?? true;
|
|
1049
|
+
const out = [];
|
|
1050
|
+
let after = null;
|
|
1051
|
+
do {
|
|
1052
|
+
const data = await execute(client, LIST_DEFINITIONS_QUERY, { after });
|
|
1053
|
+
for (const node of data.metaobjectDefinitions.nodes) {
|
|
1054
|
+
const canonical = toCanonicalType(node.type);
|
|
1055
|
+
if (appOwnedOnly && !canonical) continue;
|
|
1056
|
+
const type = canonical ?? node.type;
|
|
1057
|
+
out.push({ id: node.id, type, definition: { type, name: node.name, fieldDefinitions: node.fieldDefinitions } });
|
|
1058
|
+
}
|
|
1059
|
+
after = data.metaobjectDefinitions.pageInfo.hasNextPage ? data.metaobjectDefinitions.pageInfo.endCursor : null;
|
|
1060
|
+
} while (after !== null);
|
|
1061
|
+
return out;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// src/sync/push.ts
|
|
1065
|
+
function fieldInputFor(defByType, type, key) {
|
|
1066
|
+
return defByType.get(type)?.fieldDefinitions.find((f) => f.key === key);
|
|
1067
|
+
}
|
|
1068
|
+
function referenceEdges(def) {
|
|
1069
|
+
const out = [];
|
|
1070
|
+
for (const field of def.fieldDefinitions) {
|
|
1071
|
+
for (const v2 of field.validations) {
|
|
1072
|
+
if (v2.name === "metaobject_definition_type") {
|
|
1073
|
+
out.push(v2.value);
|
|
1074
|
+
} else if (v2.name === "metaobject_definition_types") {
|
|
1075
|
+
try {
|
|
1076
|
+
const parsed = JSON.parse(v2.value);
|
|
1077
|
+
if (Array.isArray(parsed)) {
|
|
1078
|
+
for (const t of parsed) if (typeof t === "string") out.push(t);
|
|
1079
|
+
}
|
|
1080
|
+
} catch {
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
return out;
|
|
1086
|
+
}
|
|
1087
|
+
function topoSortCreates(types, deps) {
|
|
1088
|
+
const remaining = /* @__PURE__ */ new Map();
|
|
1089
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
1090
|
+
for (const t of types) {
|
|
1091
|
+
const d = deps.get(t) ?? /* @__PURE__ */ new Set();
|
|
1092
|
+
remaining.set(t, d.size);
|
|
1093
|
+
for (const dep of d) {
|
|
1094
|
+
const list2 = dependents.get(dep) ?? [];
|
|
1095
|
+
list2.push(t);
|
|
1096
|
+
dependents.set(dep, list2);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
const queue = [];
|
|
1100
|
+
for (const t of types) if ((remaining.get(t) ?? 0) === 0) queue.push(t);
|
|
1101
|
+
const ordered = [];
|
|
1102
|
+
while (queue.length) {
|
|
1103
|
+
const t = queue.shift();
|
|
1104
|
+
ordered.push(t);
|
|
1105
|
+
for (const dependent of dependents.get(t) ?? []) {
|
|
1106
|
+
const r = (remaining.get(dependent) ?? 0) - 1;
|
|
1107
|
+
remaining.set(dependent, r);
|
|
1108
|
+
if (r === 0) queue.push(dependent);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
const orderedSet = new Set(ordered);
|
|
1112
|
+
return { ordered, unordered: [...types].filter((t) => !orderedSet.has(t)) };
|
|
1113
|
+
}
|
|
1114
|
+
async function push(client, plan, sources, options) {
|
|
1115
|
+
const allowDestructive = options?.allowDestructive ?? false;
|
|
1116
|
+
const defByType = new Map(sources.definitions.map((d) => [d.type, d]));
|
|
1117
|
+
const idByType = new Map(sources.remote.map((r) => [r.type, r.id]));
|
|
1118
|
+
const indexed = plan.map((op, index) => ({ op, index }));
|
|
1119
|
+
const createOps = indexed.filter((x) => x.op.kind === "createDefinition");
|
|
1120
|
+
const otherOps = indexed.filter((x) => x.op.kind !== "createDefinition");
|
|
1121
|
+
const createTypes = new Set(createOps.map((x) => x.op.type));
|
|
1122
|
+
const deps = /* @__PURE__ */ new Map();
|
|
1123
|
+
for (const { op } of createOps) {
|
|
1124
|
+
const def = defByType.get(op.type);
|
|
1125
|
+
const targets = def ? referenceEdges(def) : [];
|
|
1126
|
+
deps.set(op.type, new Set(targets.filter((t) => createTypes.has(t) && t !== op.type)));
|
|
1127
|
+
}
|
|
1128
|
+
const { ordered, unordered } = topoSortCreates(createTypes, deps);
|
|
1129
|
+
const orderedSet = new Set(ordered);
|
|
1130
|
+
const cyclicTypes = new Set(unordered);
|
|
1131
|
+
const createByType = new Map(createOps.map((x) => [x.op.type, x]));
|
|
1132
|
+
const execOrder = [
|
|
1133
|
+
...ordered.map((t) => createByType.get(t)),
|
|
1134
|
+
...createOps.filter((x) => !orderedSet.has(x.op.type)),
|
|
1135
|
+
...otherOps
|
|
1136
|
+
];
|
|
1137
|
+
const failedTypes = /* @__PURE__ */ new Set();
|
|
1138
|
+
async function applyOp(op) {
|
|
1139
|
+
if (op.kind === "createDefinition") {
|
|
1140
|
+
if (cyclicTypes.has(op.type)) {
|
|
1141
|
+
failedTypes.add(op.type);
|
|
1142
|
+
return { op, status: "blocked", reason: "reference cycle \u2014 two-pass create deferred" };
|
|
1143
|
+
}
|
|
1144
|
+
for (const dep of deps.get(op.type) ?? []) {
|
|
1145
|
+
if (failedTypes.has(dep)) {
|
|
1146
|
+
failedTypes.add(op.type);
|
|
1147
|
+
return { op, status: "blocked", reason: `blocked: dependency "${dep}" was not created` };
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
const def = defByType.get(op.type);
|
|
1151
|
+
if (!def) {
|
|
1152
|
+
failedTypes.add(op.type);
|
|
1153
|
+
return { op, status: "blocked", reason: `no definition input for "${op.type}"` };
|
|
1154
|
+
}
|
|
1155
|
+
const data2 = await execute(client, CREATE_DEFINITION_MUTATION, { definition: def });
|
|
1156
|
+
const payload2 = data2.metaobjectDefinitionCreate;
|
|
1157
|
+
if (payload2.userErrors.length) {
|
|
1158
|
+
failedTypes.add(op.type);
|
|
1159
|
+
return { op, status: "failed", userErrors: payload2.userErrors };
|
|
1160
|
+
}
|
|
1161
|
+
const id2 = payload2.metaobjectDefinition?.id;
|
|
1162
|
+
if (id2) idByType.set(op.type, id2);
|
|
1163
|
+
return { op, status: "applied", id: id2 };
|
|
1164
|
+
}
|
|
1165
|
+
const destructive = op.kind === "removeField" || op.kind === "changeFieldType";
|
|
1166
|
+
if (destructive && !allowDestructive) return { op, status: "skipped", reason: "destructive" };
|
|
1167
|
+
if (failedTypes.has(op.type)) return { op, status: "blocked", reason: `blocked: definition "${op.type}" was not created` };
|
|
1168
|
+
const id = idByType.get(op.type);
|
|
1169
|
+
if (id == null) return { op, status: "blocked", reason: `no definition id for "${op.type}"` };
|
|
1170
|
+
const fieldDefinitions = fieldOpsFor(op);
|
|
1171
|
+
if (!fieldDefinitions) return { op, status: "blocked", reason: `no field input for "${op.type}"` };
|
|
1172
|
+
const data = await execute(client, UPDATE_DEFINITION_MUTATION, { id, definition: { fieldDefinitions } });
|
|
1173
|
+
const payload = data.metaobjectDefinitionUpdate;
|
|
1174
|
+
if (payload.userErrors.length) return { op, status: "failed", userErrors: payload.userErrors };
|
|
1175
|
+
return { op, status: "applied", id: payload.metaobjectDefinition?.id ?? id };
|
|
1176
|
+
}
|
|
1177
|
+
function fieldOpsFor(op) {
|
|
1178
|
+
switch (op.kind) {
|
|
1179
|
+
case "addField": {
|
|
1180
|
+
const field = fieldInputFor(defByType, op.type, op.field.key);
|
|
1181
|
+
return field ? [{ create: field }] : void 0;
|
|
1182
|
+
}
|
|
1183
|
+
case "updateField": {
|
|
1184
|
+
const field = fieldInputFor(defByType, op.type, op.key);
|
|
1185
|
+
if (!field) return void 0;
|
|
1186
|
+
const update = {
|
|
1187
|
+
key: field.key,
|
|
1188
|
+
name: field.name,
|
|
1189
|
+
required: field.required,
|
|
1190
|
+
validations: field.validations
|
|
1191
|
+
};
|
|
1192
|
+
if (field.description != null) update.description = field.description;
|
|
1193
|
+
return [{ update }];
|
|
1194
|
+
}
|
|
1195
|
+
case "removeField":
|
|
1196
|
+
return [{ delete: { key: op.key } }];
|
|
1197
|
+
case "changeFieldType": {
|
|
1198
|
+
const field = fieldInputFor(defByType, op.type, op.key);
|
|
1199
|
+
return field ? [{ delete: { key: op.key } }, { create: field }] : void 0;
|
|
1200
|
+
}
|
|
1201
|
+
default:
|
|
1202
|
+
return void 0;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
const results = new Array(plan.length);
|
|
1206
|
+
for (const { op, index } of execOrder) results[index] = await applyOp(op);
|
|
1207
|
+
const counts = { applied: 0, skipped: 0, blocked: 0, failed: 0 };
|
|
1208
|
+
for (const r of results) counts[r.status]++;
|
|
1209
|
+
return { results, counts, ok: counts.failed === 0 && counts.blocked === 0 };
|
|
1210
|
+
}
|
|
1211
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1212
|
+
0 && (module.exports = {
|
|
1213
|
+
DEFAULT_API_VERSION,
|
|
1214
|
+
Field,
|
|
1215
|
+
SyncTransportError,
|
|
1216
|
+
defineConfig,
|
|
1217
|
+
defineMetaobject,
|
|
1218
|
+
diff,
|
|
1219
|
+
generateSchemaSource,
|
|
1220
|
+
m,
|
|
1221
|
+
normalizeLocal,
|
|
1222
|
+
normalizeRemote,
|
|
1223
|
+
pull,
|
|
1224
|
+
pullAll,
|
|
1225
|
+
push,
|
|
1226
|
+
toDefinitionInput,
|
|
1227
|
+
validateConfig
|
|
1228
|
+
});
|
|
1229
|
+
//# sourceMappingURL=index.cjs.map
|