@opensaas/stack-core 0.39.1 → 0.40.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +156 -0
- package/CLAUDE.md +51 -0
- package/dist/access/access-filter.d.ts +83 -10
- package/dist/access/access-filter.d.ts.map +1 -1
- package/dist/access/access-filter.js +139 -18
- package/dist/access/access-filter.js.map +1 -1
- package/dist/access/access-filter.test.js +224 -12
- package/dist/access/access-filter.test.js.map +1 -1
- package/dist/access/engine.d.ts +43 -1
- package/dist/access/engine.d.ts.map +1 -1
- package/dist/access/engine.js +58 -0
- package/dist/access/engine.js.map +1 -1
- package/dist/access/engine.test.d.ts +2 -0
- package/dist/access/engine.test.d.ts.map +1 -0
- package/dist/access/engine.test.js +56 -0
- package/dist/access/engine.test.js.map +1 -0
- package/dist/access/errors.d.ts +28 -0
- package/dist/access/errors.d.ts.map +1 -1
- package/dist/access/errors.js +38 -2
- package/dist/access/errors.js.map +1 -1
- package/dist/access/field-access.d.ts +12 -0
- package/dist/access/field-access.d.ts.map +1 -1
- package/dist/access/field-access.js +22 -2
- package/dist/access/field-access.js.map +1 -1
- package/dist/access/field-access.test.js +66 -0
- package/dist/access/field-access.test.js.map +1 -1
- package/dist/access/field-visibility.d.ts +2 -1
- package/dist/access/field-visibility.d.ts.map +1 -1
- package/dist/access/field-visibility.js +45 -3
- package/dist/access/field-visibility.js.map +1 -1
- package/dist/access/index.d.ts +5 -2
- package/dist/access/index.d.ts.map +1 -1
- package/dist/access/index.js +4 -2
- package/dist/access/index.js.map +1 -1
- package/dist/config/types.d.ts +77 -11
- package/dist/config/types.d.ts.map +1 -1
- package/dist/context/index.d.ts +19 -1
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +61 -17
- package/dist/context/index.js.map +1 -1
- package/dist/context/nested-operations.d.ts.map +1 -1
- package/dist/context/nested-operations.js +54 -13
- package/dist/context/nested-operations.js.map +1 -1
- package/dist/context/write-pipeline.d.ts +3 -2
- package/dist/context/write-pipeline.d.ts.map +1 -1
- package/dist/context/write-pipeline.js +8 -5
- package/dist/context/write-pipeline.js.map +1 -1
- package/dist/fields/index.d.ts +13 -1
- package/dist/fields/index.d.ts.map +1 -1
- package/dist/fields/index.js +50 -5
- package/dist/fields/index.js.map +1 -1
- package/dist/fields/relationship-foreign-key.test.d.ts +2 -0
- package/dist/fields/relationship-foreign-key.test.d.ts.map +1 -0
- package/dist/fields/relationship-foreign-key.test.js +18 -0
- package/dist/fields/relationship-foreign-key.test.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/case-utils.d.ts +15 -0
- package/dist/lib/case-utils.d.ts.map +1 -1
- package/dist/lib/case-utils.js +21 -0
- package/dist/lib/case-utils.js.map +1 -1
- package/dist/lib/case-utils.test.js +23 -1
- package/dist/lib/case-utils.test.js.map +1 -1
- package/dist/lib/prisma-errors.d.ts +38 -0
- package/dist/lib/prisma-errors.d.ts.map +1 -0
- package/dist/lib/prisma-errors.js +53 -0
- package/dist/lib/prisma-errors.js.map +1 -0
- package/dist/mcp/constants.d.ts +23 -0
- package/dist/mcp/constants.d.ts.map +1 -0
- package/dist/mcp/constants.js +23 -0
- package/dist/mcp/constants.js.map +1 -0
- package/dist/mcp/field-schema.d.ts +8 -0
- package/dist/mcp/field-schema.d.ts.map +1 -0
- package/dist/mcp/field-schema.js +69 -0
- package/dist/mcp/field-schema.js.map +1 -0
- package/dist/mcp/handler.d.ts.map +1 -1
- package/dist/mcp/handler.js +76 -90
- package/dist/mcp/handler.js.map +1 -1
- package/dist/mcp/projection.d.ts +62 -0
- package/dist/mcp/projection.d.ts.map +1 -0
- package/dist/mcp/projection.js +390 -0
- package/dist/mcp/projection.js.map +1 -0
- package/dist/utils/password.d.ts +3 -1
- package/dist/utils/password.d.ts.map +1 -1
- package/dist/utils/password.js +4 -2
- package/dist/utils/password.js.map +1 -1
- package/package.json +1 -1
- package/src/access/access-filter.test.ts +278 -12
- package/src/access/access-filter.ts +207 -19
- package/src/access/engine.test.ts +67 -0
- package/src/access/engine.ts +81 -1
- package/src/access/errors.ts +42 -2
- package/src/access/field-access.test.ts +78 -0
- package/src/access/field-access.ts +34 -2
- package/src/access/field-visibility.ts +75 -10
- package/src/access/index.ts +14 -0
- package/src/config/types.ts +77 -11
- package/src/context/index.ts +95 -15
- package/src/context/nested-operations.ts +61 -16
- package/src/context/write-pipeline.ts +8 -4
- package/src/fields/index.ts +55 -5
- package/src/fields/relationship-foreign-key.test.ts +29 -0
- package/src/index.ts +17 -1
- package/src/lib/case-utils.test.ts +32 -0
- package/src/lib/case-utils.ts +26 -0
- package/src/lib/prisma-errors.ts +86 -0
- package/src/mcp/constants.ts +24 -0
- package/src/mcp/field-schema.ts +84 -0
- package/src/mcp/handler.ts +109 -107
- package/src/mcp/projection.ts +497 -0
- package/src/utils/password.ts +5 -3
- package/tests/access-relationships.test.ts +3 -3
- package/tests/access.test.ts +83 -0
- package/tests/context.test.ts +259 -12
- package/tests/field-types.test.ts +113 -0
- package/tests/mcp-fields-projection-access.test.ts +250 -0
- package/tests/mcp-handler.test.ts +695 -4
- package/tests/nested-access-and-hooks.test.ts +65 -0
- package/tests/nested-write-synthetic-relation.test.ts +437 -0
- package/tests/password-types.test.ts +28 -0
- package/tests/password.test.ts +16 -2
- package/tests/prisma-errors.test.ts +141 -0
- package/tests/singleton.test.ts +9 -1
- package/tests/to-one-include-access-filter.test.ts +172 -0
- package/tests/with-session.test.ts +243 -0
- package/tests/write-pipeline.test.ts +29 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import { checkAccess, getRelatedListConfig } from '../access/engine.js';
|
|
2
|
+
import { validateQueryFieldReadAccess, validateQueryKeys } from '../access/query-validation.js';
|
|
3
|
+
import { pickFields } from '../query/index.js';
|
|
4
|
+
import { MCP_NESTED_TAKE_DEFAULT, MCP_NESTED_TAKE_MAX } from './constants.js';
|
|
5
|
+
/** A scalar/virtual field in a `fields` projection is selected by naming it `true` — this advertises that, not the field's own value shape (`fieldToJsonSchema`, used for `create`/`update`, is a different schema entirely). */
|
|
6
|
+
function scalarSelectorSchema(fieldName) {
|
|
7
|
+
return { type: 'boolean', description: `Include the "${fieldName}" field` };
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* `id`/`createdAt`/`updatedAt` are added to every list automatically and
|
|
11
|
+
* excluded from `listConfig.fields` (CLAUDE.md's "System Fields"), so they
|
|
12
|
+
* need their own selector entries at every level a `fields` projection can
|
|
13
|
+
* name fields — a scalar-only loop over `listConfig.fields` would otherwise
|
|
14
|
+
* never advertise or accept them. `id` is additionally forced into every
|
|
15
|
+
* `FieldSelection` this module builds (`withId`, below), never left to the
|
|
16
|
+
* caller: a record projected down to none of its own identifying columns
|
|
17
|
+
* cannot be the target of a follow-up `update`/`delete` call.
|
|
18
|
+
*/
|
|
19
|
+
function systemFieldProperties() {
|
|
20
|
+
return {
|
|
21
|
+
id: {
|
|
22
|
+
type: 'boolean',
|
|
23
|
+
description: 'Include the "id" field (always returned regardless of selection)',
|
|
24
|
+
},
|
|
25
|
+
createdAt: scalarSelectorSchema('createdAt'),
|
|
26
|
+
updatedAt: scalarSelectorSchema('updatedAt'),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function isSystemFieldName(name) {
|
|
30
|
+
return name === 'id' || name === 'createdAt' || name === 'updatedAt';
|
|
31
|
+
}
|
|
32
|
+
/** Force `id` into a field selection being built for one level of a `fields` projection — see `systemFieldProperties`'s doc comment. */
|
|
33
|
+
function withId(selection) {
|
|
34
|
+
return { ...selection, id: true };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Thrown when a `query` tool's `fields` argument names something the
|
|
38
|
+
* generated projection schema does not advertise — an unknown field, a
|
|
39
|
+
* relation two levels deeper than enumerated, or the wrong shape for what
|
|
40
|
+
* it named. Caught in the MCP handler and turned into an `isError` tool
|
|
41
|
+
* result (never a JSON-RPC protocol error, #995) naming what was asked for
|
|
42
|
+
* and what is available, so the calling model can retry narrower.
|
|
43
|
+
*/
|
|
44
|
+
export class McpProjectionRefusedError extends Error {
|
|
45
|
+
constructor(message) {
|
|
46
|
+
super(message);
|
|
47
|
+
this.name = 'McpProjectionRefusedError';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function isRelationshipField(fieldConfig) {
|
|
51
|
+
return (!!fieldConfig &&
|
|
52
|
+
fieldConfig.type === 'relationship' &&
|
|
53
|
+
'ref' in fieldConfig &&
|
|
54
|
+
!!fieldConfig.ref);
|
|
55
|
+
}
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- RelationshipField must accept any TypeInfo
|
|
57
|
+
function isMany(fieldConfig) {
|
|
58
|
+
return fieldConfig.many === true;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Resolve a relation field's target list, or `null` if it should not be
|
|
62
|
+
* advertised/selectable at all: the ref doesn't resolve, its MCP tools are
|
|
63
|
+
* disabled (ADR-0033 — a list with `mcp.enabled: false` is unreachable
|
|
64
|
+
* through MCP, including as a relation target), or this session's
|
|
65
|
+
* operation-level `query` access on it is denied outright.
|
|
66
|
+
*/
|
|
67
|
+
async function relatedListIfVisible(
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- RelationshipField must accept any TypeInfo
|
|
69
|
+
fieldConfig, config, session, context) {
|
|
70
|
+
const related = getRelatedListConfig(fieldConfig.ref, config);
|
|
71
|
+
if (!related)
|
|
72
|
+
return null;
|
|
73
|
+
if (related.listConfig.mcp?.enabled === false)
|
|
74
|
+
return null;
|
|
75
|
+
const queryAccess = related.listConfig.access?.operation?.query;
|
|
76
|
+
const result = await checkAccess(queryAccess, { session, context });
|
|
77
|
+
if (result === false)
|
|
78
|
+
return null;
|
|
79
|
+
return related;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Generate the JSON Schema for the `query` tool's `fields` projection
|
|
83
|
+
* argument: two levels of the list's own vocabulary (ADR-0033). Level 1 is
|
|
84
|
+
* this list's own scalar/virtual fields (booleans) and relations (nested
|
|
85
|
+
* objects); level 2, inside a relation's own `fields`, is the related
|
|
86
|
+
* list's scalar/virtual fields only — a relation named there terminates
|
|
87
|
+
* (not itself selectable further), which is what keeps the schema finite
|
|
88
|
+
* against a self-referential relationship. Denied/MCP-disabled relation
|
|
89
|
+
* targets are omitted from the vocabulary entirely, per-session.
|
|
90
|
+
*/
|
|
91
|
+
export async function generateFieldsProjectionSchema(
|
|
92
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
|
|
93
|
+
listConfig, config, session, context) {
|
|
94
|
+
const properties = systemFieldProperties();
|
|
95
|
+
for (const [fieldName, fieldConfig] of Object.entries(listConfig.fields)) {
|
|
96
|
+
if (!isRelationshipField(fieldConfig)) {
|
|
97
|
+
properties[fieldName] = scalarSelectorSchema(fieldName);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const related = await relatedListIfVisible(fieldConfig, config, session, context);
|
|
101
|
+
if (!related)
|
|
102
|
+
continue;
|
|
103
|
+
const level2Properties = systemFieldProperties();
|
|
104
|
+
for (const [relFieldName, relFieldConfig] of Object.entries(related.listConfig.fields)) {
|
|
105
|
+
if (isRelationshipField(relFieldConfig))
|
|
106
|
+
continue;
|
|
107
|
+
level2Properties[relFieldName] = scalarSelectorSchema(relFieldName);
|
|
108
|
+
}
|
|
109
|
+
const many = isMany(fieldConfig);
|
|
110
|
+
properties[fieldName] = {
|
|
111
|
+
type: 'object',
|
|
112
|
+
description: many
|
|
113
|
+
? `Select fields from the related ${related.listName} records`
|
|
114
|
+
: `Select fields from the related ${related.listName} record`,
|
|
115
|
+
properties: {
|
|
116
|
+
fields: {
|
|
117
|
+
type: 'object',
|
|
118
|
+
description: `Fields to return from ${related.listName}`,
|
|
119
|
+
properties: level2Properties,
|
|
120
|
+
additionalProperties: false,
|
|
121
|
+
},
|
|
122
|
+
...(many
|
|
123
|
+
? {
|
|
124
|
+
where: { type: 'object', description: `Prisma where clause for ${related.listName}` },
|
|
125
|
+
orderBy: { type: 'object', description: 'Sort order' },
|
|
126
|
+
take: {
|
|
127
|
+
type: 'number',
|
|
128
|
+
description: `Max rows to return (default ${MCP_NESTED_TAKE_DEFAULT}, hard cap ${MCP_NESTED_TAKE_MAX})`,
|
|
129
|
+
},
|
|
130
|
+
skip: { type: 'number', description: 'Rows to skip' },
|
|
131
|
+
count: {
|
|
132
|
+
type: 'boolean',
|
|
133
|
+
description: 'Return the total row count instead of, or alongside, the rows themselves',
|
|
134
|
+
},
|
|
135
|
+
}
|
|
136
|
+
: {}),
|
|
137
|
+
},
|
|
138
|
+
...(many ? {} : { required: ['fields'] }),
|
|
139
|
+
additionalProperties: false,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
type: 'object',
|
|
144
|
+
description: 'Select which fields (and, for relations, which nested fields) to return. Omit to get scalars and virtuals only, unchanged from today.',
|
|
145
|
+
properties,
|
|
146
|
+
additionalProperties: false,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Build the `context.db` include entry for a to-many relation's own rows —
|
|
151
|
+
* shared by a `fields`-selected relation and a count-only one (the latter
|
|
152
|
+
* fetches identically, just never adds the key to `fieldSelection`, so the
|
|
153
|
+
* rows reach field-visibility's access check but never the caller).
|
|
154
|
+
*/
|
|
155
|
+
function buildManyIncludeEntry(many, entry) {
|
|
156
|
+
if (!many)
|
|
157
|
+
return true;
|
|
158
|
+
const includeEntry = {};
|
|
159
|
+
if (entry.where !== undefined)
|
|
160
|
+
includeEntry.where = entry.where;
|
|
161
|
+
if (entry.orderBy !== undefined)
|
|
162
|
+
includeEntry.orderBy = entry.orderBy;
|
|
163
|
+
const requestedTake = entry.take !== undefined ? Number(entry.take) : MCP_NESTED_TAKE_DEFAULT;
|
|
164
|
+
includeEntry.take = Math.min(requestedTake, MCP_NESTED_TAKE_MAX);
|
|
165
|
+
if (entry.skip !== undefined)
|
|
166
|
+
includeEntry.skip = entry.skip;
|
|
167
|
+
return includeEntry;
|
|
168
|
+
}
|
|
169
|
+
/** Access-scoped `_count.select` entry for one to-many relation: the related list's `query` access ANDed with any caller `where` on that same relation — mirrors how `buildAccessScopedInclude` scopes the relation's own rows (`andWhere`), since Prisma's `_count` is a sibling key that walk does not itself touch. */
|
|
170
|
+
async function accessScopedCountEntry(
|
|
171
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- RelationshipField must accept any TypeInfo
|
|
172
|
+
fieldConfig, config, session, context, callerWhere) {
|
|
173
|
+
const related = getRelatedListConfig(fieldConfig.ref, config);
|
|
174
|
+
if (!related)
|
|
175
|
+
return null;
|
|
176
|
+
const queryAccess = related.listConfig.access?.operation?.query;
|
|
177
|
+
const result = await checkAccess(queryAccess, { session, context });
|
|
178
|
+
if (result === false)
|
|
179
|
+
return null;
|
|
180
|
+
const accessWhere = typeof result === 'object' ? result : undefined;
|
|
181
|
+
const merged = accessWhere && callerWhere ? { AND: [accessWhere, callerWhere] } : (accessWhere ?? callerWhere);
|
|
182
|
+
return merged ? { where: merged } : true;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Validate a caller-supplied `fields` argument against the same vocabulary
|
|
186
|
+
* `generateFieldsProjectionSchema` advertises, and translate it into a
|
|
187
|
+
* `context.db` `include` (so the normal read pipeline — access scoping,
|
|
188
|
+
* `needs` folding, the depth cap — applies exactly as it does for any other
|
|
189
|
+
* caller `include`; no parallel read path) plus a `FieldSelection` for
|
|
190
|
+
* projecting the result down to what was asked for. Throws
|
|
191
|
+
* `McpProjectionRefusedError` naming what was asked for and what's
|
|
192
|
+
* available on any mismatch, never serves on a best-effort basis.
|
|
193
|
+
*/
|
|
194
|
+
export async function resolveFieldsProjection(fieldsArg, listKey,
|
|
195
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
|
|
196
|
+
listConfig, config, session, context) {
|
|
197
|
+
if (fieldsArg === null || typeof fieldsArg !== 'object' || Array.isArray(fieldsArg)) {
|
|
198
|
+
throw new McpProjectionRefusedError(`"fields" must be an object mapping field names to \`true\` or a relation selector.`);
|
|
199
|
+
}
|
|
200
|
+
const include = {};
|
|
201
|
+
// `id` is always projected back, whether or not the caller asked for it —
|
|
202
|
+
// see `systemFieldProperties`'s doc comment.
|
|
203
|
+
const fieldSelection = { id: true };
|
|
204
|
+
const countRequests = new Map();
|
|
205
|
+
let hasIncludeEntries = false;
|
|
206
|
+
for (const [fieldName, rawValue] of Object.entries(fieldsArg)) {
|
|
207
|
+
if (isSystemFieldName(fieldName)) {
|
|
208
|
+
if (rawValue !== true) {
|
|
209
|
+
throw new McpProjectionRefusedError(`"${listKey}.${fieldName}" is a scalar — select it with \`true\`, not ${JSON.stringify(rawValue)}.`);
|
|
210
|
+
}
|
|
211
|
+
fieldSelection[fieldName] = true;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const fieldConfig = listConfig.fields[fieldName];
|
|
215
|
+
if (!fieldConfig) {
|
|
216
|
+
throw new McpProjectionRefusedError(`"${listKey}" has no field "${fieldName}". Available fields: ${Object.keys(listConfig.fields).join(', ')}.`);
|
|
217
|
+
}
|
|
218
|
+
if (!isRelationshipField(fieldConfig)) {
|
|
219
|
+
if (rawValue !== true) {
|
|
220
|
+
throw new McpProjectionRefusedError(`"${listKey}.${fieldName}" is a scalar — select it with \`true\`, not ${JSON.stringify(rawValue)}.`);
|
|
221
|
+
}
|
|
222
|
+
fieldSelection[fieldName] = true;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const related = await relatedListIfVisible(fieldConfig, config, session, context);
|
|
226
|
+
if (!related) {
|
|
227
|
+
throw new McpProjectionRefusedError(`"${listKey}.${fieldName}" is not available for selection — its related list is not ` +
|
|
228
|
+
`queryable by this session, or has MCP disabled.`);
|
|
229
|
+
}
|
|
230
|
+
if (rawValue === null || typeof rawValue !== 'object' || Array.isArray(rawValue)) {
|
|
231
|
+
throw new McpProjectionRefusedError(`"${listKey}.${fieldName}" is a relation — select it with an object, e.g. { "fields": { ... } }.`);
|
|
232
|
+
}
|
|
233
|
+
const entry = rawValue;
|
|
234
|
+
const many = isMany(fieldConfig);
|
|
235
|
+
const allowedKeys = many
|
|
236
|
+
? new Set(['fields', 'where', 'orderBy', 'take', 'skip', 'count'])
|
|
237
|
+
: new Set(['fields']);
|
|
238
|
+
for (const key of Object.keys(entry)) {
|
|
239
|
+
if (!allowedKeys.has(key)) {
|
|
240
|
+
throw new McpProjectionRefusedError(`"${listKey}.${fieldName}" does not accept "${key}" (allowed: ${[...allowedKeys].join(', ')}).`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// Type-check each key before it reaches Prisma — a malformed value here
|
|
244
|
+
// would otherwise only surface as an opaque Prisma error out of the
|
|
245
|
+
// `context.db` call below, instead of a clear refusal naming the shape
|
|
246
|
+
// that was expected (mirrors `access-filter.ts`'s `asEntryObject`).
|
|
247
|
+
if (entry.where !== undefined && (entry.where === null || typeof entry.where !== 'object')) {
|
|
248
|
+
throw new McpProjectionRefusedError(`"${listKey}.${fieldName}.where" must be an object.`);
|
|
249
|
+
}
|
|
250
|
+
if (entry.orderBy !== undefined &&
|
|
251
|
+
(entry.orderBy === null || typeof entry.orderBy !== 'object')) {
|
|
252
|
+
throw new McpProjectionRefusedError(`"${listKey}.${fieldName}.orderBy" must be an object or an array of objects.`);
|
|
253
|
+
}
|
|
254
|
+
if (entry.take !== undefined && typeof entry.take !== 'number') {
|
|
255
|
+
throw new McpProjectionRefusedError(`"${listKey}.${fieldName}.take" must be a number.`);
|
|
256
|
+
}
|
|
257
|
+
if (entry.skip !== undefined && typeof entry.skip !== 'number') {
|
|
258
|
+
throw new McpProjectionRefusedError(`"${listKey}.${fieldName}.skip" must be a number.`);
|
|
259
|
+
}
|
|
260
|
+
if (entry.count !== undefined && typeof entry.count !== 'boolean') {
|
|
261
|
+
throw new McpProjectionRefusedError(`"${listKey}.${fieldName}.count" must be a boolean.`);
|
|
262
|
+
}
|
|
263
|
+
if (entry.take !== undefined && entry.take < 0) {
|
|
264
|
+
throw new McpProjectionRefusedError(`"${listKey}.${fieldName}.take" must not be negative (nested reverse pagination isn't supported).`);
|
|
265
|
+
}
|
|
266
|
+
// A nested `where`/`orderBy` names fields on the RELATED list — validate
|
|
267
|
+
// them the same way the root read path validates the root list's own
|
|
268
|
+
// `where`/`orderBy` (#912/#915), or a session could infer a field-level-
|
|
269
|
+
// denied related field's value from which rows come back, their order,
|
|
270
|
+
// or (via `count`, below) how many there are.
|
|
271
|
+
if (entry.where !== undefined || entry.orderBy !== undefined) {
|
|
272
|
+
validateQueryKeys({
|
|
273
|
+
where: entry.where,
|
|
274
|
+
orderBy: entry.orderBy,
|
|
275
|
+
listConfig: related.listConfig,
|
|
276
|
+
listName: related.listName,
|
|
277
|
+
config,
|
|
278
|
+
isSudo: false,
|
|
279
|
+
});
|
|
280
|
+
await validateQueryFieldReadAccess({
|
|
281
|
+
where: entry.where,
|
|
282
|
+
orderBy: entry.orderBy,
|
|
283
|
+
listConfig: related.listConfig,
|
|
284
|
+
listName: related.listName,
|
|
285
|
+
session,
|
|
286
|
+
context,
|
|
287
|
+
isSudo: false,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
const nestedFieldsArg = entry.fields;
|
|
291
|
+
const wantsCount = many && entry.count === true;
|
|
292
|
+
if (nestedFieldsArg === undefined && !wantsCount) {
|
|
293
|
+
throw new McpProjectionRefusedError(`"${listKey}.${fieldName}" needs "fields"${many ? ' or "count"' : ''}.`);
|
|
294
|
+
}
|
|
295
|
+
const nestedSelection = {};
|
|
296
|
+
if (nestedFieldsArg !== undefined) {
|
|
297
|
+
if (nestedFieldsArg === null ||
|
|
298
|
+
typeof nestedFieldsArg !== 'object' ||
|
|
299
|
+
Array.isArray(nestedFieldsArg)) {
|
|
300
|
+
throw new McpProjectionRefusedError(`"${listKey}.${fieldName}.fields" must be an object.`);
|
|
301
|
+
}
|
|
302
|
+
for (const [relFieldName, relValue] of Object.entries(nestedFieldsArg)) {
|
|
303
|
+
if (!isSystemFieldName(relFieldName)) {
|
|
304
|
+
const relFieldConfig = related.listConfig.fields[relFieldName];
|
|
305
|
+
if (!relFieldConfig || isRelationshipField(relFieldConfig)) {
|
|
306
|
+
throw new McpProjectionRefusedError(`"${related.listName}" has no selectable field "${relFieldName}" at this depth — relations ` +
|
|
307
|
+
`are not selectable two levels deep; issue a second query for that.`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (relValue !== true) {
|
|
311
|
+
throw new McpProjectionRefusedError(`"${related.listName}.${relFieldName}" is a scalar — select it with \`true\`.`);
|
|
312
|
+
}
|
|
313
|
+
nestedSelection[relFieldName] = true;
|
|
314
|
+
}
|
|
315
|
+
include[fieldName] = buildManyIncludeEntry(many, entry);
|
|
316
|
+
hasIncludeEntries = true;
|
|
317
|
+
fieldSelection[fieldName] = { _type: 'fragment', _fields: withId(nestedSelection) };
|
|
318
|
+
}
|
|
319
|
+
else if (wantsCount) {
|
|
320
|
+
// Count-only (no `fields`): still name the relation in the include,
|
|
321
|
+
// with the SAME rows a `fields`-and-`count` request would fetch —
|
|
322
|
+
// never a synthetic zero-row placeholder. Field-visibility's read-
|
|
323
|
+
// access check for the relationship field runs against whatever the
|
|
324
|
+
// include actually fetched (`accessItem: workingItem`); a rule that
|
|
325
|
+
// inspects the relation's own value (e.g. `item.comments.length`)
|
|
326
|
+
// would see a permanently-empty array under `take: 0` regardless of
|
|
327
|
+
// the true content, which is wrong in the opposite direction from
|
|
328
|
+
// what this whole mechanism exists to prevent. The rows themselves
|
|
329
|
+
// never reach the caller — `fieldSelection` has no entry for this key
|
|
330
|
+
// — only the access decision `filterReadableFields` made against them
|
|
331
|
+
// does, read off in `projectMcpResult` below via whether the key
|
|
332
|
+
// survived into the filtered result.
|
|
333
|
+
include[fieldName] = buildManyIncludeEntry(many, entry);
|
|
334
|
+
hasIncludeEntries = true;
|
|
335
|
+
}
|
|
336
|
+
if (wantsCount) {
|
|
337
|
+
countRequests.set(fieldName, nestedFieldsArg !== undefined ? 'alongside' : 'only');
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (countRequests.size > 0) {
|
|
341
|
+
const countSelect = {};
|
|
342
|
+
for (const fieldName of countRequests.keys()) {
|
|
343
|
+
const fieldConfig = listConfig.fields[fieldName];
|
|
344
|
+
const rawEntry = fieldsArg[fieldName];
|
|
345
|
+
const scoped = await accessScopedCountEntry(fieldConfig, config, session, context, rawEntry.where);
|
|
346
|
+
if (scoped)
|
|
347
|
+
countSelect[fieldName] = scoped;
|
|
348
|
+
}
|
|
349
|
+
if (Object.keys(countSelect).length > 0) {
|
|
350
|
+
include._count = { select: countSelect };
|
|
351
|
+
hasIncludeEntries = true;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
include: hasIncludeEntries ? include : undefined,
|
|
356
|
+
fieldSelection: fieldSelection,
|
|
357
|
+
countRequests,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Project one raw (already access-checked and field-visibility-filtered)
|
|
362
|
+
* result row down to a resolved `fields` projection, folding in any
|
|
363
|
+
* requested relation counts from Prisma's `_count`.
|
|
364
|
+
*
|
|
365
|
+
* `_count` is not a declared field, so field-visibility (`filterReadableFields`)
|
|
366
|
+
* never applies the relationship field's own field-level `read` access to it
|
|
367
|
+
* the way it already does for the relation's rows. Rather than re-deriving
|
|
368
|
+
* that decision here — which would mean evaluating the field's access rule
|
|
369
|
+
* a second time against `rawItem`, a row `filterReadableFields` may have
|
|
370
|
+
* already stripped OTHER fields from, risking a different answer than the
|
|
371
|
+
* canonical one it computed against the true raw row — `resolveFieldsProjection`
|
|
372
|
+
* names every counted relation in the include (even a count-only one, with
|
|
373
|
+
* `take: 0`) purely so the ordinary pipeline makes that decision once, at the
|
|
374
|
+
* right place. A count is emitted only for a relation whose key survived
|
|
375
|
+
* into `rawItem`; one field-visibility dropped is a relation whose count
|
|
376
|
+
* must stay hidden too.
|
|
377
|
+
*/
|
|
378
|
+
export function projectMcpResult(rawItem, resolved) {
|
|
379
|
+
const picked = pickFields(rawItem, resolved.fieldSelection);
|
|
380
|
+
const rawCounts = rawItem._count;
|
|
381
|
+
const counts = rawCounts && typeof rawCounts === 'object' ? rawCounts : {};
|
|
382
|
+
for (const [key, mode] of resolved.countRequests) {
|
|
383
|
+
if (!(key in rawItem))
|
|
384
|
+
continue;
|
|
385
|
+
const count = typeof counts[key] === 'number' ? counts[key] : 0;
|
|
386
|
+
picked[key] = mode === 'only' ? count : { items: picked[key], count };
|
|
387
|
+
}
|
|
388
|
+
return picked;
|
|
389
|
+
}
|
|
390
|
+
//# sourceMappingURL=projection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projection.js","sourceRoot":"","sources":["../../src/mcp/projection.ts"],"names":[],"mappings":"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"}
|
package/dist/utils/password.d.ts
CHANGED
|
@@ -7,7 +7,9 @@ export declare class HashedPassword {
|
|
|
7
7
|
compare(plainPassword: string): Promise<boolean>;
|
|
8
8
|
toString(): string;
|
|
9
9
|
[Symbol.toPrimitive](hint: string): string;
|
|
10
|
-
toJSON():
|
|
10
|
+
toJSON(): {
|
|
11
|
+
isSet: boolean;
|
|
12
|
+
};
|
|
11
13
|
valueOf(): string;
|
|
12
14
|
}
|
|
13
15
|
//# sourceMappingURL=password.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"password.d.ts","sourceRoot":"","sources":["../../src/utils/password.ts"],"names":[],"mappings":"AAKA,wBAAsB,YAAY,CAChC,aAAa,EAAE,MAAM,EACrB,UAAU,GAAE,MAA4B,GACvC,OAAO,CAAC,MAAM,CAAC,CAMjB;AAED,wBAAsB,eAAe,CACnC,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,OAAO,CAAC,CAiBlB;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAIvD;AAID,qBAAa,cAAc;IACb,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,MAAM,EAIxC;IAEK,OAAO,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAErD;IAED,QAAQ,IAAI,MAAM,CAEjB;IAGD,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKzC;
|
|
1
|
+
{"version":3,"file":"password.d.ts","sourceRoot":"","sources":["../../src/utils/password.ts"],"names":[],"mappings":"AAKA,wBAAsB,YAAY,CAChC,aAAa,EAAE,MAAM,EACrB,UAAU,GAAE,MAA4B,GACvC,OAAO,CAAC,MAAM,CAAC,CAMjB;AAED,wBAAsB,eAAe,CACnC,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,OAAO,CAAC,CAiBlB;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAIvD;AAID,qBAAa,cAAc;IACb,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,MAAM,EAIxC;IAEK,OAAO,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAErD;IAED,QAAQ,IAAI,MAAM,CAEjB;IAGD,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKzC;IAKD,MAAM,IAAI;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAE3B;IAED,OAAO,IAAI,MAAM,CAEhB;CACF"}
|
package/dist/utils/password.js
CHANGED
|
@@ -52,9 +52,11 @@ export class HashedPassword {
|
|
|
52
52
|
}
|
|
53
53
|
return this.hash;
|
|
54
54
|
}
|
|
55
|
-
// JSON.stringify calls this when present, so
|
|
55
|
+
// JSON.stringify calls this when present, so this is the serialize-for-output
|
|
56
|
+
// redaction point — never return the hash here. toString()/valueOf()/
|
|
57
|
+
// Symbol.toPrimitive stay unredacted for internal string/compare use.
|
|
56
58
|
toJSON() {
|
|
57
|
-
return this.hash;
|
|
59
|
+
return { isSet: !!this.hash };
|
|
58
60
|
}
|
|
59
61
|
valueOf() {
|
|
60
62
|
return this.hash;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"password.js","sourceRoot":"","sources":["../../src/utils/password.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,UAAU,CAAA;AAE7B,0EAA0E;AAC1E,MAAM,mBAAmB,GAAG,EAAE,CAAA;AAE9B,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,aAAqB,EACrB,UAAU,GAAW,mBAAmB;IAExC,IAAI,OAAO,aAAa,KAAK,QAAQ,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAA;IACxD,CAAC;IAED,OAAO,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,UAAU,CAAC,CAAA;AAC/C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,aAAqB,EACrB,cAAsB;IAEtB,IAAI,OAAO,aAAa,KAAK,QAAQ,IAAI,OAAO,cAAc,KAAK,QAAQ,EAAE,CAAC;QAC5E,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9D,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IAAI,CAAC;QACH,OAAO,MAAM,MAAM,CAAC,OAAO,CAAC,aAAa,EAAE,cAAc,CAAC,CAAA;IAC5D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,qEAAqE;QACrE,iEAAiE;QACjE,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,KAAK,CAAC,CAAA;QACnD,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAE3C,OAAO,0BAA0B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AAC/C,CAAC;AAED,yEAAyE;AACzE,iEAAiE;AACjE,MAAM,OAAO,cAAc;IACI,IAAI;IAAjC,YAA6B,IAAY;oBAAZ,IAAI;QAC/B,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YACtC,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAA;QACpE,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,aAAqB;QACjC,OAAO,eAAe,CAAC,aAAa,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;IAClD,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,CAAC,IAAI,CAAA;IAClB,CAAC;IAED,6EAA6E;IAC7E,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,IAAY;QAC/B,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YAC5C,OAAO,IAAI,CAAC,IAAI,CAAA;QAClB,CAAC;QACD,OAAO,IAAI,CAAC,IAAI,CAAA;IAClB,CAAC;IAED,
|
|
1
|
+
{"version":3,"file":"password.js","sourceRoot":"","sources":["../../src/utils/password.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,UAAU,CAAA;AAE7B,0EAA0E;AAC1E,MAAM,mBAAmB,GAAG,EAAE,CAAA;AAE9B,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,aAAqB,EACrB,UAAU,GAAW,mBAAmB;IAExC,IAAI,OAAO,aAAa,KAAK,QAAQ,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAA;IACxD,CAAC;IAED,OAAO,MAAM,CAAC,IAAI,CAAC,aAAa,EAAE,UAAU,CAAC,CAAA;AAC/C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,aAAqB,EACrB,cAAsB;IAEtB,IAAI,OAAO,aAAa,KAAK,QAAQ,IAAI,OAAO,cAAc,KAAK,QAAQ,EAAE,CAAC;QAC5E,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9D,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IAAI,CAAC;QACH,OAAO,MAAM,MAAM,CAAC,OAAO,CAAC,aAAa,EAAE,cAAc,CAAC,CAAA;IAC5D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,qEAAqE;QACrE,iEAAiE;QACjE,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,KAAK,CAAC,CAAA;QACnD,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAE3C,OAAO,0BAA0B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AAC/C,CAAC;AAED,yEAAyE;AACzE,iEAAiE;AACjE,MAAM,OAAO,cAAc;IACI,IAAI;IAAjC,YAA6B,IAAY;oBAAZ,IAAI;QAC/B,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YACtC,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAA;QACpE,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,aAAqB;QACjC,OAAO,eAAe,CAAC,aAAa,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;IAClD,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,CAAC,IAAI,CAAA;IAClB,CAAC;IAED,6EAA6E;IAC7E,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,IAAY;QAC/B,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YAC5C,OAAO,IAAI,CAAC,IAAI,CAAA;QAClB,CAAC;QACD,OAAO,IAAI,CAAC,IAAI,CAAA;IAClB,CAAC;IAED,8EAA8E;IAC9E,sEAAsE;IACtE,sEAAsE;IACtE,MAAM;QACJ,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;IAC/B,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC,IAAI,CAAA;IAClB,CAAC;CACF"}
|