@simtlix/simfinity-core 3.5.6 → 3.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simtlix/simfinity-core",
3
- "version": "3.5.6",
3
+ "version": "3.5.7",
4
4
  "description": "Driver-free GraphQL runtime and model metadata for Simfinity",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
package/src/runtime.js CHANGED
@@ -24,6 +24,26 @@ import './introspection.js';
24
24
  // created is application code, such as a masking resolver, recorded once per type so later
25
25
  // in-place wrapping (for example by the auth plugin) does not change the snapshot.
26
26
  const generatedResolvers = new WeakSet();
27
+ // A caller that runs one operation per call, such as simfinity-mcp, can collect the pagination
28
+ // count through the root value instead of the shared context: a function stored under this key
29
+ // receives the count. The key is not exported; simfinity-mcp defines the same Symbol.for key.
30
+ const COUNT_SINK = Symbol.for('simfinity.countSink');
31
+ const countSinkOf = (value) => {
32
+ if (value === null || (typeof value !== 'object' && typeof value !== 'function')) return undefined;
33
+ const sink = value[COUNT_SINK];
34
+ return typeof sink === 'function' ? sink : undefined;
35
+ };
36
+ // The root value arrives as parent, which resolver wrappers that forward only (parent, args,
37
+ // context) still pass on; info.rootValue is the fallback. Without a sink the count goes to
38
+ // context.count, where Envelop/Apollo/Yoga count plugins read it.
39
+ const reportCount = (parent, info, context, count) => {
40
+ const sink = countSinkOf(parent) || (info ? countSinkOf(info.rootValue) : undefined);
41
+ if (sink) {
42
+ sink(count);
43
+ return;
44
+ }
45
+ context.count = count;
46
+ };
27
47
  const applicationResolvedFields = new WeakMap();
28
48
  const markGenerated = (resolve) => {
29
49
  generatedResolvers.add(resolve);
@@ -1508,6 +1528,9 @@ const resolveById = async (type, args, context, requiredId) => {
1508
1528
  return results[0] || null;
1509
1529
  };
1510
1530
 
1531
+ // Every generated mutation field carries extensions.simfinityMutation with its operation (and, for
1532
+ // a state-machine action, the action and its from/to states), so tools such as the MCP package can
1533
+ // classify it without guessing from its name. Extensions do not appear in SDL or introspection.
1511
1534
  const buildMutation = (name, includedMutationTypes, includedCustomMutations) => {
1512
1535
  const rootQueryArgs = {};
1513
1536
  rootQueryArgs.name = name;
@@ -1524,6 +1547,7 @@ const buildMutation = (name, includedMutationTypes, includedCustomMutations) =>
1524
1547
  type: type.gqltype,
1525
1548
  description: 'add',
1526
1549
  args: argsObject,
1550
+ extensions: { simfinityMutation: { typeName: type.gqltype.name, operation: operations.SAVE } },
1527
1551
  async resolve(parent, args, context) {
1528
1552
  const params = {
1529
1553
  type,
@@ -1541,6 +1565,7 @@ const buildMutation = (name, includedMutationTypes, includedCustomMutations) =>
1541
1565
  type: type.gqltype,
1542
1566
  description: 'delete',
1543
1567
  args: { id: { type: new GraphQLNonNull(GraphQLID) } },
1568
+ extensions: { simfinityMutation: { typeName: type.gqltype.name, operation: operations.DELETE } },
1544
1569
  async resolve(parent, args, context) {
1545
1570
  const params = {
1546
1571
  type,
@@ -1566,6 +1591,7 @@ const buildMutation = (name, includedMutationTypes, includedCustomMutations) =>
1566
1591
  type: type.gqltype,
1567
1592
  description: 'update',
1568
1593
  args: argsObject,
1594
+ extensions: { simfinityMutation: { typeName: type.gqltype.name, operation: operations.UPDATE } },
1569
1595
  async resolve(parent, args, context) {
1570
1596
  const params = {
1571
1597
  type,
@@ -1586,6 +1612,15 @@ const buildMutation = (name, includedMutationTypes, includedCustomMutations) =>
1586
1612
  type: type.gqltype,
1587
1613
  description: actionField.description,
1588
1614
  args: argsObject,
1615
+ extensions: {
1616
+ simfinityMutation: {
1617
+ typeName: type.gqltype.name,
1618
+ operation: operations.STATE_CHANGED,
1619
+ action: actionName,
1620
+ from: actionField.from && actionField.from.name,
1621
+ to: actionField.to && actionField.to.name,
1622
+ },
1623
+ },
1589
1624
  async resolve(parent, args, context) {
1590
1625
  const params = {
1591
1626
  type,
@@ -1617,6 +1652,7 @@ const buildMutation = (name, includedMutationTypes, includedCustomMutations) =>
1617
1652
  type: registeredMutation.outputModel,
1618
1653
  description: registeredMutation.description,
1619
1654
  args: argsObject,
1655
+ extensions: { simfinityMutation: { operation: operations.CUSTOM_MUTATION } },
1620
1656
  async resolve(parent, args, context) {
1621
1657
  const params = {
1622
1658
  args,
@@ -1657,8 +1693,10 @@ const buildRootQuery = (name, includedTypes) => {
1657
1693
  rootQueryArgs.fields[type.listEntitiesEndpointName] = {
1658
1694
  type: new GraphQLList(type.gqltype),
1659
1695
  args: argsObject,
1660
- extensions: { simfinityQuery: { typeName: type.gqltype.name, operation: 'find' } },
1661
- async resolve(parent, args, context) {
1696
+ // countSink: this resolver reports pagination counts through reportCount, so a caller
1697
+ // that supplies a root-value sink can pass its context unchanged.
1698
+ extensions: { simfinityQuery: { typeName: type.gqltype.name, operation: 'find', countSink: true } },
1699
+ async resolve(parent, args, context, info) {
1662
1700
  const joinedScopes = inspectClientPaths(type.gqltype, args, 'find');
1663
1701
  const params = {
1664
1702
  type,
@@ -1679,7 +1717,7 @@ const buildRootQuery = (name, includedTypes) => {
1679
1717
 
1680
1718
  const [result, resultCount] = await Promise.all([dataPromise, countPromise]);
1681
1719
  if (wantsCount) {
1682
- context.count = resultCount;
1720
+ reportCount(parent, info, context, resultCount);
1683
1721
  }
1684
1722
  return result;
1685
1723
  },
@@ -3,6 +3,11 @@ import { GraphQLScalarType, GraphQLString, GraphQLInt, GraphQLFloat, GraphQLBool
3
3
  // The base each validated scalar delegates parsing to. A base exists before its scalar, so these links cannot cycle.
4
4
  const validatedBases = new WeakMap();
5
5
 
6
+ // The same link as a non-enumerable property, so MCP (packages/mcp/src/index.js, VALIDATED_SCALAR_BASE) can tell a
7
+ // validated scalar, whose output is its base's, from a hand-set `baseScalarType` storage hint, whose own `serialize`
8
+ // decides the output. Not exported; Symbol.for gives both packages the same key.
9
+ const VALIDATED_SCALAR_BASE = Symbol.for('simfinity.validatedScalarBase');
10
+
6
11
  // Follow validated-scalar chains to the scalar that defines the literal syntax. Any other scalar is the root, even
7
12
  // with a hand-set `baseScalarType` storage hint, because its own parseLiteral decides which literals it takes.
8
13
  const findRootScalar = (scalarType) => {
@@ -68,5 +73,6 @@ export function createValidatedScalar(name, description, baseScalarType, validat
68
73
 
69
74
  scalar.baseScalarType = baseScalarType;
70
75
  validatedBases.set(scalar, baseScalarType);
76
+ Object.defineProperty(scalar, VALIDATED_SCALAR_BASE, { value: baseScalarType });
71
77
  return scalar;
72
78
  }