@stonecrop/schema 0.30.0 → 0.32.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/README.md +10 -10
- package/dist/cli.js +194 -121
- package/dist/cli.js.map +1 -1
- package/dist/converter-CLwduvT_.js +2102 -0
- package/dist/converter-CLwduvT_.js.map +1 -0
- package/dist/flatten-Bx2cfvw3.js +37 -0
- package/dist/flatten-Bx2cfvw3.js.map +1 -0
- package/dist/index.js +123 -117
- package/dist/index.js.map +1 -1
- package/dist/record-BQOOi83C.js +134 -0
- package/dist/record-BQOOi83C.js.map +1 -0
- package/dist/record.js +2 -6
- package/dist/tsdoc-metadata.json +1 -1
- package/package.json +34 -18
- package/dist/flatten-C1MjkzFh.js +0 -10
- package/dist/flatten-C1MjkzFh.js.map +0 -1
- package/dist/record-Bc0lI9Rq.js +0 -61
- package/dist/record-Bc0lI9Rq.js.map +0 -1
- package/dist/record.js.map +0 -1
- package/dist/schema.tsbuildinfo +0 -1
- package/dist/src/badge.d.ts +0 -74
- package/dist/src/badge.d.ts.map +0 -1
- package/dist/src/badge.js +0 -158
- package/dist/src/cli.d.ts +0 -3
- package/dist/src/cli.d.ts.map +0 -1
- package/dist/src/cli.js +0 -292
- package/dist/src/column-schema.d.ts +0 -163
- package/dist/src/column-schema.d.ts.map +0 -1
- package/dist/src/column-schema.js +0 -0
- package/dist/src/component-meta.d.ts +0 -96
- package/dist/src/component-meta.d.ts.map +0 -1
- package/dist/src/component-meta.js +0 -88
- package/dist/src/converter/aggregate.d.ts +0 -127
- package/dist/src/converter/aggregate.d.ts.map +0 -1
- package/dist/src/converter/aggregate.js +0 -235
- package/dist/src/converter/authored.d.ts +0 -43
- package/dist/src/converter/authored.d.ts.map +0 -1
- package/dist/src/converter/authored.js +0 -52
- package/dist/src/converter/heuristics.d.ts +0 -60
- package/dist/src/converter/heuristics.d.ts.map +0 -1
- package/dist/src/converter/heuristics.js +0 -304
- package/dist/src/converter/index.d.ts +0 -51
- package/dist/src/converter/index.d.ts.map +0 -1
- package/dist/src/converter/index.js +0 -195
- package/dist/src/converter/merge.d.ts +0 -102
- package/dist/src/converter/merge.d.ts.map +0 -1
- package/dist/src/converter/merge.js +0 -136
- package/dist/src/converter/scalars.d.ts +0 -46
- package/dist/src/converter/scalars.d.ts.map +0 -1
- package/dist/src/converter/scalars.js +0 -83
- package/dist/src/converter/types.d.ts +0 -157
- package/dist/src/converter/types.d.ts.map +0 -1
- package/dist/src/converter/types.js +0 -5
- package/dist/src/doctype.d.ts +0 -516
- package/dist/src/doctype.d.ts.map +0 -1
- package/dist/src/doctype.js +0 -343
- package/dist/src/field.d.ts +0 -423
- package/dist/src/field.d.ts.map +0 -1
- package/dist/src/field.js +0 -378
- package/dist/src/flatten.d.ts +0 -29
- package/dist/src/flatten.d.ts.map +0 -1
- package/dist/src/flatten.js +0 -38
- package/dist/src/index.d.ts +0 -16
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js +0 -20
- package/dist/src/mode.d.ts +0 -15
- package/dist/src/mode.d.ts.map +0 -1
- package/dist/src/mode.js +0 -0
- package/dist/src/naming.d.ts +0 -80
- package/dist/src/naming.d.ts.map +0 -1
- package/dist/src/naming.js +0 -106
- package/dist/src/record.d.ts +0 -29
- package/dist/src/record.d.ts.map +0 -1
- package/dist/src/record.js +0 -55
- package/dist/src/table.d.ts +0 -33
- package/dist/src/table.d.ts.map +0 -1
- package/dist/src/table.js +0 -25
- package/dist/src/validation.d.ts +0 -54
- package/dist/src/validation.d.ts.map +0 -1
- package/dist/src/validation.js +0 -60
- package/dist/validation-C9P__pRF.js +0 -994
- package/dist/validation-C9P__pRF.js.map +0 -1
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Aggregate doctype derivation.
|
|
3
|
-
*
|
|
4
|
-
* A table gets two generated doctypes: the entity itself, whose `fields` carry every column and
|
|
5
|
-
* which backs the record form, and an **aggregate** — the collection view over the same table.
|
|
6
|
-
* The aggregate starts with identity alone, because the useful default for a collection is the
|
|
7
|
-
* one column that lets a row be opened, not all forty. Widening it is curation, and curation
|
|
8
|
-
* survives regeneration (see `mergeIntrospectedDoctype`).
|
|
9
|
-
*
|
|
10
|
-
* The two are peers: each is a complete doctype with its own `name` and `slug`, and nothing here
|
|
11
|
-
* encodes a relationship between them. Deriving the aggregate's name from the entity's is a
|
|
12
|
-
* generated encoding, not a readable one — no consumer recovers the pair by parsing a slug.
|
|
13
|
-
*
|
|
14
|
-
* @packageDocumentation
|
|
15
|
-
*/
|
|
16
|
-
import pluralize from 'pluralize';
|
|
17
|
-
import { toSlug } from '../naming';
|
|
18
|
-
import { getDoctypeSlug } from '../doctype';
|
|
19
|
-
import { flattenFields, getPrimaryKeyField } from '../field';
|
|
20
|
-
/**
|
|
21
|
-
* The name an entity's aggregate doctype is generated under: the entity's name, pluralised.
|
|
22
|
-
*
|
|
23
|
-
* One definition, because the CLI writes the file under `toSlug` of this and any later caller
|
|
24
|
-
* (a scaffolder, a docs generator) must land on the same name or it silently addresses a
|
|
25
|
-
* different file.
|
|
26
|
-
*
|
|
27
|
-
* `pluralize` rather than appending `s`, because the irregulars are not rare in practice —
|
|
28
|
-
* measured against a consumer's 41 hand-authored aggregate doctypes, this rule reproduces every
|
|
29
|
-
* one of their names, slugs and filenames exactly, while `+ 's'` gets five wrong
|
|
30
|
-
* (`Currencys`, `JournalEntrys`, …).
|
|
31
|
-
*
|
|
32
|
-
* The rule is not total: an already-plural name pluralises to itself. Callers must handle that —
|
|
33
|
-
* see {@link buildAggregateDoctype}.
|
|
34
|
-
*
|
|
35
|
-
* @param doctypeName - the entity doctype's `name`
|
|
36
|
-
* @returns the aggregate doctype's `name`
|
|
37
|
-
* @public
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* ```typescript
|
|
41
|
-
* aggregateDoctypeName('SalesOrder') // 'SalesOrders' -> slug 'sales-orders'
|
|
42
|
-
* ```
|
|
43
|
-
*/
|
|
44
|
-
export function aggregateDoctypeName(doctypeName) {
|
|
45
|
-
return pluralize.plural(doctypeName);
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Derive the aggregate doctype for a converted entity.
|
|
49
|
-
*
|
|
50
|
-
* Returns `undefined` when no identity column can be found — a natural-key table whose key the
|
|
51
|
-
* converter refuses to guess and whose author has not declared one, or a foreign PostGraphile
|
|
52
|
-
* endpoint that has left the Relay identifier occupying `id` (Stonecrop's own preset moves it to
|
|
53
|
-
* `nodeId`). That is deliberate: an aggregate with an empty `fields` array is a valid doctype that
|
|
54
|
-
* renders a table with no columns, which looks like a data problem rather than a generation one.
|
|
55
|
-
* Emitting nothing and saying so is the loud failure.
|
|
56
|
-
*
|
|
57
|
-
* Identity resolves the same way `getRecordIdField` resolves it — the declared `primaryKey`, then
|
|
58
|
-
* the conventional `id` — so an aggregate is always keyed on the column the client will later ask
|
|
59
|
-
* for. `declaredIdentity` overrides both: SDL cannot express which `UNIQUE` column is the key, so
|
|
60
|
-
* for a natural-key table the answer only exists in the authored file, and the caller that read it
|
|
61
|
-
* passes the fieldname back.
|
|
62
|
-
*
|
|
63
|
-
* @param doctype - a converted entity doctype, as returned by `convertGraphQLSchema`
|
|
64
|
-
* @param declaredIdentity - fieldname the authored doctype declares as its `primaryKey`, when the
|
|
65
|
-
* caller has read one. Must name a field the converter emitted; the caller checks that, because
|
|
66
|
-
* only it can say whether a missing one is a dropped column or a typo.
|
|
67
|
-
* @returns the aggregate doctype, or `undefined` when no identity column exists
|
|
68
|
-
* @public
|
|
69
|
-
*
|
|
70
|
-
* @example
|
|
71
|
-
* ```typescript
|
|
72
|
-
* const [order] = convertGraphQLSchema(sdl, { include: ['Order'] })
|
|
73
|
-
* const aggregate = buildAggregateDoctype(order)
|
|
74
|
-
* // { name: 'Orders', slug: 'orders', fields: [ the id field ] }
|
|
75
|
-
* ```
|
|
76
|
-
*/
|
|
77
|
-
export function buildAggregateDoctype(doctype, declaredIdentity) {
|
|
78
|
-
const identity = findIdentityField(doctype.fields, declaredIdentity);
|
|
79
|
-
if (!identity)
|
|
80
|
-
return undefined;
|
|
81
|
-
const name = aggregateDoctypeName(doctype.name);
|
|
82
|
-
// An already-plural name pluralises to itself, which would give the aggregate the entity's own
|
|
83
|
-
// `name` *and* its filename. Both write paths are silent about it: the CLI writes the file twice
|
|
84
|
-
// in one run, and the middleware's registry is a Map keyed by name, so the later read wins in
|
|
85
|
-
// whatever order `readdirSync` returns. Refusing is the only loud option.
|
|
86
|
-
if (name === doctype.name)
|
|
87
|
-
return undefined;
|
|
88
|
-
// `primaryKey` is stamped rather than copied through: a declared identity is not marked on the
|
|
89
|
-
// converter's own field, and an aggregate whose one column carries no marker resolves identity
|
|
90
|
-
// through `getRecordIdField`'s `id` fallback — a column it does not have, so every listed row is
|
|
91
|
-
// silently dropped. Rebuilt with `source` last so the key order matches an entity's identity
|
|
92
|
-
// field and both files stay byte-stable.
|
|
93
|
-
//
|
|
94
|
-
// A copy, not a reference: the two doctypes are written to separate files and an edit to one
|
|
95
|
-
// must not reach the other.
|
|
96
|
-
const { source, ...rest } = identity;
|
|
97
|
-
return {
|
|
98
|
-
name,
|
|
99
|
-
slug: toSlug(name),
|
|
100
|
-
fields: [{ ...rest, primaryKey: true, ...(source === undefined ? {} : { source }) }],
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* The field an aggregate is keyed on: an identity the author declared, else the primary key the
|
|
105
|
-
* converter derived, else the conventional `id`.
|
|
106
|
-
*
|
|
107
|
-
* The author wins because the authored doctype is the source of truth — generation verifies it and
|
|
108
|
-
* never overwrites it (see `mergeIntrospectedDoctype`), and the divergence is already reported as
|
|
109
|
-
* identity drift.
|
|
110
|
-
*
|
|
111
|
-
* Calls `getPrimaryKeyField` for the derived half rather than restating it: a restatement drifted
|
|
112
|
-
* exactly as one does, staying top-level while the helper learned to descend into fieldsets.
|
|
113
|
-
*
|
|
114
|
-
* @internal
|
|
115
|
-
*/
|
|
116
|
-
function findIdentityField(fields, declared) {
|
|
117
|
-
if (declared !== undefined)
|
|
118
|
-
return fields.find(field => field.fieldname === declared);
|
|
119
|
-
return getPrimaryKeyField(fields) ?? fields.find(field => field.fieldname === 'id');
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* The doctypes in a run that get a URL of their own.
|
|
123
|
-
*
|
|
124
|
-
* A child table has no page: its rows exist inside a parent and are edited there, so a route for
|
|
125
|
-
* it is an address nothing can link to. The declaration that says so is the parent's `links` entry
|
|
126
|
-
* with a to-many cardinality — which the server derives from the foreign keys it treats as owning,
|
|
127
|
-
* so this reads what the schema states rather than guessing from a name.
|
|
128
|
-
*
|
|
129
|
-
* The rule is *listed by something, referenced by nothing*. A single reference wins over any number
|
|
130
|
-
* of listings, and the asymmetry is deliberate: a doctype that is both a parent's rows and another
|
|
131
|
-
* doctype's link target — a recipe task, say, embedded in its recipe and pointed at by four other
|
|
132
|
-
* records — needs somewhere for those links to navigate to. Denying it leaves the arrow on an
|
|
133
|
-
* `AFormLink` dead, which fails silently; granting it leaves a URL nobody visits, which does not.
|
|
134
|
-
*
|
|
135
|
-
* Scoped to one run, so a partial generation sees a partial graph and grants more routes than a
|
|
136
|
-
* whole one would. That is the safe direction, and the extra routes are deletable — an authored
|
|
137
|
-
* file's keys survive regeneration untouched.
|
|
138
|
-
*
|
|
139
|
-
* @internal
|
|
140
|
-
*/
|
|
141
|
-
function routableDoctypes(entities) {
|
|
142
|
-
const listed = new Set();
|
|
143
|
-
const referenced = new Set();
|
|
144
|
-
for (const entity of entities) {
|
|
145
|
-
for (const link of Object.values(entity.links ?? {})) {
|
|
146
|
-
if (link.cardinality === 'noneOrMany' || link.cardinality === 'atLeastOne')
|
|
147
|
-
listed.add(link.target);
|
|
148
|
-
else
|
|
149
|
-
referenced.add(link.target);
|
|
150
|
-
}
|
|
151
|
-
// `flattenFields` rather than a top-level scan: a link inside a fieldset is still a reference,
|
|
152
|
-
// and the two ways to answer this question have already drifted apart once.
|
|
153
|
-
for (const field of flattenFields(entity.fields)) {
|
|
154
|
-
if ('doctype' in field && typeof field.doctype === 'string')
|
|
155
|
-
referenced.add(field.doctype);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return new Set(entities
|
|
159
|
-
.filter(entity => {
|
|
160
|
-
const slug = getDoctypeSlug(entity);
|
|
161
|
-
return referenced.has(slug) || !listed.has(slug);
|
|
162
|
-
})
|
|
163
|
-
.map(entity => entity.name));
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Expand converted entities into the set of doctype files to write.
|
|
167
|
-
*
|
|
168
|
-
* Each table yields two: the entity, whose fields carry every column and which backs the record
|
|
169
|
-
* form, and its aggregate — the collection view. They are written as peers, one file each, with
|
|
170
|
-
* no key relating them.
|
|
171
|
-
*
|
|
172
|
-
* Separate from the CLI because the pairing of a file to its verification basis is the part that
|
|
173
|
-
* is easy to get wrong and impossible to notice: getting it wrong does not throw, it just reports
|
|
174
|
-
* drift that is not there, forever.
|
|
175
|
-
*
|
|
176
|
-
* @param entities - `convertGraphQLSchema` output
|
|
177
|
-
* @param options - see {@link GenerationPlanOptions}
|
|
178
|
-
* @returns one entry per file to write
|
|
179
|
-
* @public
|
|
180
|
-
*/
|
|
181
|
-
export function planGeneration(entities, options = {}) {
|
|
182
|
-
const entityNames = new Set(entities.map(entity => entity.name));
|
|
183
|
-
const claimed = new Set();
|
|
184
|
-
const routable = routableDoctypes(entities);
|
|
185
|
-
return entities.flatMap(entity => {
|
|
186
|
-
// Written out in full rather than as a segment the host assembles: the record parameter has
|
|
187
|
-
// to live somewhere, and a host given `/order` cannot know whether this doctype is the
|
|
188
|
-
// collection or the record without asking a second question. The pair shares the entity's
|
|
189
|
-
// slug, so no URL ever carries a plural.
|
|
190
|
-
const segment = `/${getDoctypeSlug(entity)}`;
|
|
191
|
-
const routed = routable.has(entity.name) ? { ...entity, route: `${segment}/:id` } : entity;
|
|
192
|
-
// `basis` is the same object as `generated` for an entity — it is verified against itself.
|
|
193
|
-
const self = { generated: routed, basis: routed, subset: false };
|
|
194
|
-
if (options.noAggregates)
|
|
195
|
-
return [self];
|
|
196
|
-
// Name collisions are checked here rather than in the builder because only this function
|
|
197
|
-
// holds the whole set. Reported before the identity check so each refusal names its own
|
|
198
|
-
// cause — the two are repaired differently.
|
|
199
|
-
const name = aggregateDoctypeName(entity.name);
|
|
200
|
-
if (name === entity.name) {
|
|
201
|
-
options.onWarning?.(`${entity.name} is already plural, so its aggregate would take the same name and the same ` +
|
|
202
|
-
`file. No aggregate was generated. Rename the doctype to its singular form, or author ` +
|
|
203
|
-
`${entity.slug}.json's collection view by hand.`);
|
|
204
|
-
return [self];
|
|
205
|
-
}
|
|
206
|
-
if (entityNames.has(name) || claimed.has(name)) {
|
|
207
|
-
options.onWarning?.(`${entity.name}'s aggregate would be named ${name}, which is already taken by another ` +
|
|
208
|
-
`doctype in this run. No aggregate was generated — one of the two needs an explicit ` +
|
|
209
|
-
`name via the doctypeNames option.`);
|
|
210
|
-
return [self];
|
|
211
|
-
}
|
|
212
|
-
// Checked here rather than in the builder because only the caller knows whether a name that
|
|
213
|
-
// matches nothing is a dropped column or a typo — and an aggregate built around a field the
|
|
214
|
-
// table has no column for renders a collection whose only column is absent from every row.
|
|
215
|
-
const declared = options.identity?.[entity.name];
|
|
216
|
-
if (declared !== undefined && !entity.fields.some(field => field.fieldname === declared)) {
|
|
217
|
-
options.onWarning?.(`${entity.name} declares its primaryKey on '${declared}', which the schema has no column for. ` +
|
|
218
|
-
`No aggregate was generated — correct the declaration in ${entity.slug}.json, or restore the ` +
|
|
219
|
-
`column to the table.`);
|
|
220
|
-
return [self];
|
|
221
|
-
}
|
|
222
|
-
const aggregate = buildAggregateDoctype(entity, declared);
|
|
223
|
-
if (!aggregate) {
|
|
224
|
-
options.onWarning?.(`${entity.name} has no derivable identity column, so no aggregate doctype was generated. ` +
|
|
225
|
-
`Declare a primaryKey on ${entity.slug}.json and re-run.`);
|
|
226
|
-
return [self];
|
|
227
|
-
}
|
|
228
|
-
claimed.add(name);
|
|
229
|
-
// The basis is the entity itself: an aggregate is verified against the table it curates from,
|
|
230
|
-
// not against its own one-field generation. Drift lines take their name from the authored file
|
|
231
|
-
// being checked, so they already name the file the reader has to edit.
|
|
232
|
-
const listed = routable.has(entity.name) ? { ...aggregate, route: segment } : aggregate;
|
|
233
|
-
return [self, { generated: listed, basis: entity, subset: true }];
|
|
234
|
-
});
|
|
235
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Reading an authored doctype — the JSON as it sits on disk, before any parsing.
|
|
3
|
-
*
|
|
4
|
-
* A separate reader from `@stonecrop/schema`'s `flattenFields`/`getPrimaryKeyField` because the two
|
|
5
|
-
* operate on different *shapes*, not different rules: those take parsed `DoctypeField`s and branch
|
|
6
|
-
* on the `kind` discriminant the Zod parser synthesizes, which authored JSON does not carry.
|
|
7
|
-
* `getPrimaryKeyField` on a raw file therefore returns `undefined` — indistinguishable from "no key
|
|
8
|
-
* declared", which is the exact condition its callers are testing.
|
|
9
|
-
*
|
|
10
|
-
* Every question about authored JSON is answered here once, so the rule cannot drift between the
|
|
11
|
-
* merge and the generation plan.
|
|
12
|
-
*
|
|
13
|
-
* @internal
|
|
14
|
-
*/
|
|
15
|
-
/**
|
|
16
|
-
* A doctype as it exists on disk: a plain object that may carry keys this package does not model
|
|
17
|
-
* (`handler` on an action, `filterFunction` on a field, whatever an app has added). Typing it
|
|
18
|
-
* loosely is what lets the merge round-trip those keys untouched instead of dropping them.
|
|
19
|
-
*
|
|
20
|
-
* @public
|
|
21
|
-
*/
|
|
22
|
-
export type AuthoredDoctype = Record<string, unknown>;
|
|
23
|
-
/** @internal */
|
|
24
|
-
export declare function isAuthoredRecord(value: unknown): value is AuthoredDoctype;
|
|
25
|
-
/**
|
|
26
|
-
* Flatten authored fields, descending into fieldsets.
|
|
27
|
-
*
|
|
28
|
-
* A fieldset is a layout grouping, not a scope: a field inside one is still a field of the doctype,
|
|
29
|
-
* with a column of its own and a key it may declare.
|
|
30
|
-
*
|
|
31
|
-
* @internal
|
|
32
|
-
*/
|
|
33
|
-
export declare function flattenAuthored(fields: readonly AuthoredDoctype[]): AuthoredDoctype[];
|
|
34
|
-
/**
|
|
35
|
-
* The fieldname an authored doctype declares as its identity, or `undefined` when it declares none.
|
|
36
|
-
*
|
|
37
|
-
* Descends into fieldsets, because a nested `primaryKey` is a real declaration — ignoring one is
|
|
38
|
-
* what `getPrimaryKeyField` was fixed for.
|
|
39
|
-
*
|
|
40
|
-
* @internal
|
|
41
|
-
*/
|
|
42
|
-
export declare function authoredPrimaryKey(doctype: AuthoredDoctype): string | undefined;
|
|
43
|
-
//# sourceMappingURL=authored.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"authored.d.ts","sourceRoot":"","sources":["../../../src/converter/authored.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAErD,gBAAgB;AAChB,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,eAAe,CAEzE;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,GAAG,eAAe,EAAE,CAUrF;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,GAAG,SAAS,CAI/E"}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Reading an authored doctype — the JSON as it sits on disk, before any parsing.
|
|
3
|
-
*
|
|
4
|
-
* A separate reader from `@stonecrop/schema`'s `flattenFields`/`getPrimaryKeyField` because the two
|
|
5
|
-
* operate on different *shapes*, not different rules: those take parsed `DoctypeField`s and branch
|
|
6
|
-
* on the `kind` discriminant the Zod parser synthesizes, which authored JSON does not carry.
|
|
7
|
-
* `getPrimaryKeyField` on a raw file therefore returns `undefined` — indistinguishable from "no key
|
|
8
|
-
* declared", which is the exact condition its callers are testing.
|
|
9
|
-
*
|
|
10
|
-
* Every question about authored JSON is answered here once, so the rule cannot drift between the
|
|
11
|
-
* merge and the generation plan.
|
|
12
|
-
*
|
|
13
|
-
* @internal
|
|
14
|
-
*/
|
|
15
|
-
/** @internal */
|
|
16
|
-
export function isAuthoredRecord(value) {
|
|
17
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Flatten authored fields, descending into fieldsets.
|
|
21
|
-
*
|
|
22
|
-
* A fieldset is a layout grouping, not a scope: a field inside one is still a field of the doctype,
|
|
23
|
-
* with a column of its own and a key it may declare.
|
|
24
|
-
*
|
|
25
|
-
* @internal
|
|
26
|
-
*/
|
|
27
|
-
export function flattenAuthored(fields) {
|
|
28
|
-
const out = [];
|
|
29
|
-
for (const field of fields) {
|
|
30
|
-
if (Array.isArray(field.schema)) {
|
|
31
|
-
out.push(...flattenAuthored(field.schema.filter(isAuthoredRecord)));
|
|
32
|
-
}
|
|
33
|
-
else {
|
|
34
|
-
out.push(field);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return out;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* The fieldname an authored doctype declares as its identity, or `undefined` when it declares none.
|
|
41
|
-
*
|
|
42
|
-
* Descends into fieldsets, because a nested `primaryKey` is a real declaration — ignoring one is
|
|
43
|
-
* what `getPrimaryKeyField` was fixed for.
|
|
44
|
-
*
|
|
45
|
-
* @internal
|
|
46
|
-
*/
|
|
47
|
-
export function authoredPrimaryKey(doctype) {
|
|
48
|
-
if (!Array.isArray(doctype.fields))
|
|
49
|
-
return undefined;
|
|
50
|
-
const declared = flattenAuthored(doctype.fields.filter(isAuthoredRecord)).find(f => f.primaryKey === true);
|
|
51
|
-
return typeof declared?.fieldname === 'string' ? declared.fieldname : undefined;
|
|
52
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Default heuristics for identifying entity types and fields in a GraphQL schema.
|
|
3
|
-
*
|
|
4
|
-
* These heuristics work across common GraphQL servers (PostGraphile, Hasura, Apollo, etc.)
|
|
5
|
-
* by detecting widely-adopted conventions like the Relay connection pattern.
|
|
6
|
-
*
|
|
7
|
-
* All heuristics can be overridden via the `isEntityType`, `isEntityField`, and
|
|
8
|
-
* `classifyField` options in `GraphQLConversionOptions`.
|
|
9
|
-
*
|
|
10
|
-
* @packageDocumentation
|
|
11
|
-
*/
|
|
12
|
-
import { type GraphQLObjectType, type GraphQLField } from 'graphql';
|
|
13
|
-
import type { GraphQLConversionFieldMeta, GraphQLConversionOptions } from './types';
|
|
14
|
-
/**
|
|
15
|
-
* Default heuristic to determine if a GraphQL object type represents an entity.
|
|
16
|
-
* An entity type becomes a Stonecrop doctype.
|
|
17
|
-
*
|
|
18
|
-
* This heuristic excludes:
|
|
19
|
-
* - Introspection types (`__*`)
|
|
20
|
-
* - Root operation types (`Query`, `Mutation`, `Subscription`)
|
|
21
|
-
* - Types with synthetic suffixes (e.g., `*Connection`, `*Edge`, `*Input`)
|
|
22
|
-
* - Types starting with `Node` interface marker (exact match only)
|
|
23
|
-
*
|
|
24
|
-
* @param typeName - The GraphQL type name
|
|
25
|
-
* @param type - The GraphQL object type definition
|
|
26
|
-
* @returns `true` if this type should become a Stonecrop doctype
|
|
27
|
-
* @public
|
|
28
|
-
*/
|
|
29
|
-
export declare function defaultIsEntityType(typeName: string, type: GraphQLObjectType): boolean;
|
|
30
|
-
/**
|
|
31
|
-
* Default heuristic to filter fields on entity types.
|
|
32
|
-
* Skips internal fields that don't represent meaningful data.
|
|
33
|
-
*
|
|
34
|
-
* @param fieldName - The GraphQL field name
|
|
35
|
-
* @param _field - The GraphQL field definition (unused in default implementation)
|
|
36
|
-
* @param parentType - The parent entity type, whose interfaces declare its Relay identifier
|
|
37
|
-
* @returns `true` if this field should be included
|
|
38
|
-
* @public
|
|
39
|
-
*/
|
|
40
|
-
export declare function defaultIsEntityField(fieldName: string, _field: GraphQLField<unknown, unknown>, parentType: GraphQLObjectType): boolean;
|
|
41
|
-
/**
|
|
42
|
-
* Classify a single GraphQL field into a Stonecrop field definition.
|
|
43
|
-
*
|
|
44
|
-
* Classification rules (in order):
|
|
45
|
-
* 1. Scalar types → look up in merged scalar map
|
|
46
|
-
* 2. Enum types → `Select` with enum values as options
|
|
47
|
-
* 3. Object types that are entities → `Link` with slug as options
|
|
48
|
-
* 4. Object types that are Connections → `Doctype` with node type slug as options
|
|
49
|
-
* 5. List of entity type → `Doctype` with item type slug as options
|
|
50
|
-
* 6. Anything else → `Data` with `_unmapped: true`
|
|
51
|
-
*
|
|
52
|
-
* @param fieldName - The GraphQL field name
|
|
53
|
-
* @param field - The GraphQL field definition
|
|
54
|
-
* @param entityTypes - Set of type names classified as entities
|
|
55
|
-
* @param options - Conversion options (for custom scalars, unmapped meta, etc.)
|
|
56
|
-
* @returns The Stonecrop field definition
|
|
57
|
-
* @public
|
|
58
|
-
*/
|
|
59
|
-
export declare function classifyFieldType(fieldName: string, field: GraphQLField<unknown, unknown>, entityTypes: Set<string>, options?: GraphQLConversionOptions): GraphQLConversionFieldMeta;
|
|
60
|
-
//# sourceMappingURL=heuristics.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"heuristics.d.ts","sourceRoot":"","sources":["../../../src/converter/heuristics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAON,KAAK,iBAAiB,EACtB,KAAK,YAAY,EAGjB,MAAM,SAAS,CAAA;AAGhB,OAAO,KAAK,EAAE,0BAA0B,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AA+BnF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CA8BtF;AA+CD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CACnC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,EACtC,UAAU,EAAE,iBAAiB,GAC3B,OAAO,CAGT;AAyED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAChC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,EACrC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,EACxB,OAAO,GAAE,wBAA6B,GACpC,0BAA0B,CAkG5B"}
|