@stonecrop/schema 0.8.5 → 0.8.7
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/README.md +329 -0
- package/dist/cli.cjs +40 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +130 -0
- package/dist/index-COrltkHl.js +400 -0
- package/dist/index-aeXXzPET.cjs +1 -0
- package/dist/index.cjs +1 -26
- package/dist/index.d.ts +226 -169
- package/dist/index.js +29 -6142
- package/package.json +8 -2
package/README.md
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
# @stonecrop/schema
|
|
2
|
+
|
|
3
|
+
Schema definitions and validation for Stonecrop doctypes, fields, and workflows.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`@stonecrop/schema` provides the foundational type system for Stonecrop applications. It defines strongly-typed schemas using [Zod](https://zod.dev/) for:
|
|
8
|
+
|
|
9
|
+
- **Field definitions** (`FieldMeta`) - Unified field configuration for forms and tables
|
|
10
|
+
- **Doctype definitions** (`DoctypeMeta`) - Complete document type schemas
|
|
11
|
+
- **Workflows** (`WorkflowMeta`) - State machines and action definitions
|
|
12
|
+
- **Validation** - Runtime schema validation with detailed error reporting
|
|
13
|
+
- **DDL Conversion** - PostgreSQL DDL to Stonecrop schema transformation
|
|
14
|
+
|
|
15
|
+
This package is schema-only and has no UI dependencies - it can be used in both frontend and backend contexts.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# From the monorepo root
|
|
21
|
+
rush update
|
|
22
|
+
|
|
23
|
+
# Or with pnpm
|
|
24
|
+
pnpm add @stonecrop/schema
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Core Concepts
|
|
28
|
+
|
|
29
|
+
### Field Types
|
|
30
|
+
|
|
31
|
+
Stonecrop uses semantic field types that remain consistent whether rendered in a form or table:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { StonecropFieldType, FieldMeta } from '@stonecrop/schema'
|
|
35
|
+
|
|
36
|
+
// Field types include:
|
|
37
|
+
// Text: Data, Text
|
|
38
|
+
// Numeric: Int, Float, Decimal, Currency, Quantity
|
|
39
|
+
// Boolean: Check
|
|
40
|
+
// Date/Time: Date, Time, Datetime, Duration, DateRange
|
|
41
|
+
// Structured: JSON, Code
|
|
42
|
+
// Relational: Link, Doctype
|
|
43
|
+
// Files: Attach
|
|
44
|
+
// Selection: Select
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Field Metadata
|
|
48
|
+
|
|
49
|
+
`FieldMeta` is the single source of truth for field definitions:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { FieldMeta } from '@stonecrop/schema'
|
|
53
|
+
|
|
54
|
+
const field: FieldMeta = {
|
|
55
|
+
fieldname: 'customer_name',
|
|
56
|
+
fieldtype: 'Data',
|
|
57
|
+
label: 'Customer Name',
|
|
58
|
+
required: true,
|
|
59
|
+
readOnly: false,
|
|
60
|
+
width: '40ch',
|
|
61
|
+
align: 'left',
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Type-specific options
|
|
65
|
+
const linkField: FieldMeta = {
|
|
66
|
+
fieldname: 'customer',
|
|
67
|
+
fieldtype: 'Link',
|
|
68
|
+
label: 'Customer',
|
|
69
|
+
options: 'customer', // Target doctype slug
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const selectField: FieldMeta = {
|
|
73
|
+
fieldname: 'status',
|
|
74
|
+
fieldtype: 'Select',
|
|
75
|
+
label: 'Status',
|
|
76
|
+
options: ['Draft', 'Submitted', 'Cancelled'], // Choices array
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const decimalField: FieldMeta = {
|
|
80
|
+
fieldname: 'price',
|
|
81
|
+
fieldtype: 'Decimal',
|
|
82
|
+
label: 'Price',
|
|
83
|
+
options: { precision: 10, scale: 2 }, // Config object
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Doctype Metadata
|
|
88
|
+
|
|
89
|
+
`DoctypeMeta` defines a complete doctype with fields, workflow, and inheritance:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { DoctypeMeta } from '@stonecrop/schema'
|
|
93
|
+
|
|
94
|
+
const doctype: DoctypeMeta = {
|
|
95
|
+
name: 'Sales Order',
|
|
96
|
+
slug: 'sales-order',
|
|
97
|
+
tableName: 'sales_order',
|
|
98
|
+
fields: [
|
|
99
|
+
{
|
|
100
|
+
fieldname: 'customer',
|
|
101
|
+
fieldtype: 'Link',
|
|
102
|
+
label: 'Customer',
|
|
103
|
+
options: 'customer',
|
|
104
|
+
required: true,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
fieldname: 'items',
|
|
108
|
+
fieldtype: 'Doctype',
|
|
109
|
+
label: 'Order Items',
|
|
110
|
+
options: 'sales-order-item', // Child doctype
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
workflow: {
|
|
114
|
+
states: ['Draft', 'Submitted', 'Cancelled'],
|
|
115
|
+
actions: {
|
|
116
|
+
submit: {
|
|
117
|
+
label: 'Submit',
|
|
118
|
+
handler: 'submitOrder',
|
|
119
|
+
requiredFields: ['customer', 'items'],
|
|
120
|
+
allowedStates: ['Draft'],
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Workflow and Actions
|
|
128
|
+
|
|
129
|
+
Define state machines and actions for doctypes:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { WorkflowMeta, ActionDefinition } from '@stonecrop/schema'
|
|
133
|
+
|
|
134
|
+
const workflow: WorkflowMeta = {
|
|
135
|
+
states: ['Draft', 'Pending Approval', 'Approved', 'Rejected'],
|
|
136
|
+
actions: {
|
|
137
|
+
submit: {
|
|
138
|
+
label: 'Submit for Approval',
|
|
139
|
+
handler: 'handleSubmit',
|
|
140
|
+
requiredFields: ['title', 'description'],
|
|
141
|
+
allowedStates: ['Draft'],
|
|
142
|
+
confirm: true,
|
|
143
|
+
},
|
|
144
|
+
approve: {
|
|
145
|
+
label: 'Approve',
|
|
146
|
+
handler: 'handleApprove',
|
|
147
|
+
allowedStates: ['Pending Approval'],
|
|
148
|
+
args: { notifyUser: true },
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Validation
|
|
155
|
+
|
|
156
|
+
Runtime validation with detailed error reporting:
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
import { validateField, validateDoctype } from '@stonecrop/schema'
|
|
160
|
+
|
|
161
|
+
// Validate a field definition
|
|
162
|
+
const fieldResult = validateField({
|
|
163
|
+
fieldname: 'email',
|
|
164
|
+
fieldtype: 'Data',
|
|
165
|
+
label: 'Email',
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
if (!fieldResult.success) {
|
|
169
|
+
console.error('Validation errors:', fieldResult.errors)
|
|
170
|
+
// errors: [{ path: ['fieldname'], message: 'Required' }]
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Validate a doctype definition
|
|
174
|
+
const doctypeResult = validateDoctype(doctypeData)
|
|
175
|
+
|
|
176
|
+
if (doctypeResult.success) {
|
|
177
|
+
console.log('Doctype is valid!')
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Parse and Validate
|
|
182
|
+
|
|
183
|
+
Use Zod's parse methods for type-safe validation:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import { parseField, parseDoctype } from '@stonecrop/schema'
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const field = parseField(untrustedData)
|
|
190
|
+
// TypeScript knows field is FieldMeta
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.error('Invalid field:', error)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const doctype = parseDoctype(untrustedData)
|
|
197
|
+
// TypeScript knows doctype is DoctypeMeta
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.error('Invalid doctype:', error)
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## DDL Conversion
|
|
204
|
+
|
|
205
|
+
Convert PostgreSQL DDL statements to Stonecrop doctype schemas:
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import { convertSchema, type ConversionOptions } from '@stonecrop/schema'
|
|
209
|
+
|
|
210
|
+
const ddl = `
|
|
211
|
+
CREATE TABLE customers (
|
|
212
|
+
id SERIAL PRIMARY KEY,
|
|
213
|
+
name VARCHAR(255) NOT NULL,
|
|
214
|
+
email VARCHAR(255) UNIQUE,
|
|
215
|
+
created_at TIMESTAMP DEFAULT NOW()
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
CREATE TABLE sales_orders (
|
|
219
|
+
id SERIAL PRIMARY KEY,
|
|
220
|
+
customer_id INTEGER REFERENCES customers(id),
|
|
221
|
+
status VARCHAR(20) DEFAULT 'Draft',
|
|
222
|
+
total_amount DECIMAL(10, 2)
|
|
223
|
+
);
|
|
224
|
+
`
|
|
225
|
+
|
|
226
|
+
const options: ConversionOptions = {
|
|
227
|
+
inheritanceMode: 'flatten', // or 'reference'
|
|
228
|
+
useCamelCase: true, // Convert snake_case to camelCase
|
|
229
|
+
includeUnmappedMeta: false, // Include unmapped metadata
|
|
230
|
+
schema: 'public', // Filter by schema
|
|
231
|
+
exclude: ['migrations'], // Exclude tables
|
|
232
|
+
typeOverrides: {
|
|
233
|
+
status: { fieldtype: 'Select', options: ['Draft', 'Submitted'] },
|
|
234
|
+
},
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const doctypes = convertSchema(ddl, options)
|
|
238
|
+
|
|
239
|
+
doctypes.forEach(doctype => {
|
|
240
|
+
console.log(`Doctype: ${doctype.name}`)
|
|
241
|
+
console.log(`Table: ${doctype.tableName}`)
|
|
242
|
+
console.log(`Fields: ${doctype.fields.length}`)
|
|
243
|
+
})
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Naming Utilities
|
|
247
|
+
|
|
248
|
+
Convert between different naming conventions:
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
import {
|
|
252
|
+
snakeToCamel,
|
|
253
|
+
camelToSnake,
|
|
254
|
+
snakeToLabel,
|
|
255
|
+
camelToLabel,
|
|
256
|
+
toPascalCase,
|
|
257
|
+
toSlug,
|
|
258
|
+
} from '@stonecrop/schema'
|
|
259
|
+
|
|
260
|
+
snakeToCamel('customer_name') // 'customerName'
|
|
261
|
+
camelToSnake('customerName') // 'customer_name'
|
|
262
|
+
snakeToLabel('customer_name') // 'Customer Name'
|
|
263
|
+
camelToLabel('customerName') // 'Customer Name'
|
|
264
|
+
toPascalCase('customer_name') // 'CustomerName'
|
|
265
|
+
toSlug('Customer Name') // 'customer-name'
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## API
|
|
269
|
+
|
|
270
|
+
### Field Type Mapping
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
import { TYPE_MAP, getDefaultComponent } from '@stonecrop/schema'
|
|
274
|
+
|
|
275
|
+
// Get default component for a field type
|
|
276
|
+
const component = getDefaultComponent('Data') // 'ATextInput'
|
|
277
|
+
|
|
278
|
+
// Access full type map
|
|
279
|
+
console.log(TYPE_MAP['Link']) // { component: 'ALink', fieldtype: 'Link' }
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Usage in Stonecrop
|
|
283
|
+
|
|
284
|
+
This package provides the type system used throughout Stonecrop:
|
|
285
|
+
|
|
286
|
+
- **`@stonecrop/stonecrop`** - Registry uses `DoctypeMeta` for schema storage
|
|
287
|
+
- **`@stonecrop/aform`** - Renders fields based on `FieldMeta` definitions
|
|
288
|
+
- **`@stonecrop/atable`** - Uses `FieldMeta` for column configuration
|
|
289
|
+
- **Backend APIs** - Validates and stores doctypes using these schemas
|
|
290
|
+
|
|
291
|
+
## Development
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
# Install dependencies
|
|
295
|
+
rush update
|
|
296
|
+
|
|
297
|
+
# Build
|
|
298
|
+
rushx build
|
|
299
|
+
|
|
300
|
+
# Run tests
|
|
301
|
+
rushx test
|
|
302
|
+
|
|
303
|
+
# Watch mode
|
|
304
|
+
rushx test:watch
|
|
305
|
+
|
|
306
|
+
# Generate API documentation
|
|
307
|
+
rushx docs
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## TypeScript Support
|
|
311
|
+
|
|
312
|
+
This package is written in TypeScript with strict mode enabled and provides full type definitions:
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
import type { FieldMeta, DoctypeMeta } from '@stonecrop/schema'
|
|
316
|
+
|
|
317
|
+
// Types are inferred from Zod schemas
|
|
318
|
+
const field: FieldMeta = {
|
|
319
|
+
fieldname: 'title',
|
|
320
|
+
fieldtype: 'Data',
|
|
321
|
+
// TypeScript will catch typos and missing required fields
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Use Zod's infer utility for derived types
|
|
325
|
+
import { z } from 'zod'
|
|
326
|
+
import { FieldMeta as FieldMetaSchema } from '@stonecrop/schema'
|
|
327
|
+
|
|
328
|
+
type FieldMetaType = z.infer<typeof FieldMetaSchema>
|
|
329
|
+
```
|
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";const r=require("node:fs"),c=require("node:path"),w=require("node:util"),x=require("graphql"),v=require("./index-aeXXzPET.cjs");async function O(e,h){const t=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...h},body:JSON.stringify({query:x.getIntrospectionQuery()})});if(!t.ok)throw new Error(`Failed to fetch introspection: ${t.status} ${t.statusText}`);const a=await t.json();if(a.errors?.length)throw new Error(`GraphQL errors: ${a.errors.map(n=>n.message).join(", ")}`);if(!a.data)throw new Error("No data in introspection response");return a.data}async function $(){const{values:e,positionals:h}=w.parseArgs({allowPositionals:!0,options:{endpoint:{type:"string",short:"e"},introspection:{type:"string",short:"i"},sdl:{type:"string",short:"s"},output:{type:"string",short:"o"},include:{type:"string"},exclude:{type:"string"},overrides:{type:"string"},"custom-scalars":{type:"string"},"include-unmapped":{type:"boolean",default:!1},help:{type:"boolean",short:"h"}}}),t=h[0];(e.help||!t)&&(q(),process.exit(t?0:1)),t!=="generate"&&(console.error(`Unknown command: ${t}`),console.error("Available commands: generate"),process.exit(1)),[e.endpoint,e.introspection,e.sdl].filter(Boolean).length!==1&&(console.error("Exactly one of --endpoint, --introspection, or --sdl must be provided"),process.exit(1)),e.output||(console.error("--output <dir> is required"),process.exit(1));const n=c.resolve(e.output),l={includeUnmappedMeta:e["include-unmapped"]};if(e.include&&(l.include=e.include.split(",").map(o=>o.trim())),e.exclude&&(l.exclude=e.exclude.split(",").map(o=>o.trim())),e.overrides){const o=c.resolve(e.overrides),s=r.readFileSync(o,"utf-8");l.typeOverrides=JSON.parse(s)}if(e["custom-scalars"]){const o=c.resolve(e["custom-scalars"]),s=r.readFileSync(o,"utf-8");l.customScalars=JSON.parse(s)}let p;if(e.endpoint)console.log(`Fetching introspection from ${e.endpoint}...`),p=await O(e.endpoint);else if(e.introspection){const o=c.resolve(e.introspection),s=r.readFileSync(o,"utf-8"),u=JSON.parse(s);p=u.data??u}else{const o=c.resolve(e.sdl);p=r.readFileSync(o,"utf-8")}const m=v.convertGraphQLSchema(p,l);m.length===0&&(console.warn("No entity types found in the schema. Check your include/exclude filters."),process.exit(0)),r.existsSync(n)||r.mkdirSync(n,{recursive:!0});let f=0,d=0;for(const o of m){const s=`${o.slug}.json`,u=c.join(n,s),S=JSON.stringify(o,null," ");r.writeFileSync(u,S+`
|
|
3
|
+
`,"utf-8");const g=v.validateDoctype(o);if(g.success){const i=o.fields.filter(y=>y._unmapped);i.length>0&&(f++,console.warn(` WARN: ${s} has ${i.length} unmapped field(s): ${i.map(y=>y.fieldname).join(", ")}`))}else{d++,console.error(` ERROR: ${s} failed validation:`);for(const i of g.errors)console.error(` ${i.path.join(".")}: ${i.message}`)}}console.log(`
|
|
4
|
+
Generated ${m.length} doctype(s) in ${n}`+(f?` (${f} with warnings)`:"")+(d?` (${d} with errors)`:"")),d>0&&process.exit(1)}function q(){console.log(`
|
|
5
|
+
stonecrop-schema - Convert GraphQL schemas to Stonecrop doctypes
|
|
6
|
+
|
|
7
|
+
USAGE:
|
|
8
|
+
stonecrop-schema generate [options]
|
|
9
|
+
|
|
10
|
+
SOURCE (exactly one required):
|
|
11
|
+
--endpoint, -e <url> Fetch introspection from a live GraphQL endpoint
|
|
12
|
+
--introspection, -i <file> Read from a saved introspection JSON file
|
|
13
|
+
--sdl, -s <file> Read from a GraphQL SDL (.graphql) file
|
|
14
|
+
|
|
15
|
+
OUTPUT:
|
|
16
|
+
--output, -o <dir> Directory to write doctype JSON files (required)
|
|
17
|
+
|
|
18
|
+
OPTIONS:
|
|
19
|
+
--include <types> Comma-separated list of type names to include
|
|
20
|
+
--exclude <types> Comma-separated list of type names to exclude
|
|
21
|
+
--overrides <file> JSON file with per-type field overrides
|
|
22
|
+
--custom-scalars <file> JSON file mapping custom scalar names to field templates
|
|
23
|
+
--include-unmapped Include _graphqlType metadata on unmapped fields
|
|
24
|
+
--help, -h Show this help message
|
|
25
|
+
|
|
26
|
+
EXAMPLES:
|
|
27
|
+
# From a live PostGraphile server
|
|
28
|
+
stonecrop-schema generate -e http://localhost:5000/graphql -o ./schemas
|
|
29
|
+
|
|
30
|
+
# From a saved introspection result
|
|
31
|
+
stonecrop-schema generate -i introspection.json -o ./schemas
|
|
32
|
+
|
|
33
|
+
# From an SDL file with custom scalars
|
|
34
|
+
stonecrop-schema generate -s schema.graphql -o ./schemas \\
|
|
35
|
+
--custom-scalars custom-scalars.json
|
|
36
|
+
|
|
37
|
+
# Only convert specific types
|
|
38
|
+
stonecrop-schema generate -e http://localhost:5000/graphql -o ./schemas \\
|
|
39
|
+
--include "User,Post,Comment"
|
|
40
|
+
`)}$().catch(e=>{console.error("Error:",e.message),process.exit(1)});
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { }
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync as u, existsSync as S, mkdirSync as w, writeFileSync as x } from "node:fs";
|
|
3
|
+
import { resolve as c, join as O } from "node:path";
|
|
4
|
+
import { parseArgs as $ } from "node:util";
|
|
5
|
+
import { getIntrospectionQuery as N } from "graphql";
|
|
6
|
+
import { h as P, v as j } from "./index-COrltkHl.js";
|
|
7
|
+
async function C(e, m) {
|
|
8
|
+
const t = await fetch(e, {
|
|
9
|
+
method: "POST",
|
|
10
|
+
headers: {
|
|
11
|
+
"Content-Type": "application/json",
|
|
12
|
+
...m
|
|
13
|
+
},
|
|
14
|
+
body: JSON.stringify({
|
|
15
|
+
query: N()
|
|
16
|
+
})
|
|
17
|
+
});
|
|
18
|
+
if (!t.ok)
|
|
19
|
+
throw new Error(`Failed to fetch introspection: ${t.status} ${t.statusText}`);
|
|
20
|
+
const i = await t.json();
|
|
21
|
+
if (i.errors?.length)
|
|
22
|
+
throw new Error(`GraphQL errors: ${i.errors.map((r) => r.message).join(", ")}`);
|
|
23
|
+
if (!i.data)
|
|
24
|
+
throw new Error("No data in introspection response");
|
|
25
|
+
return i.data;
|
|
26
|
+
}
|
|
27
|
+
async function E() {
|
|
28
|
+
const { values: e, positionals: m } = $({
|
|
29
|
+
allowPositionals: !0,
|
|
30
|
+
options: {
|
|
31
|
+
endpoint: { type: "string", short: "e" },
|
|
32
|
+
introspection: { type: "string", short: "i" },
|
|
33
|
+
sdl: { type: "string", short: "s" },
|
|
34
|
+
output: { type: "string", short: "o" },
|
|
35
|
+
include: { type: "string" },
|
|
36
|
+
exclude: { type: "string" },
|
|
37
|
+
overrides: { type: "string" },
|
|
38
|
+
"custom-scalars": { type: "string" },
|
|
39
|
+
"include-unmapped": { type: "boolean", default: !1 },
|
|
40
|
+
help: { type: "boolean", short: "h" }
|
|
41
|
+
}
|
|
42
|
+
}), t = m[0];
|
|
43
|
+
(e.help || !t) && (q(), process.exit(t ? 0 : 1)), t !== "generate" && (console.error(`Unknown command: ${t}`), console.error("Available commands: generate"), process.exit(1)), [e.endpoint, e.introspection, e.sdl].filter(Boolean).length !== 1 && (console.error("Exactly one of --endpoint, --introspection, or --sdl must be provided"), process.exit(1)), e.output || (console.error("--output <dir> is required"), process.exit(1));
|
|
44
|
+
const r = c(e.output), a = {
|
|
45
|
+
includeUnmappedMeta: e["include-unmapped"]
|
|
46
|
+
};
|
|
47
|
+
if (e.include && (a.include = e.include.split(",").map((o) => o.trim())), e.exclude && (a.exclude = e.exclude.split(",").map((o) => o.trim())), e.overrides) {
|
|
48
|
+
const o = c(e.overrides), s = u(o, "utf-8");
|
|
49
|
+
a.typeOverrides = JSON.parse(s);
|
|
50
|
+
}
|
|
51
|
+
if (e["custom-scalars"]) {
|
|
52
|
+
const o = c(e["custom-scalars"]), s = u(o, "utf-8");
|
|
53
|
+
a.customScalars = JSON.parse(s);
|
|
54
|
+
}
|
|
55
|
+
let l;
|
|
56
|
+
if (e.endpoint)
|
|
57
|
+
console.log(`Fetching introspection from ${e.endpoint}...`), l = await C(e.endpoint);
|
|
58
|
+
else if (e.introspection) {
|
|
59
|
+
const o = c(e.introspection), s = u(o, "utf-8"), d = JSON.parse(s);
|
|
60
|
+
l = d.data ?? d;
|
|
61
|
+
} else {
|
|
62
|
+
const o = c(e.sdl);
|
|
63
|
+
l = u(o, "utf-8");
|
|
64
|
+
}
|
|
65
|
+
const f = P(l, a);
|
|
66
|
+
f.length === 0 && (console.warn("No entity types found in the schema. Check your include/exclude filters."), process.exit(0)), S(r) || w(r, { recursive: !0 });
|
|
67
|
+
let h = 0, p = 0;
|
|
68
|
+
for (const o of f) {
|
|
69
|
+
const s = `${o.slug}.json`, d = O(r, s), v = JSON.stringify(o, null, " ");
|
|
70
|
+
x(d, v + `
|
|
71
|
+
`, "utf-8");
|
|
72
|
+
const g = j(o);
|
|
73
|
+
if (g.success) {
|
|
74
|
+
const n = o.fields.filter((y) => y._unmapped);
|
|
75
|
+
n.length > 0 && (h++, console.warn(
|
|
76
|
+
` WARN: ${s} has ${n.length} unmapped field(s): ${n.map((y) => y.fieldname).join(", ")}`
|
|
77
|
+
));
|
|
78
|
+
} else {
|
|
79
|
+
p++, console.error(` ERROR: ${s} failed validation:`);
|
|
80
|
+
for (const n of g.errors)
|
|
81
|
+
console.error(` ${n.path.join(".")}: ${n.message}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
console.log(
|
|
85
|
+
`
|
|
86
|
+
Generated ${f.length} doctype(s) in ${r}` + (h ? ` (${h} with warnings)` : "") + (p ? ` (${p} with errors)` : "")
|
|
87
|
+
), p > 0 && process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
function q() {
|
|
90
|
+
console.log(`
|
|
91
|
+
stonecrop-schema - Convert GraphQL schemas to Stonecrop doctypes
|
|
92
|
+
|
|
93
|
+
USAGE:
|
|
94
|
+
stonecrop-schema generate [options]
|
|
95
|
+
|
|
96
|
+
SOURCE (exactly one required):
|
|
97
|
+
--endpoint, -e <url> Fetch introspection from a live GraphQL endpoint
|
|
98
|
+
--introspection, -i <file> Read from a saved introspection JSON file
|
|
99
|
+
--sdl, -s <file> Read from a GraphQL SDL (.graphql) file
|
|
100
|
+
|
|
101
|
+
OUTPUT:
|
|
102
|
+
--output, -o <dir> Directory to write doctype JSON files (required)
|
|
103
|
+
|
|
104
|
+
OPTIONS:
|
|
105
|
+
--include <types> Comma-separated list of type names to include
|
|
106
|
+
--exclude <types> Comma-separated list of type names to exclude
|
|
107
|
+
--overrides <file> JSON file with per-type field overrides
|
|
108
|
+
--custom-scalars <file> JSON file mapping custom scalar names to field templates
|
|
109
|
+
--include-unmapped Include _graphqlType metadata on unmapped fields
|
|
110
|
+
--help, -h Show this help message
|
|
111
|
+
|
|
112
|
+
EXAMPLES:
|
|
113
|
+
# From a live PostGraphile server
|
|
114
|
+
stonecrop-schema generate -e http://localhost:5000/graphql -o ./schemas
|
|
115
|
+
|
|
116
|
+
# From a saved introspection result
|
|
117
|
+
stonecrop-schema generate -i introspection.json -o ./schemas
|
|
118
|
+
|
|
119
|
+
# From an SDL file with custom scalars
|
|
120
|
+
stonecrop-schema generate -s schema.graphql -o ./schemas \\
|
|
121
|
+
--custom-scalars custom-scalars.json
|
|
122
|
+
|
|
123
|
+
# Only convert specific types
|
|
124
|
+
stonecrop-schema generate -e http://localhost:5000/graphql -o ./schemas \\
|
|
125
|
+
--include "User,Post,Comment"
|
|
126
|
+
`);
|
|
127
|
+
}
|
|
128
|
+
E().catch((e) => {
|
|
129
|
+
console.error("Error:", e.message), process.exit(1);
|
|
130
|
+
});
|