@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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 fmap labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # meta-manifest
2
+
3
+ A zero-dependency, zod-style builder for Shopify **metaobject definitions**, plus a CLI
4
+ (`mm` / `meta-manifest`) that keeps a store's definitions in sync with schema declared in code.
5
+ Think [tento](https://github.com/drizzle-team/tento), but scoped to metaobject-definition
6
+ schema/migrations rather than a runtime query client (see [Roadmap](#roadmap-runtime-query-client)
7
+ below).
8
+
9
+ `meta-manifest` is **not** a runtime client for reading/writing metaobject *entries* — it declares
10
+ definitions, validates values against them, and syncs the definitions themselves (create/update
11
+ fields) to a store via `pull` → `diff` → `push`.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm i -D @fmaplabs/meta-manifest
17
+ # or
18
+ pnpm add -D @fmaplabs/meta-manifest
19
+ ```
20
+
21
+ ## Library usage
22
+
23
+ Declare a metaobject with `defineMetaobject` and the `m` field builders. Implements
24
+ [Standard Schema](https://github.com/standard-schema/standard-schema).
25
+
26
+ ```ts
27
+ import { defineMetaobject, m, type Infer } from "@fmaplabs/meta-manifest";
28
+
29
+ export const Author = defineMetaobject("author", {
30
+ name: "Author",
31
+ displayName: "name",
32
+ access: { storefront: "public_read" },
33
+ fields: {
34
+ name: m.text({ required: true, max: 120 }),
35
+ bio: m.multilineText(),
36
+ rating: m.rating({ min: 1, max: 5 }),
37
+ },
38
+ });
39
+
40
+ type AuthorValue = Infer<typeof Author.fields>;
41
+
42
+ Author.type; // "$app:author"
43
+ Author.toDefinitionInput(); // MetaobjectDefinitionCreateInput (for metaobjectDefinitionCreate)
44
+ Author.parse(fields); // Shopify {key, jsonValue}[] -> typed, validated
45
+ Author.encode({ name: "Ursula" }); // typed -> [{ key, value }] for metaobjectUpsert
46
+ ```
47
+
48
+ References between metaobjects are declared with `m.ref(...)` / `m.list(m.ref(...))`:
49
+
50
+ ```ts
51
+ export const Book = defineMetaobject("book", {
52
+ name: "Book",
53
+ fields: {
54
+ title: m.text({ required: true }),
55
+ author: m.ref(Author),
56
+ },
57
+ });
58
+ ```
59
+
60
+ For the full `pull` → `diff` → `push` sync model (how local schema and a live store are
61
+ reconciled, destructive-change gating, dependency ordering, error handling), see
62
+ [`docs/SYNC.md`](./docs/SYNC.md).
63
+
64
+ ## CLI
65
+
66
+ The CLI drives sync against a real store using an Admin API access token. For a
67
+ step-by-step walk-through (install → token → `init` → `pull`/`diff`/`push`, with
68
+ example output and CI usage), see the [CLI quick start & usage guide](./docs/CLI.md).
69
+
70
+ ### Config
71
+
72
+ `meta-manifest.config.ts` (safe to commit — the token comes from the environment):
73
+
74
+ ```ts
75
+ import { defineConfig } from "@fmaplabs/meta-manifest";
76
+
77
+ export default defineConfig({
78
+ shop: "my-store.myshopify.com",
79
+ accessToken: process.env.SHOPIFY_ADMIN_TOKEN!,
80
+ apiVersion: "2026-07", // optional; defaults to DEFAULT_API_VERSION
81
+ schema: "./src/schema.ts", // where `pull` writes, `diff`/`push` read
82
+ });
83
+ ```
84
+
85
+ Set `SHOPIFY_ADMIN_TOKEN` in your environment before running `pull`, `diff`, or `push`. The token
86
+ needs the `read_metaobject_definitions` scope for `pull`/`diff`, and `write_metaobject_definitions`
87
+ (which implies read) for `push`.
88
+
89
+ ### Commands
90
+
91
+ | Command | Behavior | Exit |
92
+ |----------|----------|------|
93
+ | `mm init` | Scaffold `meta-manifest.config.ts` + a starter `src/schema.ts`. No network. | 0 / 1 |
94
+ | `mm pull` | Enumerate the store's app-owned metaobject definitions and **codegen** `schema.ts` (tento-style — writes/overwrites the schema source file). | 0 / 1 |
95
+ | `mm diff` | Load `schema.ts`, compare it against the store, and print the plan. Read-only. | 0 / 1 |
96
+ | `mm push` | Diff, then apply: topologically ordered (referenced types created first) and **destructive-gated** — `removeField`/`changeFieldType` are skipped unless you pass `--allow-destructive`. | 0 / 1 / 2 |
97
+
98
+ ```bash
99
+ npx mm init # scaffold config + schema
100
+ npx mm pull # bootstrap schema.ts from an existing store
101
+ npx mm diff # preview what push would do
102
+ npx mm push # apply non-destructive changes
103
+ npx mm push --allow-destructive # also apply field removals/type changes
104
+ npx mm pull --force # overwrite an existing schema.ts without the warning
105
+ npx mm diff --config ./staging.config.ts # use a non-default config file
106
+ ```
107
+
108
+ ### Flags
109
+
110
+ - `--config <path>` — use a non-default config file instead of `meta-manifest.config.ts`.
111
+ - `--allow-destructive` — apply destructive changes (`removeField`/`changeFieldType`) on push.
112
+ - `--force` — overwrite the schema file on `pull` without the "overwriting" warning.
113
+
114
+ `mm push` exits `2` if any operation failed **or was blocked** (e.g. a reference cycle among the
115
+ definitions being created in that push — so CI can detect a partial failure), `1` on a
116
+ config/transport error, and `0` otherwise — including when destructive ops were skipped.
117
+
118
+ ## Roadmap: runtime query client
119
+
120
+ v1 covers metaobject **definitions** (schema sync) only. A runtime client for reading/writing
121
+ metaobject **entries** — the tento-style query API — is not implemented yet and is tracked as a
122
+ follow-up, along with codegen of `access`/`capabilities` config.
@@ -0,0 +1,97 @@
1
+ // src/config.ts
2
+ var DEFAULT_API_VERSION = "2026-07";
3
+ function defineConfig(config) {
4
+ return config;
5
+ }
6
+ function validateConfig(raw) {
7
+ const c = raw;
8
+ for (const key of ["shop", "accessToken", "schema"]) {
9
+ if (!c || typeof c[key] !== "string" || c[key] === "") {
10
+ throw new Error(`Invalid config: missing or empty "${key}".`);
11
+ }
12
+ }
13
+ return c;
14
+ }
15
+
16
+ // src/sync/client.ts
17
+ var PULL_DEFINITION_QUERY = `query PullMetaobjectDefinition($type: String!) {
18
+ metaobjectDefinitionByType(type: $type) {
19
+ id
20
+ name
21
+ type
22
+ description
23
+ displayNameKey
24
+ fieldDefinitions {
25
+ key
26
+ name
27
+ description
28
+ required
29
+ type { name }
30
+ validations { name value }
31
+ }
32
+ access { admin storefront }
33
+ capabilities {
34
+ publishable { enabled }
35
+ translatable { enabled }
36
+ renderable { enabled }
37
+ }
38
+ }
39
+ }`;
40
+ var LIST_DEFINITIONS_QUERY = `query ListMetaobjectDefinitions($after: String) {
41
+ metaobjectDefinitions(first: 50, after: $after) {
42
+ nodes {
43
+ id
44
+ name
45
+ type
46
+ fieldDefinitions {
47
+ key
48
+ name
49
+ description
50
+ required
51
+ type { name }
52
+ validations { name value }
53
+ }
54
+ }
55
+ pageInfo { hasNextPage endCursor }
56
+ }
57
+ }`;
58
+ var CREATE_DEFINITION_MUTATION = `mutation CreateMetaobjectDefinition($definition: MetaobjectDefinitionCreateInput!) {
59
+ metaobjectDefinitionCreate(definition: $definition) {
60
+ metaobjectDefinition { id type }
61
+ userErrors { field message code }
62
+ }
63
+ }`;
64
+ var UPDATE_DEFINITION_MUTATION = `mutation UpdateMetaobjectDefinition($id: ID!, $definition: MetaobjectDefinitionUpdateInput!) {
65
+ metaobjectDefinitionUpdate(id: $id, definition: $definition) {
66
+ metaobjectDefinition { id type }
67
+ userErrors { field message code }
68
+ }
69
+ }`;
70
+ var SyncTransportError = class extends Error {
71
+ constructor(message, errors) {
72
+ super(message);
73
+ this.errors = errors;
74
+ this.name = "SyncTransportError";
75
+ }
76
+ errors;
77
+ };
78
+ async function execute(client, query, variables) {
79
+ const result = await client(query, variables ? { variables } : void 0);
80
+ if (Array.isArray(result.errors) ? result.errors.length > 0 : result.errors != null) {
81
+ throw new SyncTransportError("GraphQL request failed", result.errors);
82
+ }
83
+ return result.data;
84
+ }
85
+
86
+ export {
87
+ DEFAULT_API_VERSION,
88
+ defineConfig,
89
+ validateConfig,
90
+ PULL_DEFINITION_QUERY,
91
+ LIST_DEFINITIONS_QUERY,
92
+ CREATE_DEFINITION_MUTATION,
93
+ UPDATE_DEFINITION_MUTATION,
94
+ SyncTransportError,
95
+ execute
96
+ };
97
+ //# sourceMappingURL=chunk-3R6VQ3Z3.js.map
@@ -0,0 +1 @@
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":[]}