@suss/contract-prisma 0.22.3 → 0.23.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 +8 -1
- package/dist/index.cjs +149 -7
- package/dist/index.js +149 -7
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -16,13 +16,20 @@ The behavior is in `metadata.storageContract`:
|
|
|
16
16
|
|
|
17
17
|
- `fieldSet: "exhaustive"`. A Prisma model declares every column its table has, so a column the code touches and this list leaves out is unknown rather than merely unlisted.
|
|
18
18
|
- `fields`, one entry per column, with the Prisma type, whether the field is optional (`nullable`), and `primary` / `unique` from the `@id` and `@unique` field attributes. Scalar fields (`Int`, `BigInt`, `Float`, `Decimal`, `String`, `Boolean`, `DateTime`, `Json`, `Bytes`) and enum fields are columns.
|
|
19
|
-
- Relation fields also appear, marked `derived`, because the Prisma client accepts them in an `include` or a `select` even though no column of that name exists. Leaving them out of a contract that calls itself exhaustive would report working code as reading an undeclared field. When the model stores the foreign key, the columns listed in `@relation(fields: [...])` come along as `relationKey`. Prisma allows that argument on one side of a relation only, so the other side and every implicit many-to-many have no key
|
|
19
|
+
- Relation fields also appear, marked `derived`, because the Prisma client accepts them in an `include` or a `select` even though no column of that name exists. Leaving them out of a contract that calls itself exhaustive would report working code as reading an undeclared field. When the model stores the foreign key, the columns listed in `@relation(fields: [...])` come along as `relationKey`. Prisma allows that argument on one side of a relation only, so the other side and every implicit many-to-many have no key.
|
|
20
|
+
- A many-to-many relation with no explicit join model gets a `joinContainer` entry instead, saying which table Prisma manages for it. See below.
|
|
20
21
|
- A model with any relation also gets a derived `_count` entry, which is what the client exposes for relation counts.
|
|
21
22
|
- `indexes`, from `@@index([...])` (not unique), `@@unique([...])`, and `@@id([...])` (both unique).
|
|
22
23
|
- `physicalTable`, when `@@map("...")` gives the table a SQL name different from the model name. That is the bridge to code speaking SQL names directly, such as Drizzle's `pgTable("users")` or a raw query.
|
|
23
24
|
|
|
24
25
|
These summaries have no inputs and no transitions. A table is a shape the code agrees with, not a function that returns something. Confidence is `declared` at `high`, since the schema says all of this outright.
|
|
25
26
|
|
|
27
|
+
### Implicit many-to-many join tables
|
|
28
|
+
|
|
29
|
+
Two list fields pointing at each other with neither side declaring `@relation(fields: [...])` are Prisma's implicit many-to-many: Prisma creates and manages the join table itself, and the schema never declares a model for it. This reader emits a summary for that table too, with the boundary Prisma would create: container `_Name` when the fields carry `@relation("Name")`, or `_FirstToSecond` with the two model names in alphabetical order otherwise, and two columns, `A` and `B`, keyed to whichever model sorts first and second. Each side of the relation gets `joinContainer` set to that table's own container name, so a `connect`, `disconnect` or `set` through the field counts as a write there instead of going unrecorded.
|
|
30
|
+
|
|
31
|
+
An explicit join model, the kind written out by hand to carry extra columns, already has a boundary through the ordinary model path above and gets none of this.
|
|
32
|
+
|
|
26
33
|
## What it does not read
|
|
27
34
|
|
|
28
35
|
- **Non-relational providers.** A MongoDB schema comes back empty; document storage semantics are separate work.
|
package/dist/index.cjs
CHANGED
|
@@ -85,6 +85,21 @@ function prismaSchemaToSummaries(source, options = {}) {
|
|
|
85
85
|
const sourceFile = options.source ?? "schema.prisma";
|
|
86
86
|
const scope = options.scope ?? "default";
|
|
87
87
|
const summaries = [];
|
|
88
|
+
const models = list.filter(
|
|
89
|
+
(node) => node.type === "model"
|
|
90
|
+
);
|
|
91
|
+
const relations = implicitManyToManyRelations(models, modelNames);
|
|
92
|
+
const joinContainerByField = /* @__PURE__ */ new Map();
|
|
93
|
+
for (const relation of relations) {
|
|
94
|
+
joinContainerByField.set(
|
|
95
|
+
`${relation.leftModel}.${relation.leftField}`,
|
|
96
|
+
relation.joinTable
|
|
97
|
+
);
|
|
98
|
+
joinContainerByField.set(
|
|
99
|
+
`${relation.rightModel}.${relation.rightField}`,
|
|
100
|
+
relation.joinTable
|
|
101
|
+
);
|
|
102
|
+
}
|
|
88
103
|
for (const node of list) {
|
|
89
104
|
const n = node;
|
|
90
105
|
if (n.type !== "model" && n.type !== "view") {
|
|
@@ -98,12 +113,132 @@ function prismaSchemaToSummaries(source, options = {}) {
|
|
|
98
113
|
enumNames,
|
|
99
114
|
storageSystem,
|
|
100
115
|
scope,
|
|
101
|
-
sourceFile
|
|
116
|
+
sourceFile,
|
|
117
|
+
joinContainerByField
|
|
102
118
|
})
|
|
103
119
|
);
|
|
104
120
|
}
|
|
121
|
+
for (const relation of relations) {
|
|
122
|
+
summaries.push(
|
|
123
|
+
buildJoinTableSummary({ relation, storageSystem, scope, sourceFile })
|
|
124
|
+
);
|
|
125
|
+
}
|
|
105
126
|
return summaries;
|
|
106
127
|
}
|
|
128
|
+
function implicitManyToManyRelations(models, modelNames) {
|
|
129
|
+
const byName = new Map(models.map((model) => [model.name, model]));
|
|
130
|
+
const found = [];
|
|
131
|
+
const seen = /* @__PURE__ */ new Set();
|
|
132
|
+
for (const model of models) {
|
|
133
|
+
for (const property of model.properties) {
|
|
134
|
+
if (property.type !== "field") {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const field = property;
|
|
138
|
+
if (field.array !== true || !modelNames.has(field.fieldType) || relationKeyOf(field) !== null) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const target = byName.get(field.fieldType);
|
|
142
|
+
if (target === void 0) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const relationName = relationNameOf(field);
|
|
146
|
+
const counterpart = counterpartField(
|
|
147
|
+
target,
|
|
148
|
+
model.name,
|
|
149
|
+
field.name,
|
|
150
|
+
relationName
|
|
151
|
+
);
|
|
152
|
+
if (counterpart === null) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const signature = pairSignature(
|
|
156
|
+
{ model: model.name, field: field.name },
|
|
157
|
+
{ model: field.fieldType, field: counterpart.name }
|
|
158
|
+
);
|
|
159
|
+
if (seen.has(signature)) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
seen.add(signature);
|
|
163
|
+
found.push({
|
|
164
|
+
leftModel: model.name,
|
|
165
|
+
leftField: field.name,
|
|
166
|
+
rightModel: field.fieldType,
|
|
167
|
+
rightField: counterpart.name,
|
|
168
|
+
relationName,
|
|
169
|
+
joinTable: joinTableName(model.name, field.fieldType, relationName)
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return found;
|
|
174
|
+
}
|
|
175
|
+
function counterpartField(target, backTo, ownFieldName, relationName) {
|
|
176
|
+
for (const property of target.properties) {
|
|
177
|
+
if (property.type !== "field") {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const field = property;
|
|
181
|
+
if (target.name === backTo && field.name === ownFieldName) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (field.array === true && field.fieldType === backTo && relationKeyOf(field) === null && relationNameOf(field) === relationName) {
|
|
185
|
+
return field;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
function pairSignature(a, b) {
|
|
191
|
+
const label = (side) => `${side.model}.${side.field}`;
|
|
192
|
+
return [label(a), label(b)].sort().join("|");
|
|
193
|
+
}
|
|
194
|
+
function joinTableName(leftModel, rightModel, relationName) {
|
|
195
|
+
if (relationName !== null) {
|
|
196
|
+
return `_${relationName}`;
|
|
197
|
+
}
|
|
198
|
+
const [first, second] = [leftModel, rightModel].sort();
|
|
199
|
+
return `_${first}To${second}`;
|
|
200
|
+
}
|
|
201
|
+
function buildJoinTableSummary(opts) {
|
|
202
|
+
const [modelA, modelB] = [
|
|
203
|
+
opts.relation.leftModel,
|
|
204
|
+
opts.relation.rightModel
|
|
205
|
+
].sort();
|
|
206
|
+
return {
|
|
207
|
+
kind: "library",
|
|
208
|
+
location: {
|
|
209
|
+
file: opts.sourceFile,
|
|
210
|
+
range: { start: 1, end: 1 },
|
|
211
|
+
exportName: null
|
|
212
|
+
},
|
|
213
|
+
identity: {
|
|
214
|
+
name: opts.relation.joinTable,
|
|
215
|
+
exportPath: null,
|
|
216
|
+
boundaryBinding: (0, import_behavioral_ir.storageBinding)({
|
|
217
|
+
recognition: "prisma",
|
|
218
|
+
storageSystem: opts.storageSystem,
|
|
219
|
+
scope: opts.scope,
|
|
220
|
+
container: opts.relation.joinTable
|
|
221
|
+
})
|
|
222
|
+
},
|
|
223
|
+
inputs: [],
|
|
224
|
+
transitions: [],
|
|
225
|
+
gaps: [],
|
|
226
|
+
confidence: { source: "declared", level: "high" },
|
|
227
|
+
metadata: {
|
|
228
|
+
storageContract: {
|
|
229
|
+
fieldSet: "exhaustive",
|
|
230
|
+
fields: [
|
|
231
|
+
{ name: "A", type: modelA, nullable: false, primary: true },
|
|
232
|
+
{ name: "B", type: modelB, nullable: false, primary: true }
|
|
233
|
+
],
|
|
234
|
+
indexes: [
|
|
235
|
+
{ fields: ["A", "B"], unique: true },
|
|
236
|
+
{ fields: ["B"], unique: false }
|
|
237
|
+
]
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
107
242
|
function prismaSchemaFileToSummaries(schemaPath, options = {}) {
|
|
108
243
|
if (!import_node_fs.default.existsSync(schemaPath)) {
|
|
109
244
|
throw new Error(`Prisma schema not found: ${schemaPath}`);
|
|
@@ -126,7 +261,8 @@ function buildModelSummary(opts) {
|
|
|
126
261
|
if (column !== null) {
|
|
127
262
|
columns.push(column);
|
|
128
263
|
}
|
|
129
|
-
const
|
|
264
|
+
const joinContainer = opts.joinContainerByField.get(`${opts.model.name}.${field.name}`) ?? null;
|
|
265
|
+
const related = relationField(field, opts.modelNames, joinContainer);
|
|
130
266
|
if (related !== null) {
|
|
131
267
|
hasRelation = true;
|
|
132
268
|
columns.push(related);
|
|
@@ -180,7 +316,7 @@ function buildModelSummary(opts) {
|
|
|
180
316
|
}
|
|
181
317
|
};
|
|
182
318
|
}
|
|
183
|
-
function relationField(field, modelNames) {
|
|
319
|
+
function relationField(field, modelNames, joinContainer) {
|
|
184
320
|
if (!modelNames.has(field.fieldType)) {
|
|
185
321
|
return null;
|
|
186
322
|
}
|
|
@@ -190,15 +326,21 @@ function relationField(field, modelNames) {
|
|
|
190
326
|
type: field.array === true ? `${field.fieldType}[]` : field.fieldType,
|
|
191
327
|
nullable: field.optional === true,
|
|
192
328
|
derived: true,
|
|
193
|
-
...key === null ? {} : { relationKey: key }
|
|
329
|
+
...key === null ? {} : { relationKey: key },
|
|
330
|
+
...joinContainer === null ? {} : { joinContainer }
|
|
194
331
|
};
|
|
195
332
|
}
|
|
333
|
+
function relationAttributeOf(field) {
|
|
334
|
+
return (field.attributes ?? []).find((attr) => attr.name === "relation");
|
|
335
|
+
}
|
|
196
336
|
function relationKeyOf(field) {
|
|
197
|
-
const relation = (field
|
|
198
|
-
(attr) => attr.name === "relation"
|
|
199
|
-
);
|
|
337
|
+
const relation = relationAttributeOf(field);
|
|
200
338
|
return relation === void 0 ? null : readKeyedArrayArg(relation, "fields");
|
|
201
339
|
}
|
|
340
|
+
function relationNameOf(field) {
|
|
341
|
+
const relation = relationAttributeOf(field);
|
|
342
|
+
return relation === void 0 ? null : readStringArg(relation);
|
|
343
|
+
}
|
|
202
344
|
function fieldToColumn(field, modelNames, enumNames) {
|
|
203
345
|
if (field.array === true) {
|
|
204
346
|
return null;
|
package/dist/index.js
CHANGED
|
@@ -50,6 +50,21 @@ function prismaSchemaToSummaries(source, options = {}) {
|
|
|
50
50
|
const sourceFile = options.source ?? "schema.prisma";
|
|
51
51
|
const scope = options.scope ?? "default";
|
|
52
52
|
const summaries = [];
|
|
53
|
+
const models = list.filter(
|
|
54
|
+
(node) => node.type === "model"
|
|
55
|
+
);
|
|
56
|
+
const relations = implicitManyToManyRelations(models, modelNames);
|
|
57
|
+
const joinContainerByField = /* @__PURE__ */ new Map();
|
|
58
|
+
for (const relation of relations) {
|
|
59
|
+
joinContainerByField.set(
|
|
60
|
+
`${relation.leftModel}.${relation.leftField}`,
|
|
61
|
+
relation.joinTable
|
|
62
|
+
);
|
|
63
|
+
joinContainerByField.set(
|
|
64
|
+
`${relation.rightModel}.${relation.rightField}`,
|
|
65
|
+
relation.joinTable
|
|
66
|
+
);
|
|
67
|
+
}
|
|
53
68
|
for (const node of list) {
|
|
54
69
|
const n = node;
|
|
55
70
|
if (n.type !== "model" && n.type !== "view") {
|
|
@@ -63,12 +78,132 @@ function prismaSchemaToSummaries(source, options = {}) {
|
|
|
63
78
|
enumNames,
|
|
64
79
|
storageSystem,
|
|
65
80
|
scope,
|
|
66
|
-
sourceFile
|
|
81
|
+
sourceFile,
|
|
82
|
+
joinContainerByField
|
|
67
83
|
})
|
|
68
84
|
);
|
|
69
85
|
}
|
|
86
|
+
for (const relation of relations) {
|
|
87
|
+
summaries.push(
|
|
88
|
+
buildJoinTableSummary({ relation, storageSystem, scope, sourceFile })
|
|
89
|
+
);
|
|
90
|
+
}
|
|
70
91
|
return summaries;
|
|
71
92
|
}
|
|
93
|
+
function implicitManyToManyRelations(models, modelNames) {
|
|
94
|
+
const byName = new Map(models.map((model) => [model.name, model]));
|
|
95
|
+
const found = [];
|
|
96
|
+
const seen = /* @__PURE__ */ new Set();
|
|
97
|
+
for (const model of models) {
|
|
98
|
+
for (const property of model.properties) {
|
|
99
|
+
if (property.type !== "field") {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const field = property;
|
|
103
|
+
if (field.array !== true || !modelNames.has(field.fieldType) || relationKeyOf(field) !== null) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const target = byName.get(field.fieldType);
|
|
107
|
+
if (target === void 0) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const relationName = relationNameOf(field);
|
|
111
|
+
const counterpart = counterpartField(
|
|
112
|
+
target,
|
|
113
|
+
model.name,
|
|
114
|
+
field.name,
|
|
115
|
+
relationName
|
|
116
|
+
);
|
|
117
|
+
if (counterpart === null) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const signature = pairSignature(
|
|
121
|
+
{ model: model.name, field: field.name },
|
|
122
|
+
{ model: field.fieldType, field: counterpart.name }
|
|
123
|
+
);
|
|
124
|
+
if (seen.has(signature)) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
seen.add(signature);
|
|
128
|
+
found.push({
|
|
129
|
+
leftModel: model.name,
|
|
130
|
+
leftField: field.name,
|
|
131
|
+
rightModel: field.fieldType,
|
|
132
|
+
rightField: counterpart.name,
|
|
133
|
+
relationName,
|
|
134
|
+
joinTable: joinTableName(model.name, field.fieldType, relationName)
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return found;
|
|
139
|
+
}
|
|
140
|
+
function counterpartField(target, backTo, ownFieldName, relationName) {
|
|
141
|
+
for (const property of target.properties) {
|
|
142
|
+
if (property.type !== "field") {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const field = property;
|
|
146
|
+
if (target.name === backTo && field.name === ownFieldName) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (field.array === true && field.fieldType === backTo && relationKeyOf(field) === null && relationNameOf(field) === relationName) {
|
|
150
|
+
return field;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
function pairSignature(a, b) {
|
|
156
|
+
const label = (side) => `${side.model}.${side.field}`;
|
|
157
|
+
return [label(a), label(b)].sort().join("|");
|
|
158
|
+
}
|
|
159
|
+
function joinTableName(leftModel, rightModel, relationName) {
|
|
160
|
+
if (relationName !== null) {
|
|
161
|
+
return `_${relationName}`;
|
|
162
|
+
}
|
|
163
|
+
const [first, second] = [leftModel, rightModel].sort();
|
|
164
|
+
return `_${first}To${second}`;
|
|
165
|
+
}
|
|
166
|
+
function buildJoinTableSummary(opts) {
|
|
167
|
+
const [modelA, modelB] = [
|
|
168
|
+
opts.relation.leftModel,
|
|
169
|
+
opts.relation.rightModel
|
|
170
|
+
].sort();
|
|
171
|
+
return {
|
|
172
|
+
kind: "library",
|
|
173
|
+
location: {
|
|
174
|
+
file: opts.sourceFile,
|
|
175
|
+
range: { start: 1, end: 1 },
|
|
176
|
+
exportName: null
|
|
177
|
+
},
|
|
178
|
+
identity: {
|
|
179
|
+
name: opts.relation.joinTable,
|
|
180
|
+
exportPath: null,
|
|
181
|
+
boundaryBinding: storageBinding({
|
|
182
|
+
recognition: "prisma",
|
|
183
|
+
storageSystem: opts.storageSystem,
|
|
184
|
+
scope: opts.scope,
|
|
185
|
+
container: opts.relation.joinTable
|
|
186
|
+
})
|
|
187
|
+
},
|
|
188
|
+
inputs: [],
|
|
189
|
+
transitions: [],
|
|
190
|
+
gaps: [],
|
|
191
|
+
confidence: { source: "declared", level: "high" },
|
|
192
|
+
metadata: {
|
|
193
|
+
storageContract: {
|
|
194
|
+
fieldSet: "exhaustive",
|
|
195
|
+
fields: [
|
|
196
|
+
{ name: "A", type: modelA, nullable: false, primary: true },
|
|
197
|
+
{ name: "B", type: modelB, nullable: false, primary: true }
|
|
198
|
+
],
|
|
199
|
+
indexes: [
|
|
200
|
+
{ fields: ["A", "B"], unique: true },
|
|
201
|
+
{ fields: ["B"], unique: false }
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
72
207
|
function prismaSchemaFileToSummaries(schemaPath, options = {}) {
|
|
73
208
|
if (!fs.existsSync(schemaPath)) {
|
|
74
209
|
throw new Error(`Prisma schema not found: ${schemaPath}`);
|
|
@@ -91,7 +226,8 @@ function buildModelSummary(opts) {
|
|
|
91
226
|
if (column !== null) {
|
|
92
227
|
columns.push(column);
|
|
93
228
|
}
|
|
94
|
-
const
|
|
229
|
+
const joinContainer = opts.joinContainerByField.get(`${opts.model.name}.${field.name}`) ?? null;
|
|
230
|
+
const related = relationField(field, opts.modelNames, joinContainer);
|
|
95
231
|
if (related !== null) {
|
|
96
232
|
hasRelation = true;
|
|
97
233
|
columns.push(related);
|
|
@@ -145,7 +281,7 @@ function buildModelSummary(opts) {
|
|
|
145
281
|
}
|
|
146
282
|
};
|
|
147
283
|
}
|
|
148
|
-
function relationField(field, modelNames) {
|
|
284
|
+
function relationField(field, modelNames, joinContainer) {
|
|
149
285
|
if (!modelNames.has(field.fieldType)) {
|
|
150
286
|
return null;
|
|
151
287
|
}
|
|
@@ -155,15 +291,21 @@ function relationField(field, modelNames) {
|
|
|
155
291
|
type: field.array === true ? `${field.fieldType}[]` : field.fieldType,
|
|
156
292
|
nullable: field.optional === true,
|
|
157
293
|
derived: true,
|
|
158
|
-
...key === null ? {} : { relationKey: key }
|
|
294
|
+
...key === null ? {} : { relationKey: key },
|
|
295
|
+
...joinContainer === null ? {} : { joinContainer }
|
|
159
296
|
};
|
|
160
297
|
}
|
|
298
|
+
function relationAttributeOf(field) {
|
|
299
|
+
return (field.attributes ?? []).find((attr) => attr.name === "relation");
|
|
300
|
+
}
|
|
161
301
|
function relationKeyOf(field) {
|
|
162
|
-
const relation = (field
|
|
163
|
-
(attr) => attr.name === "relation"
|
|
164
|
-
);
|
|
302
|
+
const relation = relationAttributeOf(field);
|
|
165
303
|
return relation === void 0 ? null : readKeyedArrayArg(relation, "fields");
|
|
166
304
|
}
|
|
305
|
+
function relationNameOf(field) {
|
|
306
|
+
const relation = relationAttributeOf(field);
|
|
307
|
+
return relation === void 0 ? null : readStringArg(relation);
|
|
308
|
+
}
|
|
167
309
|
function fieldToColumn(field, modelNames, enumNames) {
|
|
168
310
|
if (field.array === true) {
|
|
169
311
|
return null;
|
package/package.json
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suss/contract-prisma",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Turn a Prisma schema into a suss contract and check it against the queries your code runs on those tables.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"suss",
|
|
8
|
-
"
|
|
8
|
+
"static-analysis",
|
|
9
9
|
"prisma",
|
|
10
10
|
"orm",
|
|
11
|
-
"
|
|
12
|
-
"contract"
|
|
11
|
+
"database",
|
|
12
|
+
"api-contract",
|
|
13
|
+
"api-drift"
|
|
13
14
|
],
|
|
14
15
|
"license": "Apache-2.0",
|
|
15
16
|
"author": "Nimbus Cloud AI LLC",
|
|
@@ -47,7 +48,7 @@
|
|
|
47
48
|
},
|
|
48
49
|
"dependencies": {
|
|
49
50
|
"@mrleebo/prisma-ast": "^0.15.0",
|
|
50
|
-
"@suss/behavioral-ir": "0.
|
|
51
|
+
"@suss/behavioral-ir": "0.23.0"
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
53
54
|
"tsup": "*",
|