@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.
package/dist/index.d.ts CHANGED
@@ -11,6 +11,10 @@ interface Config {
11
11
  apiVersion?: string;
12
12
  /** Path to the schema module whose `schemas` export drives diff/push, and pull writes. */
13
13
  schema: string;
14
+ /** Scope for all metaobjects, unless overridden per-metaobject. Defaults to "app". */
15
+ scope?: "app" | "merchant";
16
+ /** Default admin access for app-scoped metaobjects: false → merchant_read, true → merchant_read_write. Defaults to false. */
17
+ merchantEditable?: boolean;
14
18
  }
15
19
  /** Identity helper for type inference in `meta-manifest.config.ts`. */
16
20
  declare function defineConfig(config: Config): Config;
@@ -65,6 +69,14 @@ type DecodeResult<T> = {
65
69
  value?: undefined;
66
70
  issues: Issue[];
67
71
  };
72
+ /** Options every field builder accepts, regardless of type. */
73
+ interface CommonFieldOptions {
74
+ name?: string;
75
+ description?: string;
76
+ required?: boolean;
77
+ /** Expose this field as a filter in the admin (→ `capabilities.adminFilterable`). */
78
+ filterable?: boolean;
79
+ }
68
80
  declare abstract class Field<TOut, TIn = TOut, Req extends boolean = false> {
69
81
  abstract readonly shopifyType: string;
70
82
  /** True when Shopify stores this type as a JSON string (objects, lists). */
@@ -72,6 +84,9 @@ declare abstract class Field<TOut, TIn = TOut, Req extends boolean = false> {
72
84
  required: boolean;
73
85
  name?: string;
74
86
  description?: string;
87
+ filterable: boolean;
88
+ /** Apply the shared field options; call from each concrete constructor. */
89
+ protected applyCommon(opts: CommonFieldOptions): void;
75
90
  readonly _out: TOut;
76
91
  readonly _in: TIn;
77
92
  readonly _req: Req;
@@ -96,9 +111,7 @@ declare abstract class Field<TOut, TIn = TOut, Req extends boolean = false> {
96
111
  get ["~standard"](): StandardSchemaV1.Props<TIn, TOut>;
97
112
  }
98
113
 
99
- interface BooleanOptions<R extends boolean = false> {
100
- name?: string;
101
- description?: string;
114
+ interface BooleanOptions<R extends boolean = false> extends CommonFieldOptions {
102
115
  required?: R;
103
116
  }
104
117
  declare class BooleanField<R extends boolean> extends Field<boolean, boolean, R> {
@@ -112,9 +125,7 @@ declare function boolean<R extends boolean = false>(opts?: BooleanOptions<R>): B
112
125
 
113
126
  type InnerOut<E> = E extends Field<infer O, any, any> ? O : never;
114
127
  type InnerIn<E> = E extends Field<any, infer I, any> ? I : never;
115
- interface ListOptions<R extends boolean = false> {
116
- name?: string;
117
- description?: string;
128
+ interface ListOptions<R extends boolean = false> extends CommonFieldOptions {
118
129
  required?: R;
119
130
  min?: number;
120
131
  max?: number;
@@ -135,9 +146,7 @@ interface Measure {
135
146
  value: number;
136
147
  unit: string;
137
148
  }
138
- interface MeasureOptions<R extends boolean = false> {
139
- name?: string;
140
- description?: string;
149
+ interface MeasureOptions<R extends boolean = false> extends CommonFieldOptions {
141
150
  required?: R;
142
151
  }
143
152
  declare class MeasurementField<R extends boolean> extends Field<Measure, Measure, R> {
@@ -156,9 +165,7 @@ interface Money {
156
165
  amount: string;
157
166
  currencyCode: string;
158
167
  }
159
- interface MoneyOptions<R extends boolean = false> {
160
- name?: string;
161
- description?: string;
168
+ interface MoneyOptions<R extends boolean = false> extends CommonFieldOptions {
162
169
  required?: R;
163
170
  }
164
171
  declare class MoneyField<R extends boolean> extends Field<Money, Money, R> {
@@ -171,9 +178,7 @@ declare class MoneyField<R extends boolean> extends Field<Money, Money, R> {
171
178
  }
172
179
  declare function money<R extends boolean = false>(opts?: MoneyOptions<R>): MoneyField<R>;
173
180
 
174
- interface NumberOptions<R extends boolean = false> {
175
- name?: string;
176
- description?: string;
181
+ interface NumberOptions<R extends boolean = false> extends CommonFieldOptions {
177
182
  required?: R;
178
183
  min?: number;
179
184
  max?: number;
@@ -212,9 +217,7 @@ interface Rating {
212
217
  interface RatingInput {
213
218
  value: number;
214
219
  }
215
- interface RatingOptions<R extends boolean = false> {
216
- name?: string;
217
- description?: string;
220
+ interface RatingOptions<R extends boolean = false> extends CommonFieldOptions {
218
221
  required?: R;
219
222
  min: number;
220
223
  max: number;
@@ -232,9 +235,7 @@ declare class RatingField<R extends boolean> extends Field<Rating, RatingInput,
232
235
  }
233
236
  declare function rating<R extends boolean = false>(opts: RatingOptions<R>): RatingField<R>;
234
237
 
235
- interface RefOptions<R extends boolean = false> {
236
- name?: string;
237
- description?: string;
238
+ interface RefOptions<R extends boolean = false> extends CommonFieldOptions {
238
239
  required?: R;
239
240
  }
240
241
  declare abstract class GidField<R extends boolean> extends Field<string, string, R> {
@@ -268,16 +269,21 @@ declare class MetaobjectRefField<R extends boolean> extends GidField<R> {
268
269
  constructor(target: TypeRef, opts: RefOptions<R>);
269
270
  validations(): FieldValidation[];
270
271
  }
272
+ declare class MixedRefField<R extends boolean> extends GidField<R> {
273
+ private readonly targets;
274
+ readonly shopifyType = "mixed_reference";
275
+ constructor(targets: readonly TypeRef[], opts: RefOptions<R>);
276
+ validations(): FieldValidation[];
277
+ }
271
278
  declare function product<R extends boolean = false>(opts?: RefOptions<R>): SimpleRefField<R>;
272
279
  declare function variant<R extends boolean = false>(opts?: RefOptions<R>): SimpleRefField<R>;
273
280
  declare function collection<R extends boolean = false>(opts?: RefOptions<R>): SimpleRefField<R>;
274
281
  declare function page<R extends boolean = false>(opts?: RefOptions<R>): SimpleRefField<R>;
275
282
  declare function file<R extends boolean = false>(opts?: FileOptions<R>): FileField<R>;
276
283
  declare function ref<R extends boolean = false>(target: TypeRef, opts?: RefOptions<R>): MetaobjectRefField<R>;
284
+ declare function mixedRef<R extends boolean = false>(targets: readonly TypeRef[], opts?: RefOptions<R>): MixedRefField<R>;
277
285
 
278
- interface BaseOptions<R extends boolean = false> {
279
- name?: string;
280
- description?: string;
286
+ interface BaseOptions<R extends boolean = false> extends CommonFieldOptions {
281
287
  required?: R;
282
288
  }
283
289
  declare abstract class StringScalarField<R extends boolean> extends Field<string, string, R> {
@@ -323,9 +329,7 @@ declare function url<R extends boolean = false>(opts?: UrlOptions<R>): UrlField<
323
329
  declare function color<R extends boolean = false>(opts?: BaseOptions<R>): ColorField<R>;
324
330
  declare function json<R extends boolean = false>(opts?: BaseOptions<R>): JsonField<R>;
325
331
 
326
- interface TextOptions<R extends boolean = false> {
327
- name?: string;
328
- description?: string;
332
+ interface TextOptions<R extends boolean = false> extends CommonFieldOptions {
329
333
  required?: R;
330
334
  min?: number;
331
335
  max?: number;
@@ -367,6 +371,7 @@ declare const m: {
367
371
  readonly page: typeof page;
368
372
  readonly file: typeof file;
369
373
  readonly ref: typeof ref;
374
+ readonly mixedRef: typeof mixedRef;
370
375
  readonly list: typeof list;
371
376
  };
372
377
 
@@ -392,6 +397,11 @@ type InferInput<F extends FieldMap> = Simplify<{
392
397
  [K in OptionalKeys<F>]?: FieldIn<F[K]>;
393
398
  }>;
394
399
 
400
+ interface FieldCapabilitiesInput {
401
+ adminFilterable?: {
402
+ enabled: boolean;
403
+ };
404
+ }
395
405
  interface FieldDefinitionInput {
396
406
  key: string;
397
407
  name: string;
@@ -399,19 +409,44 @@ interface FieldDefinitionInput {
399
409
  required: boolean;
400
410
  type: string;
401
411
  validations: FieldValidation[];
412
+ capabilities?: FieldCapabilitiesInput;
413
+ }
414
+ interface AccessInput {
415
+ admin?: string;
416
+ storefront?: string;
417
+ customerAccount?: string;
418
+ }
419
+ interface RenderableData {
420
+ metaTitleKey?: string;
421
+ metaDescriptionKey?: string;
422
+ }
423
+ interface OnlineStoreData {
424
+ urlHandle: string;
425
+ createRedirects?: boolean;
426
+ }
427
+ interface CapabilitiesInput {
428
+ publishable?: {
429
+ enabled: boolean;
430
+ };
431
+ translatable?: {
432
+ enabled: boolean;
433
+ };
434
+ renderable?: {
435
+ enabled: boolean;
436
+ data?: RenderableData;
437
+ };
438
+ onlineStore?: {
439
+ enabled: boolean;
440
+ data?: OnlineStoreData;
441
+ };
402
442
  }
403
443
  interface MetaobjectDefinitionInput {
404
444
  type: string;
405
445
  name: string;
406
446
  description?: string;
407
447
  displayNameKey?: string;
408
- access?: {
409
- admin?: string;
410
- storefront?: string;
411
- };
412
- capabilities?: Record<string, {
413
- enabled: boolean;
414
- }>;
448
+ access?: AccessInput;
449
+ capabilities?: CapabilitiesInput;
415
450
  fieldDefinitions: FieldDefinitionInput[];
416
451
  }
417
452
  declare function toDefinitionInput<F extends FieldMap>(schema: MetaobjectSchema<F>): MetaobjectDefinitionInput;
@@ -419,16 +454,30 @@ declare function toDefinitionInput<F extends FieldMap>(schema: MetaobjectSchema<
419
454
  interface AccessConfig {
420
455
  admin?: "merchant_read" | "merchant_read_write";
421
456
  storefront?: "public_read" | "none";
457
+ customerAccount?: "none" | "read";
458
+ }
459
+ /** SEO metadata: `true` enables with auto keys; an object maps field keys as title/description. */
460
+ type RenderableConfig = boolean | {
461
+ metaTitleKey?: string;
462
+ metaDescriptionKey?: string;
463
+ };
464
+ /** Publish entries as Online Store web pages. `urlHandle` is required by Shopify. */
465
+ interface OnlineStoreConfig {
466
+ urlHandle: string;
467
+ createRedirects?: boolean;
422
468
  }
423
469
  interface CapabilitiesConfig {
424
470
  publishable?: boolean;
425
471
  translatable?: boolean;
426
- renderable?: boolean;
472
+ renderable?: RenderableConfig;
473
+ onlineStore?: OnlineStoreConfig;
427
474
  }
428
475
  interface MetaobjectConfig<F> {
429
476
  name: string;
430
477
  description?: string;
431
478
  displayName?: keyof F & string;
479
+ /** Override the global config scope for this metaobject. */
480
+ scope?: "app" | "merchant";
432
481
  access?: AccessConfig;
433
482
  capabilities?: CapabilitiesConfig;
434
483
  fields: F;
@@ -459,17 +508,54 @@ interface MetaobjectSchema<F extends FieldMap> {
459
508
  }
460
509
  declare function defineMetaobject<F extends Record<string, unknown>>(handle: string, config: MetaobjectConfig<F>): F extends FieldMap ? MetaobjectSchema<F> : never;
461
510
 
511
+ interface RemoteAccess {
512
+ admin?: string;
513
+ storefront?: string;
514
+ customerAccount?: string;
515
+ }
516
+ /** Normalized capability comparison shape. `enabled` is compared for every capability
517
+ * present locally; `data` keys are compared only when locally declared. [design §8] */
518
+ interface RemoteRenderable {
519
+ enabled: boolean;
520
+ data?: {
521
+ metaTitleKey?: string;
522
+ metaDescriptionKey?: string;
523
+ };
524
+ }
525
+ interface RemoteOnlineStore {
526
+ enabled: boolean;
527
+ data?: {
528
+ urlHandle?: string;
529
+ };
530
+ }
531
+ interface RemoteCapabilities {
532
+ publishable?: {
533
+ enabled: boolean;
534
+ };
535
+ translatable?: {
536
+ enabled: boolean;
537
+ };
538
+ renderable?: RemoteRenderable;
539
+ onlineStore?: RemoteOnlineStore;
540
+ }
462
541
  interface RemoteField {
463
542
  key: string;
464
543
  type: string;
465
544
  required: boolean;
545
+ filterable: boolean;
466
546
  validations: FieldValidation[];
467
547
  }
468
548
  interface RemoteDefinition {
469
549
  type: string;
470
550
  name?: string;
551
+ description?: string;
552
+ displayNameKey?: string;
553
+ access?: RemoteAccess;
554
+ capabilities?: RemoteCapabilities;
471
555
  fields: RemoteField[];
472
556
  }
557
+ /** Normalize a (already scope-resolved) definition input to the diff shape. */
558
+ declare function normalizeDefinition(def: MetaobjectDefinitionInput): RemoteDefinition;
473
559
  declare function normalizeLocal<F extends FieldMap>(schema: MetaobjectSchema<F>): RemoteDefinition;
474
560
  interface PulledFieldDefinition {
475
561
  key: string;
@@ -478,10 +564,45 @@ interface PulledFieldDefinition {
478
564
  } | string;
479
565
  required: boolean;
480
566
  validations?: FieldValidation[];
567
+ capabilities?: {
568
+ adminFilterable?: {
569
+ enabled: boolean;
570
+ } | null;
571
+ } | null;
572
+ }
573
+ interface PulledAccess {
574
+ admin?: string | null;
575
+ storefront?: string | null;
576
+ customerAccount?: string | null;
577
+ }
578
+ interface PulledCapabilities {
579
+ publishable?: {
580
+ enabled: boolean;
581
+ } | null;
582
+ translatable?: {
583
+ enabled: boolean;
584
+ } | null;
585
+ renderable?: {
586
+ enabled: boolean;
587
+ data?: {
588
+ metaTitleKey?: string | null;
589
+ metaDescriptionKey?: string | null;
590
+ } | null;
591
+ } | null;
592
+ onlineStore?: {
593
+ enabled: boolean;
594
+ data?: {
595
+ urlHandle?: string | null;
596
+ } | null;
597
+ } | null;
481
598
  }
482
599
  interface PulledDefinition {
483
600
  type: string;
484
601
  name?: string;
602
+ description?: string | null;
603
+ displayNameKey?: string | null;
604
+ access?: PulledAccess | null;
605
+ capabilities?: PulledCapabilities | null;
485
606
  fieldDefinitions: PulledFieldDefinition[];
486
607
  }
487
608
  declare function normalizeRemote(def: PulledDefinition): RemoteDefinition;
@@ -493,10 +614,17 @@ declare function normalizeRemote(def: PulledDefinition): RemoteDefinition;
493
614
  */
494
615
  declare function generateSchemaSource(defs: RemoteDefinition[]): string;
495
616
 
617
+ /** Definition-level metadata that `updateDefinition` reconciles. */
618
+ type DefinitionChange = "name" | "description" | "displayNameKey" | "access" | "capabilities";
496
619
  type DiffOp = {
497
620
  kind: "createDefinition";
498
621
  type: string;
499
622
  definition: RemoteDefinition;
623
+ } | {
624
+ kind: "updateDefinition";
625
+ type: string;
626
+ changes: DefinitionChange[];
627
+ destructive?: true;
500
628
  } | {
501
629
  kind: "addField";
502
630
  type: string;
@@ -521,6 +649,18 @@ type DiffOp = {
521
649
  };
522
650
  declare function diff(local: RemoteDefinition[], remote: RemoteDefinition[]): DiffOp[];
523
651
 
652
+ type AnySchema$1 = MetaobjectSchema<any>;
653
+ /** The subset of config that scope resolution reads. */
654
+ type ScopeConfig = Pick<Config, "scope" | "merchantEditable">;
655
+ type Scope = "app" | "merchant";
656
+ /**
657
+ * Resolve each schema's canonical (`$app:`) definition input into the effective
658
+ * input the sync pipeline pushes: definition `type` and reference targets are
659
+ * rewritten to each metaobject's effective scope, and `access.admin` is resolved.
660
+ * `schema.type` / `m.ref` public values are unchanged. [design §6]
661
+ */
662
+ declare function resolveDefinitions(schemas: AnySchema$1[], config?: ScopeConfig): MetaobjectDefinitionInput[];
663
+
524
664
  /**
525
665
  * A definition read back from the store. `type` is the canonical "$app:…" key
526
666
  * (the REQUESTED type, re-labeled from the resolved `app--…` form Shopify
@@ -589,8 +729,10 @@ type UserError = {
589
729
  * thrown); only transport / top-level GraphQL errors propagate. [design §7, §8]
590
730
  *
591
731
  * Create ops run in dependency order (a referenced type before its referencer);
592
- * types in a reference cycle are `blocked`. Results are returned in plan order;
593
- * client calls happen in execution order. [design §7]
732
+ * types entangled in a reference cycle are created two-pass created with the
733
+ * cycle-breaking ref fields stripped, then updated to add them once every member
734
+ * exists. Results are returned in plan order; client calls happen in execution
735
+ * order. [design §7]
594
736
  */
595
737
  declare function push(client: AdminGraphQLClient, plan: DiffOp[], sources: {
596
738
  definitions: MetaobjectDefinitionInput[];
@@ -599,4 +741,4 @@ declare function push(client: AdminGraphQLClient, plan: DiffOp[], sources: {
599
741
 
600
742
  type AnySchema = MetaobjectSchema<any>;
601
743
 
602
- export { type AccessConfig, AdminGraphQLClient, type AnySchema, type CapabilitiesConfig, type Config, DEFAULT_API_VERSION, type DecodeResult, type DiffOp, Field, type FieldDefinitionInput, type FieldValidation, type FileType, type Infer, type InferInput, type Issue, type Measure, type MetaobjectConfig, type MetaobjectDefinitionInput, type MetaobjectSchema, type Money, type ParseInput, type PulledRemote, type PushOpResult, type PushOptions, type PushResult, type Rating, type RatingInput, type RemoteDefinition, type RemoteField, StandardSchemaV1, type TypeRef, defineConfig, defineMetaobject, diff, generateSchemaSource, m, normalizeLocal, normalizeRemote, pull, pullAll, push, toDefinitionInput, validateConfig };
744
+ export { type AccessConfig, AdminGraphQLClient, type AnySchema, type CapabilitiesConfig, type Config, DEFAULT_API_VERSION, type DecodeResult, type DefinitionChange, type DiffOp, Field, type FieldDefinitionInput, type FieldValidation, type FileType, type Infer, type InferInput, type Issue, type Measure, type MetaobjectConfig, type MetaobjectDefinitionInput, type MetaobjectSchema, type Money, type ParseInput, type PulledDefinition, type PulledRemote, type PushOpResult, type PushOptions, type PushResult, type Rating, type RatingInput, type RemoteAccess, type RemoteCapabilities, type RemoteDefinition, type RemoteField, type Scope, type ScopeConfig, StandardSchemaV1, type TypeRef, defineConfig, defineMetaobject, diff, generateSchemaSource, m, normalizeDefinition, normalizeLocal, normalizeRemote, pull, pullAll, push, resolveDefinitions, toDefinitionInput, validateConfig };
package/dist/index.js CHANGED
@@ -4,19 +4,21 @@ import {
4
4
  diff,
5
5
  generateSchemaSource,
6
6
  m,
7
+ normalizeDefinition,
7
8
  normalizeLocal,
8
9
  normalizeRemote,
9
10
  pull,
10
11
  pullAll,
11
12
  push,
13
+ resolveDefinitions,
12
14
  toDefinitionInput
13
- } from "./chunk-GH5DXHS5.js";
15
+ } from "./chunk-OEJJXMYC.js";
14
16
  import {
15
17
  DEFAULT_API_VERSION,
16
18
  SyncTransportError,
17
19
  defineConfig,
18
20
  validateConfig
19
- } from "./chunk-3R6VQ3Z3.js";
21
+ } from "./chunk-VSJUGUH7.js";
20
22
  export {
21
23
  DEFAULT_API_VERSION,
22
24
  Field,
@@ -26,11 +28,13 @@ export {
26
28
  diff,
27
29
  generateSchemaSource,
28
30
  m,
31
+ normalizeDefinition,
29
32
  normalizeLocal,
30
33
  normalizeRemote,
31
34
  pull,
32
35
  pullAll,
33
36
  push,
37
+ resolveDefinitions,
34
38
  toDefinitionInput,
35
39
  validateConfig
36
40
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/node/client.ts","../../src/sync/client.ts","../../src/config.ts"],"sourcesContent":["import type { AdminGraphQLClient } from \"../sync/client\";\nimport { SyncTransportError } from \"../sync/client\";\nimport { DEFAULT_API_VERSION } from \"../config\";\n\n/**\n * Build an AdminGraphQLClient that talks directly to a store using an Admin API\n * access token — the CLI's standalone equivalent of the app's session-based\n * `admin.graphql` wrapper. [design §2]\n */\nexport function createAdminClient(opts: {\n shop: string;\n accessToken: string;\n apiVersion?: string;\n}): AdminGraphQLClient {\n const version = opts.apiVersion ?? DEFAULT_API_VERSION;\n const endpoint = `https://${opts.shop}/admin/api/${version}/graphql.json`;\n return async (query, options) => {\n let res: Response;\n try {\n res = await fetch(endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", \"X-Shopify-Access-Token\": opts.accessToken },\n body: JSON.stringify({ query, variables: options?.variables }),\n });\n } catch (cause) {\n throw new SyncTransportError(`Request to ${opts.shop} failed`, cause);\n }\n if (!res.ok) {\n throw new SyncTransportError(`Admin API returned HTTP ${res.status}`, await res.text().catch(() => null));\n }\n return res.json() as Promise<{ data?: unknown; errors?: unknown }>;\n };\n}\n","/**\n * The minimal Admin GraphQL transport the sync adapter depends on. The app\n * supplies a concrete implementation at its edge (wrapping `admin.graphql`),\n * keeping `@fmaplabs/meta-manifest` free of any runtime dependency on a GraphQL\n * client. [design §5]\n */\nexport interface AdminGraphQLClient {\n (query: string, options?: { variables?: Record<string, unknown> }): Promise<{ data?: unknown; errors?: unknown }>;\n}\n\n// GraphQL operation strings, copied verbatim from the schema-validated documents\n// in the design spec §3. Re-validated against Admin API 2026-07 during planning.\n// Do not edit by hand — keep equal to the validated documents (drift guard, §12).\n\nexport const PULL_DEFINITION_QUERY = `query PullMetaobjectDefinition($type: String!) {\n metaobjectDefinitionByType(type: $type) {\n id\n name\n type\n description\n displayNameKey\n fieldDefinitions {\n key\n name\n description\n required\n type { name }\n validations { name value }\n }\n access { admin storefront }\n capabilities {\n publishable { enabled }\n translatable { enabled }\n renderable { enabled }\n }\n }\n}`;\n\nexport const LIST_DEFINITIONS_QUERY = `query ListMetaobjectDefinitions($after: String) {\n metaobjectDefinitions(first: 50, after: $after) {\n nodes {\n id\n name\n type\n fieldDefinitions {\n key\n name\n description\n required\n type { name }\n validations { name value }\n }\n }\n pageInfo { hasNextPage endCursor }\n }\n}`;\n\nexport const CREATE_DEFINITION_MUTATION = `mutation CreateMetaobjectDefinition($definition: MetaobjectDefinitionCreateInput!) {\n metaobjectDefinitionCreate(definition: $definition) {\n metaobjectDefinition { id type }\n userErrors { field message code }\n }\n}`;\n\nexport const UPDATE_DEFINITION_MUTATION = `mutation UpdateMetaobjectDefinition($id: ID!, $definition: MetaobjectDefinitionUpdateInput!) {\n metaobjectDefinitionUpdate(id: $id, definition: $definition) {\n metaobjectDefinition { id type }\n userErrors { field message code }\n }\n}`;\n\n/**\n * Thrown when a request fails at the transport or top-level GraphQL layer —\n * distinct from per-op `userErrors`, which `push` reports as `failed` rather\n * than throwing. Carries the offending top-level `errors` payload. [design §5]\n */\nexport class SyncTransportError extends Error {\n constructor(message: string, readonly errors: unknown) {\n super(message);\n this.name = \"SyncTransportError\";\n }\n}\n\n/**\n * Runs an operation through the injected client and returns its `data`.\n * A non-empty top-level `errors` payload becomes a `SyncTransportError`;\n * a rejected transport promise propagates unchanged. [design §5]\n */\nexport async function execute<T>(\n client: AdminGraphQLClient,\n query: string,\n variables?: Record<string, unknown>,\n): Promise<T> {\n const result = await client(query, variables ? { variables } : undefined);\n if (Array.isArray(result.errors) ? result.errors.length > 0 : result.errors != null) {\n throw new SyncTransportError(\"GraphQL request failed\", result.errors);\n }\n return result.data as T;\n}\n","export const DEFAULT_API_VERSION = \"2026-07\";\n\nexport interface Config {\n /** e.g. \"my-store.myshopify.com\" */\n shop: string;\n /** Admin API access token; reference via process.env in your config file. */\n accessToken: string;\n /** Admin API version. Defaults to DEFAULT_API_VERSION. */\n apiVersion?: string;\n /** Path to the schema module whose `schemas` export drives diff/push, and pull writes. */\n schema: string;\n}\n\n/** Identity helper for type inference in `meta-manifest.config.ts`. */\nexport function defineConfig(config: Config): Config {\n return config;\n}\n\n/** Validate a loaded config object, throwing a one-line Error naming the first missing field. */\nexport function validateConfig(raw: unknown): Config {\n const c = raw as Partial<Config> | null | undefined;\n for (const key of [\"shop\", \"accessToken\", \"schema\"] as const) {\n if (!c || typeof c[key] !== \"string\" || c[key] === \"\") {\n throw new Error(`Invalid config: missing or empty \"${key}\".`);\n }\n }\n return c as Config;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC4EO,IAAM,qBAAN,cAAiC,MAAM;AAAA,EAC5C,YAAY,SAA0B,QAAiB;AACrD,UAAM,OAAO;AADuB;AAEpC,SAAK,OAAO;AAAA,EACd;AAAA,EAHsC;AAIxC;;;ACjFO,IAAM,sBAAsB;;;AFS5B,SAAS,kBAAkB,MAIX;AACrB,QAAM,UAAU,KAAK,cAAc;AACnC,QAAM,WAAW,WAAW,KAAK,IAAI,cAAc,OAAO;AAC1D,SAAO,OAAO,OAAO,YAAY;AAC/B,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,MAAM,UAAU;AAAA,QAC1B,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,oBAAoB,0BAA0B,KAAK,YAAY;AAAA,QAC1F,MAAM,KAAK,UAAU,EAAE,OAAO,WAAW,SAAS,UAAU,CAAC;AAAA,MAC/D,CAAC;AAAA,IACH,SAAS,OAAO;AACd,YAAM,IAAI,mBAAmB,cAAc,KAAK,IAAI,WAAW,KAAK;AAAA,IACtE;AACA,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,mBAAmB,2BAA2B,IAAI,MAAM,IAAI,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI,CAAC;AAAA,IAC1G;AACA,WAAO,IAAI,KAAK;AAAA,EAClB;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/node/client.ts","../../src/sync/client.ts","../../src/config.ts"],"sourcesContent":["import type { AdminGraphQLClient } from \"../sync/client\";\nimport { SyncTransportError } from \"../sync/client\";\nimport { DEFAULT_API_VERSION } from \"../config\";\n\n/**\n * Build an AdminGraphQLClient that talks directly to a store using an Admin API\n * access token — the CLI's standalone equivalent of the app's session-based\n * `admin.graphql` wrapper. [design §2]\n */\nexport function createAdminClient(opts: {\n shop: string;\n accessToken: string;\n apiVersion?: string;\n}): AdminGraphQLClient {\n const version = opts.apiVersion ?? DEFAULT_API_VERSION;\n const endpoint = `https://${opts.shop}/admin/api/${version}/graphql.json`;\n return async (query, options) => {\n let res: Response;\n try {\n res = await fetch(endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", \"X-Shopify-Access-Token\": opts.accessToken },\n body: JSON.stringify({ query, variables: options?.variables }),\n });\n } catch (cause) {\n throw new SyncTransportError(`Request to ${opts.shop} failed`, cause);\n }\n if (!res.ok) {\n throw new SyncTransportError(`Admin API returned HTTP ${res.status}`, await res.text().catch(() => null));\n }\n return res.json() as Promise<{ data?: unknown; errors?: unknown }>;\n };\n}\n","/**\n * The minimal Admin GraphQL transport the sync adapter depends on. The app\n * supplies a concrete implementation at its edge (wrapping `admin.graphql`),\n * keeping `@fmaplabs/meta-manifest` free of any runtime dependency on a GraphQL\n * client. [design §5]\n */\nexport interface AdminGraphQLClient {\n (query: string, options?: { variables?: Record<string, unknown> }): Promise<{ data?: unknown; errors?: unknown }>;\n}\n\n// GraphQL operation strings, copied verbatim from the schema-validated documents\n// in the design spec §3. Re-validated against Admin API 2026-07 during planning.\n// Do not edit by hand — keep equal to the validated documents (drift guard, §12).\n\nexport const PULL_DEFINITION_QUERY = `query PullMetaobjectDefinition($type: String!) {\n metaobjectDefinitionByType(type: $type) {\n id\n name\n type\n description\n displayNameKey\n fieldDefinitions {\n key\n name\n description\n required\n type { name }\n validations { name value }\n capabilities { adminFilterable { enabled } }\n }\n access { admin storefront customerAccount }\n capabilities {\n publishable { enabled }\n translatable { enabled }\n renderable { enabled data { metaTitleKey metaDescriptionKey } }\n onlineStore { enabled data { urlHandle } }\n }\n }\n}`;\n\nexport const LIST_DEFINITIONS_QUERY = `query ListMetaobjectDefinitions($after: String) {\n metaobjectDefinitions(first: 50, after: $after) {\n nodes {\n id\n name\n type\n description\n displayNameKey\n fieldDefinitions {\n key\n name\n description\n required\n type { name }\n validations { name value }\n capabilities { adminFilterable { enabled } }\n }\n access { admin storefront customerAccount }\n capabilities {\n publishable { enabled }\n translatable { enabled }\n renderable { enabled data { metaTitleKey metaDescriptionKey } }\n onlineStore { enabled data { urlHandle } }\n }\n }\n pageInfo { hasNextPage endCursor }\n }\n}`;\n\nexport const CREATE_DEFINITION_MUTATION = `mutation CreateMetaobjectDefinition($definition: MetaobjectDefinitionCreateInput!) {\n metaobjectDefinitionCreate(definition: $definition) {\n metaobjectDefinition { id type }\n userErrors { field message code }\n }\n}`;\n\nexport const UPDATE_DEFINITION_MUTATION = `mutation UpdateMetaobjectDefinition($id: ID!, $definition: MetaobjectDefinitionUpdateInput!) {\n metaobjectDefinitionUpdate(id: $id, definition: $definition) {\n metaobjectDefinition { id type }\n userErrors { field message code }\n }\n}`;\n\n/**\n * Thrown when a request fails at the transport or top-level GraphQL layer —\n * distinct from per-op `userErrors`, which `push` reports as `failed` rather\n * than throwing. Carries the offending top-level `errors` payload. [design §5]\n */\nexport class SyncTransportError extends Error {\n constructor(message: string, readonly errors: unknown) {\n super(message);\n this.name = \"SyncTransportError\";\n }\n}\n\n/**\n * Runs an operation through the injected client and returns its `data`.\n * A non-empty top-level `errors` payload becomes a `SyncTransportError`;\n * a rejected transport promise propagates unchanged. [design §5]\n */\nexport async function execute<T>(\n client: AdminGraphQLClient,\n query: string,\n variables?: Record<string, unknown>,\n): Promise<T> {\n const result = await client(query, variables ? { variables } : undefined);\n if (Array.isArray(result.errors) ? result.errors.length > 0 : result.errors != null) {\n throw new SyncTransportError(\"GraphQL request failed\", result.errors);\n }\n return result.data as T;\n}\n","export const DEFAULT_API_VERSION = \"2026-07\";\n\nexport interface Config {\n /** e.g. \"my-store.myshopify.com\" */\n shop: string;\n /** Admin API access token; reference via process.env in your config file. */\n accessToken: string;\n /** Admin API version. Defaults to DEFAULT_API_VERSION. */\n apiVersion?: string;\n /** Path to the schema module whose `schemas` export drives diff/push, and pull writes. */\n schema: string;\n /** Scope for all metaobjects, unless overridden per-metaobject. Defaults to \"app\". */\n scope?: \"app\" | \"merchant\";\n /** Default admin access for app-scoped metaobjects: false → merchant_read, true → merchant_read_write. Defaults to false. */\n merchantEditable?: boolean;\n}\n\n/** Identity helper for type inference in `meta-manifest.config.ts`. */\nexport function defineConfig(config: Config): Config {\n return config;\n}\n\n/** Validate a loaded config object, throwing a one-line Error naming the first missing field. */\nexport function validateConfig(raw: unknown): Config {\n const c = raw as Partial<Config> | null | undefined;\n for (const key of [\"shop\", \"accessToken\", \"schema\"] as const) {\n if (!c || typeof c[key] !== \"string\" || c[key] === \"\") {\n throw new Error(`Invalid config: missing or empty \"${key}\".`);\n }\n }\n return c as Config;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACwFO,IAAM,qBAAN,cAAiC,MAAM;AAAA,EAC5C,YAAY,SAA0B,QAAiB;AACrD,UAAM,OAAO;AADuB;AAEpC,SAAK,OAAO;AAAA,EACd;AAAA,EAHsC;AAIxC;;;AC7FO,IAAM,sBAAsB;;;AFS5B,SAAS,kBAAkB,MAIX;AACrB,QAAM,UAAU,KAAK,cAAc;AACnC,QAAM,WAAW,WAAW,KAAK,IAAI,cAAc,OAAO;AAC1D,SAAO,OAAO,OAAO,YAAY;AAC/B,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,MAAM,UAAU;AAAA,QAC1B,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,oBAAoB,0BAA0B,KAAK,YAAY;AAAA,QAC1F,MAAM,KAAK,UAAU,EAAE,OAAO,WAAW,SAAS,UAAU,CAAC;AAAA,MAC/D,CAAC;AAAA,IACH,SAAS,OAAO;AACd,YAAM,IAAI,mBAAmB,cAAc,KAAK,IAAI,WAAW,KAAK;AAAA,IACtE;AACA,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,mBAAmB,2BAA2B,IAAI,MAAM,IAAI,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI,CAAC;AAAA,IAC1G;AACA,WAAO,IAAI,KAAK;AAAA,EAClB;AACF;","names":[]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createAdminClient
3
- } from "../chunk-PFU5VAO7.js";
4
- import "../chunk-3R6VQ3Z3.js";
3
+ } from "../chunk-AUR2YQCW.js";
4
+ import "../chunk-VSJUGUH7.js";
5
5
  export {
6
6
  createAdminClient
7
7
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fmaplabs/meta-manifest",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Zero-dependency, zod-style builder for Shopify metaobject definitions, plus a CLI (mm) that syncs them to a store via pull → diff → push.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/config.ts","../src/sync/client.ts"],"sourcesContent":["export const DEFAULT_API_VERSION = \"2026-07\";\n\nexport interface Config {\n /** e.g. \"my-store.myshopify.com\" */\n shop: string;\n /** Admin API access token; reference via process.env in your config file. */\n accessToken: string;\n /** Admin API version. Defaults to DEFAULT_API_VERSION. */\n apiVersion?: string;\n /** Path to the schema module whose `schemas` export drives diff/push, and pull writes. */\n schema: string;\n}\n\n/** Identity helper for type inference in `meta-manifest.config.ts`. */\nexport function defineConfig(config: Config): Config {\n return config;\n}\n\n/** Validate a loaded config object, throwing a one-line Error naming the first missing field. */\nexport function validateConfig(raw: unknown): Config {\n const c = raw as Partial<Config> | null | undefined;\n for (const key of [\"shop\", \"accessToken\", \"schema\"] as const) {\n if (!c || typeof c[key] !== \"string\" || c[key] === \"\") {\n throw new Error(`Invalid config: missing or empty \"${key}\".`);\n }\n }\n return c as Config;\n}\n","/**\n * The minimal Admin GraphQL transport the sync adapter depends on. The app\n * supplies a concrete implementation at its edge (wrapping `admin.graphql`),\n * keeping `@fmaplabs/meta-manifest` free of any runtime dependency on a GraphQL\n * client. [design §5]\n */\nexport interface AdminGraphQLClient {\n (query: string, options?: { variables?: Record<string, unknown> }): Promise<{ data?: unknown; errors?: unknown }>;\n}\n\n// GraphQL operation strings, copied verbatim from the schema-validated documents\n// in the design spec §3. Re-validated against Admin API 2026-07 during planning.\n// Do not edit by hand — keep equal to the validated documents (drift guard, §12).\n\nexport const PULL_DEFINITION_QUERY = `query PullMetaobjectDefinition($type: String!) {\n metaobjectDefinitionByType(type: $type) {\n id\n name\n type\n description\n displayNameKey\n fieldDefinitions {\n key\n name\n description\n required\n type { name }\n validations { name value }\n }\n access { admin storefront }\n capabilities {\n publishable { enabled }\n translatable { enabled }\n renderable { enabled }\n }\n }\n}`;\n\nexport const LIST_DEFINITIONS_QUERY = `query ListMetaobjectDefinitions($after: String) {\n metaobjectDefinitions(first: 50, after: $after) {\n nodes {\n id\n name\n type\n fieldDefinitions {\n key\n name\n description\n required\n type { name }\n validations { name value }\n }\n }\n pageInfo { hasNextPage endCursor }\n }\n}`;\n\nexport const CREATE_DEFINITION_MUTATION = `mutation CreateMetaobjectDefinition($definition: MetaobjectDefinitionCreateInput!) {\n metaobjectDefinitionCreate(definition: $definition) {\n metaobjectDefinition { id type }\n userErrors { field message code }\n }\n}`;\n\nexport const UPDATE_DEFINITION_MUTATION = `mutation UpdateMetaobjectDefinition($id: ID!, $definition: MetaobjectDefinitionUpdateInput!) {\n metaobjectDefinitionUpdate(id: $id, definition: $definition) {\n metaobjectDefinition { id type }\n userErrors { field message code }\n }\n}`;\n\n/**\n * Thrown when a request fails at the transport or top-level GraphQL layer —\n * distinct from per-op `userErrors`, which `push` reports as `failed` rather\n * than throwing. Carries the offending top-level `errors` payload. [design §5]\n */\nexport class SyncTransportError extends Error {\n constructor(message: string, readonly errors: unknown) {\n super(message);\n this.name = \"SyncTransportError\";\n }\n}\n\n/**\n * Runs an operation through the injected client and returns its `data`.\n * A non-empty top-level `errors` payload becomes a `SyncTransportError`;\n * a rejected transport promise propagates unchanged. [design §5]\n */\nexport async function execute<T>(\n client: AdminGraphQLClient,\n query: string,\n variables?: Record<string, unknown>,\n): Promise<T> {\n const result = await client(query, variables ? { variables } : undefined);\n if (Array.isArray(result.errors) ? result.errors.length > 0 : result.errors != null) {\n throw new SyncTransportError(\"GraphQL request failed\", result.errors);\n }\n return result.data as T;\n}\n"],"mappings":";AAAO,IAAM,sBAAsB;AAc5B,SAAS,aAAa,QAAwB;AACnD,SAAO;AACT;AAGO,SAAS,eAAe,KAAsB;AACnD,QAAM,IAAI;AACV,aAAW,OAAO,CAAC,QAAQ,eAAe,QAAQ,GAAY;AAC5D,QAAI,CAAC,KAAK,OAAO,EAAE,GAAG,MAAM,YAAY,EAAE,GAAG,MAAM,IAAI;AACrD,YAAM,IAAI,MAAM,qCAAqC,GAAG,IAAI;AAAA,IAC9D;AAAA,EACF;AACA,SAAO;AACT;;;ACbO,IAAM,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwB9B,IAAM,yBAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmB/B,IAAM,6BAA6B;AAAA;AAAA;AAAA;AAAA;AAAA;AAOnC,IAAM,6BAA6B;AAAA;AAAA;AAAA;AAAA;AAAA;AAYnC,IAAM,qBAAN,cAAiC,MAAM;AAAA,EAC5C,YAAY,SAA0B,QAAiB;AACrD,UAAM,OAAO;AADuB;AAEpC,SAAK,OAAO;AAAA,EACd;AAAA,EAHsC;AAIxC;AAOA,eAAsB,QACpB,QACA,OACA,WACY;AACZ,QAAM,SAAS,MAAM,OAAO,OAAO,YAAY,EAAE,UAAU,IAAI,MAAS;AACxE,MAAI,MAAM,QAAQ,OAAO,MAAM,IAAI,OAAO,OAAO,SAAS,IAAI,OAAO,UAAU,MAAM;AACnF,UAAM,IAAI,mBAAmB,0BAA0B,OAAO,MAAM;AAAA,EACtE;AACA,SAAO,OAAO;AAChB;","names":[]}