graphddb 0.7.8 → 0.7.10

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/dist/cli.js CHANGED
@@ -3,10 +3,12 @@ import {
3
3
  buildBridgeBundle,
4
4
  buildManifest,
5
5
  buildOperations
6
- } from "./chunk-NZRCBEWS.js";
6
+ } from "./chunk-EOJDN3SA.js";
7
+ import "./chunk-DFUKGU2Q.js";
8
+ import "./chunk-3UD3XIF2.js";
7
9
  import {
8
10
  MetadataRegistry
9
- } from "./chunk-HFFIB77D.js";
11
+ } from "./chunk-3ZU2VW3L.js";
10
12
  import {
11
13
  createDefaultLinter
12
14
  } from "./chunk-PDUVTYC5.js";
@@ -1096,6 +1098,9 @@ function queryResultTypeName(queryName) {
1096
1098
  function txElementTypeName(txName, paramName) {
1097
1099
  return `${toPascalCase(txName)}${toPascalCase(paramName)}Item`;
1098
1100
  }
1101
+ function contractOperationId(contract, method) {
1102
+ return `${contract}__${method}`;
1103
+ }
1099
1104
  function relationSegments(resultPath) {
1100
1105
  if (resultPath === "$") return [];
1101
1106
  const parts = resultPath.replace(/^\$\.?/, "").split(".");
@@ -1179,6 +1184,7 @@ function emitResultClasses(node, typeName, manifest, out, seen) {
1179
1184
  name: key,
1180
1185
  shape: "scalar",
1181
1186
  scalarType: manifestField?.type ?? "string",
1187
+ ...manifestField?.format !== void 0 ? { scalarFormat: manifestField.format } : {},
1182
1188
  ...manifestField?.description !== void 0 ? { description: manifestField.description } : {}
1183
1189
  });
1184
1190
  }
@@ -1236,6 +1242,65 @@ function elementParamField(name, p) {
1236
1242
  ...p.literals !== void 0 ? { literals: p.literals } : {}
1237
1243
  };
1238
1244
  }
1245
+ function byContractOperationId(a, b) {
1246
+ const ida = contractOperationId(a.contract, a.method);
1247
+ const idb = contractOperationId(b.contract, b.method);
1248
+ return ida < idb ? -1 : ida > idb ? 1 : 0;
1249
+ }
1250
+ function assertFreshTypeName(name, contract, method, seen) {
1251
+ if (seen.has(name)) {
1252
+ throw new Error(
1253
+ `Contract '${contract}.${method}': generated result type name '${name}' collides with an already-generated type of the same name. Rename the contract method or the conflicting query so the generated type names are unique.`
1254
+ );
1255
+ }
1256
+ }
1257
+ function composeFields(compose) {
1258
+ return compose.map((c) => ({
1259
+ name: c.as,
1260
+ shape: "optional-item",
1261
+ itemType: queryResultTypeName(contractOperationId(c.contract, c.method))
1262
+ }));
1263
+ }
1264
+ function appendFieldsToClass(out, className, extra) {
1265
+ if (extra.length === 0) return;
1266
+ const idx = out.findIndex((c) => c.name === className);
1267
+ if (idx < 0) return;
1268
+ const cls = out[idx];
1269
+ const extraNames = new Set(extra.map((f) => f.name));
1270
+ const fields = [...cls.fields.filter((f) => !extraNames.has(f.name)), ...extra].sort(
1271
+ (a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0
1272
+ );
1273
+ out[idx] = { ...cls, fields };
1274
+ }
1275
+ function contractQueryMethodSpec(operations, binding) {
1276
+ const spec = operations.contracts?.[binding.contract]?.methods?.[binding.method];
1277
+ if (!spec) {
1278
+ throw new Error(
1279
+ `Contract query binding '${binding.contract}.${binding.method}' is not present in operations.contracts \u2014 the binding surface must be derived from the same operations document.`
1280
+ );
1281
+ }
1282
+ return spec;
1283
+ }
1284
+ function contractCommandMethodSpec(operations, binding) {
1285
+ const spec = operations.contracts?.[binding.contract]?.methods?.[binding.method];
1286
+ if (!spec || !("single" in spec)) {
1287
+ throw new Error(
1288
+ `Contract command binding '${binding.contract}.${binding.method}' is not present in operations.contracts \u2014 the binding surface must be derived from the same operations document.`
1289
+ );
1290
+ }
1291
+ return spec;
1292
+ }
1293
+ function contractCommandParams(operations, binding, spec) {
1294
+ const single = spec.single;
1295
+ const params = single.mode === "transaction" ? operations.transactions?.[single.transaction]?.params : operations.commands[single.operation]?.params;
1296
+ if (!params) {
1297
+ const ref = single.mode === "transaction" ? `transaction '${single.transaction}'` : `command '${single.operation}'`;
1298
+ throw new Error(
1299
+ `Contract '${binding.contract}.${binding.method}': its single resolution target (${ref}) is not present in the operations document.`
1300
+ );
1301
+ }
1302
+ return params;
1303
+ }
1239
1304
  function buildBindingModel(manifest, operations, options) {
1240
1305
  const queries = operations.queries;
1241
1306
  const commands = operations.commands;
@@ -1243,6 +1308,8 @@ function buildBindingModel(manifest, operations, options) {
1243
1308
  const queryEntityMap = new Map(options.queries.map((q) => [q.name, q.entity]));
1244
1309
  const queryNames = [...queryEntityMap.keys()].sort();
1245
1310
  const commandNames = new Set(options.commands);
1311
+ const contractQueries = [...options.contractQueries ?? []].sort(byContractOperationId);
1312
+ const contractCommands = [...options.contractCommands ?? []].sort(byContractOperationId);
1246
1313
  const entities = Object.keys(manifest.entities).sort().map((name) => {
1247
1314
  const meta = manifest.entities[name];
1248
1315
  return {
@@ -1266,6 +1333,42 @@ function buildBindingModel(manifest, operations, options) {
1266
1333
  for (const op of spec.operations) insertOperation(root, op, manifest);
1267
1334
  emitResultClasses(root, queryResultTypeName(queryName), manifest, resultClasses, seen);
1268
1335
  }
1336
+ for (const q of contractQueries) {
1337
+ const opId = contractOperationId(q.contract, q.method);
1338
+ const spec = queries[q.operation];
1339
+ if (!spec) {
1340
+ throw new Error(
1341
+ `Contract '${q.contract}.${q.method}': referenced operation '${q.operation}' is not present in operations.queries.`
1342
+ );
1343
+ }
1344
+ const methodSpec = contractQueryMethodSpec(operations, q);
1345
+ const cardinality = methodSpec.cardinality;
1346
+ const resultName = queryResultTypeName(opId);
1347
+ const recordName = cardinality === "many" ? `${resultName}Item` : resultName;
1348
+ assertFreshTypeName(recordName, q.contract, q.method, seen);
1349
+ if (cardinality === "many") assertFreshTypeName(resultName, q.contract, q.method, seen);
1350
+ const root = {
1351
+ entity: q.entity,
1352
+ projection: [],
1353
+ children: /* @__PURE__ */ new Map(),
1354
+ implicitFields: /* @__PURE__ */ new Set()
1355
+ };
1356
+ for (const op of spec.operations) insertOperation(root, op, manifest);
1357
+ emitResultClasses(root, recordName, manifest, resultClasses, seen);
1358
+ appendFieldsToClass(resultClasses, recordName, composeFields(methodSpec.compose ?? []));
1359
+ if (cardinality === "many") {
1360
+ seen.add(resultName);
1361
+ resultClasses.push({
1362
+ name: resultName,
1363
+ isConnection: true,
1364
+ connectionItemType: recordName,
1365
+ fields: [
1366
+ { name: "items", shape: "optional-item", itemType: recordName },
1367
+ { name: "cursor", shape: "scalar", scalarType: "string" }
1368
+ ]
1369
+ });
1370
+ }
1371
+ }
1269
1372
  emitTransactionElementTypes(transactions, resultClasses, seen);
1270
1373
  const byEntity = /* @__PURE__ */ new Map();
1271
1374
  const ensure = (entity) => {
@@ -1300,6 +1403,36 @@ function buildBindingModel(manifest, operations, options) {
1300
1403
  ...spec.description !== void 0 ? { description: spec.description } : {}
1301
1404
  });
1302
1405
  }
1406
+ for (const q of contractQueries) {
1407
+ const opId = contractOperationId(q.contract, q.method);
1408
+ const spec = queries[q.operation];
1409
+ const methodSpec = contractQueryMethodSpec(operations, q);
1410
+ const description = methodSpec.description ?? queryDescription(q.operation, q.entity, spec, manifest);
1411
+ ensure(q.entity).push({
1412
+ operationId: opId,
1413
+ kind: "query",
1414
+ params: buildParams(spec.params),
1415
+ resultType: queryResultTypeName(opId),
1416
+ contract: q.contract,
1417
+ contractMethod: q.method,
1418
+ cardinality: methodSpec.cardinality,
1419
+ contractKeyFields: operations.contracts[q.contract].key.fields,
1420
+ ...description !== void 0 ? { description } : {}
1421
+ });
1422
+ }
1423
+ for (const c of contractCommands) {
1424
+ const opId = contractOperationId(c.contract, c.method);
1425
+ const methodSpec = contractCommandMethodSpec(operations, c);
1426
+ ensure(c.entity).push({
1427
+ operationId: opId,
1428
+ kind: "command",
1429
+ params: buildParams(contractCommandParams(operations, c, methodSpec)),
1430
+ contract: c.contract,
1431
+ contractMethod: c.method,
1432
+ contractKeyFields: operations.contracts[c.contract].key.fields,
1433
+ ...methodSpec.description !== void 0 ? { description: methodSpec.description } : {}
1434
+ });
1435
+ }
1303
1436
  const repositories = [...byEntity.keys()].sort().map((className) => {
1304
1437
  const bucket = byEntity.get(className);
1305
1438
  return {
@@ -1416,7 +1549,7 @@ function resolveField(field, mapper) {
1416
1549
  type = field.isParamScalar ? mapper.paramScalar(
1417
1550
  field.scalarType === "number" ? "number" : field.literals ? "literal" : "string",
1418
1551
  field.literals
1419
- ) : mapper.scalar(field.scalarType ?? "string");
1552
+ ) : mapper.scalar(field.scalarType ?? "string", field.scalarFormat);
1420
1553
  break;
1421
1554
  }
1422
1555
  return {
@@ -1441,6 +1574,7 @@ function resolveClass(cls, mapper) {
1441
1574
  return {
1442
1575
  name: cls.name,
1443
1576
  isConnection: true,
1577
+ connectionItemType: cls.connectionItemType,
1444
1578
  fields: [items, cursor],
1445
1579
  ...cls.description !== void 0 ? { description: cls.description } : {}
1446
1580
  };
@@ -1471,11 +1605,36 @@ function resolveMethod(method, mapper) {
1471
1605
  operationId: method.operationId,
1472
1606
  kind: method.kind,
1473
1607
  params: method.params.map((p) => resolveParam(p, mapper)),
1474
- ...method.kind === "query" && method.resultType !== void 0 ? { returnType: mapper.optional(method.resultType) } : {},
1608
+ // A query returns its result type optionally (it may miss) except a
1609
+ // contract RANGE read (#250, cardinality 'many'), which always returns its
1610
+ // connection type (an empty result is a connection with zero items).
1611
+ ...method.kind === "query" && method.resultType !== void 0 ? {
1612
+ returnType: method.cardinality === "many" ? method.resultType : mapper.optional(method.resultType)
1613
+ } : {},
1614
+ // Contract identity + invocation facts (#250), passed through untouched so a
1615
+ // language generator can render the entry-point call.
1616
+ ...method.contract !== void 0 ? {
1617
+ contract: method.contract,
1618
+ contractMethod: method.contractMethod,
1619
+ contractKeyFields: method.contractKeyFields ?? []
1620
+ } : {},
1621
+ ...method.cardinality !== void 0 ? { cardinality: method.cardinality } : {},
1475
1622
  ...method.description !== void 0 ? { description: method.description } : {}
1476
1623
  };
1477
1624
  }
1478
- function resolveBindingView(model, mapper) {
1625
+ function assertContractCapable(model, language) {
1626
+ for (const repo of model.repositories) {
1627
+ for (const m of repo.methods) {
1628
+ if (m.contract !== void 0) {
1629
+ throw new Error(
1630
+ `Binding model contains the contract-derived method '${m.contract}.${m.contractMethod}' (${m.operationId}), but the '${language}' renderer did not declare contract support (supportsContracts). Rendering it through the raw-op call would silently drop the contract execution semantics (composed fragments / ConditionCheck / compose fields / range connection) \u2014 add a contract entry-point branch to the renderer's templates and pass { supportsContracts: true }, or exclude contract methods from the binding surface.`
1631
+ );
1632
+ }
1633
+ }
1634
+ }
1635
+ }
1636
+ function resolveBindingView(model, mapper, options = {}) {
1637
+ if (!options.supportsContracts) assertContractCapable(model, mapper.language);
1479
1638
  return {
1480
1639
  language: mapper.language,
1481
1640
  entities: model.entities,
@@ -1552,13 +1711,18 @@ from __future__ import annotations
1552
1711
  `;
1553
1712
  var REPO_METHOD = ` def {{methodName}}(self{{#if signature}}, {{signature}}{{/if}}) -> {{returnType}}:{{#if description}}
1554
1713
  """{{pyDoc description}}"""{{/if}}
1555
- {{#if isQuery}} return self._runtime.execute_query(
1714
+ {{#if contractName}} {{#if isQuery}}return {{/if}}self._runtime.{{contractCall}}(
1715
+ "{{contractName}}",
1716
+ "{{contractMethod}}",
1717
+ {{keyDict}},{{#if sharedDict}}
1718
+ {{sharedDict}},{{/if}}
1719
+ ){{else}}{{#if isQuery}} return self._runtime.execute_query(
1556
1720
  query_id="{{operationId}}",
1557
1721
  params={{paramsDict}},
1558
1722
  ){{else}} self._runtime.execute_command(
1559
1723
  command_id="{{operationId}}",
1560
1724
  params={{paramsDict}},
1561
- ){{/if}}`;
1725
+ ){{/if}}{{/if}}`;
1562
1726
  var REPO_CLASS = `class {{name}}:
1563
1727
  def __init__(self, runtime: GraphDDBRuntime) -> None:
1564
1728
  self._runtime = runtime
@@ -1645,7 +1809,7 @@ function classContext(cls, isDataclass) {
1645
1809
  };
1646
1810
  }
1647
1811
  function methodContext(m) {
1648
- return {
1812
+ const base = {
1649
1813
  methodName: toSnakeCase(m.operationId),
1650
1814
  signature: signatureOf(m.params),
1651
1815
  returnType: m.returnType ?? "None",
@@ -1654,6 +1818,18 @@ function methodContext(m) {
1654
1818
  paramsDict: paramsDictOf(m.params),
1655
1819
  ...m.description !== void 0 ? { description: m.description } : {}
1656
1820
  };
1821
+ if (m.contract === void 0) return base;
1822
+ const keyFields = new Set(m.contractKeyFields ?? []);
1823
+ const keyParams = m.kind === "query" ? m.params : m.params.filter((p) => keyFields.has(p.name));
1824
+ const sharedParams = m.kind === "query" ? [] : m.params.filter((p) => !keyFields.has(p.name));
1825
+ return {
1826
+ ...base,
1827
+ contractName: m.contract,
1828
+ contractMethod: m.contractMethod,
1829
+ contractCall: m.kind === "query" ? "execute_query_method" : "execute_command_method",
1830
+ keyDict: paramsDictOf(keyParams),
1831
+ ...sharedParams.length > 0 ? { sharedDict: paramsDictOf(sharedParams) } : {}
1832
+ };
1657
1833
  }
1658
1834
  function txMethodContext(m) {
1659
1835
  return {
@@ -1780,9 +1956,49 @@ function bindingSurface(queries, commands) {
1780
1956
  commands: Object.keys(commands)
1781
1957
  };
1782
1958
  }
1959
+ function fullBindingSurface(queries, commands, operations, contracts = {}) {
1960
+ const base = bindingSurface(queries, commands);
1961
+ const contractSpecs = operations.contracts ?? {};
1962
+ const contractQueries = [];
1963
+ const contractCommands = [];
1964
+ for (const contractName of Object.keys(contractSpecs).sort()) {
1965
+ const spec = contractSpecs[contractName];
1966
+ const source = contracts[contractName];
1967
+ if (!source) {
1968
+ throw new Error(
1969
+ `Contract '${contractName}' is present in the operations document but missing from the in-memory contract map \u2014 pass the same 'contracts' the bundle was built with to derive the binding surface.`
1970
+ );
1971
+ }
1972
+ for (const methodName of Object.keys(spec.methods).sort()) {
1973
+ const method = source.methods[methodName];
1974
+ if (!method) {
1975
+ throw new Error(
1976
+ `Contract '${contractName}.${methodName}' is present in the operations document but missing from the in-memory contract \u2014 the two inputs must come from the same build.`
1977
+ );
1978
+ }
1979
+ const entity = method.op.entity.name;
1980
+ if (spec.kind === "query") {
1981
+ const m = spec.methods[methodName];
1982
+ contractQueries.push({
1983
+ contract: contractName,
1984
+ method: methodName,
1985
+ operation: m.operation,
1986
+ entity
1987
+ });
1988
+ } else {
1989
+ contractCommands.push({ contract: contractName, method: methodName, entity });
1990
+ }
1991
+ }
1992
+ }
1993
+ return {
1994
+ ...base,
1995
+ ...contractQueries.length > 0 ? { contractQueries } : {},
1996
+ ...contractCommands.length > 0 ? { contractCommands } : {}
1997
+ };
1998
+ }
1783
1999
  function renderPythonBinding(manifest, operations, surface, options = {}) {
1784
2000
  const model = buildBindingModel(manifest, operations, surface);
1785
- const view = resolveBindingView(model, PythonTypeMapper);
2001
+ const view = resolveBindingView(model, PythonTypeMapper, { supportsContracts: true });
1786
2002
  return {
1787
2003
  "types.py": renderTypesFile(view, options),
1788
2004
  "repositories.py": renderRepositoriesFile(view, options),
@@ -1794,7 +2010,8 @@ function renderPythonBinding(manifest, operations, surface, options = {}) {
1794
2010
  var PHP_NUMBER = "int|float";
1795
2011
  var PhpTypeMapper = {
1796
2012
  language: "php",
1797
- scalar(type) {
2013
+ scalar(type, format) {
2014
+ if (format === "datetime" || format === "date") return "\\DateTimeImmutable";
1798
2015
  switch (type) {
1799
2016
  case "number":
1800
2017
  case "numberSet":
@@ -1892,10 +2109,13 @@ final class {{name}}
1892
2109
  }`;
1893
2110
  var REPO_METHOD2 = `{{docblock}} public function {{methodName}}({{signature}}): {{returnType}}
1894
2111
  {
1895
- {{#if isQuery}} $result = $this->runtime->executeQuery('{{operationId}}', {{paramsArray}});
2112
+ {{#if contractName}}{{#if isQuery}} $result = $this->runtime->executeQueryMethod('{{contractName}}', '{{contractMethod}}', {{keyArray}});
2113
+ return {{hydrateResult}};
2114
+ {{else}} $this->runtime->executeCommandMethod('{{contractName}}', '{{contractMethod}}', {{keyArray}}{{#if sharedArray}}, {{sharedArray}}{{/if}});
2115
+ {{/if}}{{else}}{{#if isQuery}} $result = $this->runtime->executeQuery('{{operationId}}', {{paramsArray}});
1896
2116
  return {{hydrateResult}};
1897
2117
  {{else}} $this->runtime->executeCommand('{{operationId}}', {{paramsArray}});
1898
- {{/if}} }`;
2118
+ {{/if}}{{/if}} }`;
1899
2119
  var REPO_CLASS2 = `/**
1900
2120
  * Typed repository for the {{entity}} entity, wrapping a {@see GraphDDBRuntime}.
1901
2121
  */
@@ -2042,14 +2262,14 @@ function dtoUses(cls, allClassNames) {
2042
2262
  }
2043
2263
  function connectionItemType(cls) {
2044
2264
  if (!cls.isConnection) return void 0;
2045
- return cls.name.replace(/Connection$/, "");
2265
+ return cls.connectionItemType ?? cls.name.replace(/Connection$/, "");
2046
2266
  }
2047
2267
  function hydrateResultExpr(returnType) {
2048
2268
  const result = returnType.startsWith("?") ? returnType.slice(1) : returnType;
2049
2269
  return `$result === null ? null : ${result}::fromArray($result)`;
2050
2270
  }
2051
2271
  function methodContext2(method, metas, description) {
2052
- return {
2272
+ const base = {
2053
2273
  methodName: toCamelCase(method.operationId),
2054
2274
  signature: signatureOf2(method.params),
2055
2275
  returnType: method.returnType ?? "void",
@@ -2057,7 +2277,21 @@ function methodContext2(method, metas, description) {
2057
2277
  operationId: method.operationId,
2058
2278
  paramsArray: paramsArrayOf(method.params),
2059
2279
  docblock: docblockOf(method, metas, description),
2060
- hydrateResult: method.returnType ? hydrateResultExpr(method.returnType) : ""
2280
+ // A contract RANGE read (#250, cardinality 'many') always returns its
2281
+ // connection (non-nullable), so it hydrates unconditionally; every other
2282
+ // query hydrates through the null check.
2283
+ hydrateResult: method.returnType ? method.cardinality === "many" ? `${method.returnType}::fromArray($result)` : hydrateResultExpr(method.returnType) : ""
2284
+ };
2285
+ if (method.contract === void 0) return base;
2286
+ const keyFields = new Set(method.contractKeyFields ?? []);
2287
+ const keyParams = method.kind === "query" ? method.params : method.params.filter((p) => keyFields.has(p.name));
2288
+ const sharedParams = method.kind === "query" ? [] : method.params.filter((p) => !keyFields.has(p.name));
2289
+ return {
2290
+ ...base,
2291
+ contractName: method.contract,
2292
+ contractMethod: method.contractMethod,
2293
+ keyArray: paramsArrayOf(keyParams),
2294
+ ...sharedParams.length > 0 ? { sharedArray: paramsArrayOf(sharedParams) } : {}
2061
2295
  };
2062
2296
  }
2063
2297
  function txMethodContext2(method, metas) {
@@ -2113,8 +2347,21 @@ function buildParamMetaIndex(operations, surface) {
2113
2347
  for (const [name, spec] of Object.entries(operations.transactions ?? {})) {
2114
2348
  record(name, spec.params ?? {});
2115
2349
  }
2350
+ recordContractMetas(index, operations, surface, record);
2116
2351
  return index;
2117
2352
  }
2353
+ function recordContractMetas(index, operations, surface, record) {
2354
+ for (const q of surface.contractQueries ?? []) {
2355
+ const spec = operations.queries[q.operation];
2356
+ if (spec) record(contractOperationId(q.contract, q.method), spec.params ?? {});
2357
+ }
2358
+ for (const c of surface.contractCommands ?? []) {
2359
+ const methodSpec = operations.contracts?.[c.contract]?.methods?.[c.method];
2360
+ const single = methodSpec && "single" in methodSpec ? methodSpec.single : void 0;
2361
+ const params = single === void 0 ? void 0 : single.mode === "transaction" ? operations.transactions?.[single.transaction]?.params : operations.commands[single.operation]?.params;
2362
+ if (params) record(contractOperationId(c.contract, c.method), params);
2363
+ }
2364
+ }
2118
2365
  function pascal(name) {
2119
2366
  return name.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/\s+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
2120
2367
  }
@@ -2202,7 +2449,7 @@ function renderPhpFiles(view, index, options) {
2202
2449
  }
2203
2450
  function renderPhpBinding(manifest, operations, surface, options = {}) {
2204
2451
  const model = buildBindingModel(manifest, operations, surface);
2205
- const view = resolveBindingView(model, PhpTypeMapper);
2452
+ const view = resolveBindingView(model, PhpTypeMapper, { supportsContracts: true });
2206
2453
  const index = buildParamMetaIndex(operations, surface);
2207
2454
  return renderPhpFiles(view, index, options);
2208
2455
  }
@@ -2266,7 +2513,19 @@ pub struct {{name}} {
2266
2513
  pub cursor: Option<String>,
2267
2514
  }`;
2268
2515
  var REPO_METHOD3 = `{{docComment}} pub async fn {{methodName}}(&self{{#if signature}}, {{signature}}{{/if}}) -> {{returnType}} {
2269
- {{paramsStmts}}{{#if isQuery}} let value = self.runtime.execute_query("{{operationId}}", &params).await?;
2516
+ {{#if contractName}}{{keyStmt}}{{sharedStmts}}{{#if isQuery}} let value = self.runtime.execute_query_method("{{contractName}}", "{{contractMethod}}", &key, {{paramsExpr}}).await?;
2517
+ {{#if isRange}} let typed = serde_json::from_value(value)
2518
+ .map_err(|e| GraphDDBError::hydration(format!("cannot deserialize {{operationId}} result: {e}")))?;
2519
+ Ok(typed)
2520
+ {{else}} if value.is_null() {
2521
+ return Ok(None);
2522
+ }
2523
+ let typed = serde_json::from_value(value)
2524
+ .map_err(|e| GraphDDBError::hydration(format!("cannot deserialize {{operationId}} result: {e}")))?;
2525
+ Ok(Some(typed))
2526
+ {{/if}}{{else}} self.runtime.execute_command_method("{{contractName}}", "{{contractMethod}}", &key, {{paramsExpr}}).await?;
2527
+ Ok(())
2528
+ {{/if}}{{else}}{{paramsStmts}}{{#if isQuery}} let value = self.runtime.execute_query("{{operationId}}", &params).await?;
2270
2529
  if value.is_null() {
2271
2530
  return Ok(None);
2272
2531
  }
@@ -2275,7 +2534,7 @@ var REPO_METHOD3 = `{{docComment}} pub async fn {{methodName}}(&self{{#if sig
2275
2534
  Ok(Some(typed))
2276
2535
  {{else}} self.runtime.execute_command("{{operationId}}", &params).await?;
2277
2536
  Ok(())
2278
- {{/if}} }`;
2537
+ {{/if}}{{/if}} }`;
2279
2538
  var REPO_STRUCT = `/// Typed repository for the {{entity}} entity, wrapping a \`GraphDDBRuntime\`.
2280
2539
  #[derive(Clone)]
2281
2540
  pub struct {{name}} {
@@ -2376,6 +2635,16 @@ function buildParamMetaIndex2(operations, surface) {
2376
2635
  for (const [name, spec] of Object.entries(operations.transactions ?? {})) {
2377
2636
  record(name, spec.params ?? {});
2378
2637
  }
2638
+ for (const q of surface.contractQueries ?? []) {
2639
+ const spec = operations.queries[q.operation];
2640
+ if (spec) record(contractOperationId(q.contract, q.method), spec.params ?? {});
2641
+ }
2642
+ for (const c of surface.contractCommands ?? []) {
2643
+ const methodSpec = operations.contracts?.[c.contract]?.methods?.[c.method];
2644
+ const single = methodSpec && "single" in methodSpec ? methodSpec.single : void 0;
2645
+ const params = single === void 0 ? void 0 : single.mode === "transaction" ? operations.transactions?.[single.transaction]?.params : operations.commands[single.operation]?.params;
2646
+ if (params) record(contractOperationId(c.contract, c.method), params);
2647
+ }
2379
2648
  return index;
2380
2649
  }
2381
2650
  function metasFor2(index, opId, params) {
@@ -2398,7 +2667,7 @@ function fieldContext(field) {
2398
2667
  }
2399
2668
  function connectionItemType2(cls) {
2400
2669
  if (!cls.isConnection) return void 0;
2401
- return cls.name.replace(/Connection$/, "");
2670
+ return cls.connectionItemType ?? cls.name.replace(/Connection$/, "");
2402
2671
  }
2403
2672
  function signatureOf3(params) {
2404
2673
  return params.map((p) => `${toSnakeCase2(p.name)}: ${p.type}`).join(", ");
@@ -2452,8 +2721,13 @@ function returnTypeOf(method) {
2452
2721
  }
2453
2722
  return "Result<(), GraphDDBError>";
2454
2723
  }
2724
+ function keyStmtOf(params) {
2725
+ const entries = params.map((p) => `"${p.name}": ${toSnakeCase2(p.name)}`).join(", ");
2726
+ return entries ? ` let key = json!({ ${entries} });
2727
+ ` : " let key = json!({});\n";
2728
+ }
2455
2729
  function methodContext3(method, metas) {
2456
- return {
2730
+ const base = {
2457
2731
  methodName: toSnakeCase2(method.operationId),
2458
2732
  signature: signatureOf3(method.params),
2459
2733
  returnType: returnTypeOf(method),
@@ -2462,6 +2736,19 @@ function methodContext3(method, metas) {
2462
2736
  paramsStmts: paramsStmtsOf(method.params),
2463
2737
  docComment: methodDocComment(method, metas)
2464
2738
  };
2739
+ if (method.contract === void 0) return base;
2740
+ const keyFields = new Set(method.contractKeyFields ?? []);
2741
+ const keyParams = method.kind === "query" ? method.params : method.params.filter((p) => keyFields.has(p.name));
2742
+ const sharedParams = method.kind === "query" ? [] : method.params.filter((p) => !keyFields.has(p.name));
2743
+ return {
2744
+ ...base,
2745
+ contractName: method.contract,
2746
+ contractMethod: method.contractMethod,
2747
+ keyStmt: keyStmtOf(keyParams),
2748
+ sharedStmts: sharedParams.length > 0 ? paramsStmtsOf(sharedParams) : "",
2749
+ paramsExpr: sharedParams.length > 0 ? "Some(&params)" : "None",
2750
+ isRange: method.cardinality === "many"
2751
+ };
2465
2752
  }
2466
2753
  function txMethodContext3(method, metas) {
2467
2754
  return {
@@ -2539,7 +2826,7 @@ function renderBody(view, index, engine) {
2539
2826
  }
2540
2827
  function renderRustBinding(manifest, operations, surface, options = {}) {
2541
2828
  const model = buildBindingModel(manifest, operations, surface);
2542
- const view = resolveBindingView(model, RustTypeMapper);
2829
+ const view = resolveBindingView(model, RustTypeMapper, { supportsContracts: true });
2543
2830
  const index = buildParamMetaIndex2(operations, surface);
2544
2831
  const engine = engineOptions3(options);
2545
2832
  const body = renderBody(view, index, engine);
@@ -2611,7 +2898,29 @@ type {{name}} struct {
2611
2898
  Cursor *string \`json:"cursor"\`
2612
2899
  }`;
2613
2900
  var REPO_METHOD4 = `{{docComment}}func (r *{{repoName}}) {{methodName}}(ctx context.Context{{#if signature}}, {{signature}}{{/if}}) {{returnType}} {
2614
- {{paramsStmts}}{{#if isQuery}} raw, err := r.runtime.ExecuteQuery(ctx, "{{operationId}}", params, nil)
2901
+ {{#if contractName}}{{keyStmts}}{{sharedStmts}}{{#if isQuery}} raw, err := r.runtime.ExecuteQueryMethod(ctx, "{{contractName}}", "{{contractMethod}}", key, {{paramsArg}})
2902
+ {{#if isRange}} var typed {{resultType}}
2903
+ if err != nil {
2904
+ return typed, err
2905
+ }
2906
+ if err := decodeResult(raw, &typed); err != nil {
2907
+ return typed, err
2908
+ }
2909
+ return typed, nil
2910
+ {{else}} if err != nil {
2911
+ return nil, err
2912
+ }
2913
+ if item, ok := raw.(*graphddb_runtime.OMap); !ok || item == nil {
2914
+ return nil, nil
2915
+ }
2916
+ var typed {{resultType}}
2917
+ if err := decodeResult(raw, &typed); err != nil {
2918
+ return nil, err
2919
+ }
2920
+ return &typed, nil
2921
+ {{/if}}{{else}} _, err := r.runtime.ExecuteCommandMethod(ctx, "{{contractName}}", "{{contractMethod}}", key, {{paramsArg}})
2922
+ return err
2923
+ {{/if}}{{else}}{{paramsStmts}}{{#if isQuery}} raw, err := r.runtime.ExecuteQuery(ctx, "{{operationId}}", params, nil)
2615
2924
  if err != nil {
2616
2925
  return nil, err
2617
2926
  }
@@ -2625,6 +2934,7 @@ var REPO_METHOD4 = `{{docComment}}func (r *{{repoName}}) {{methodName}}(ctx cont
2625
2934
  return &typed, nil
2626
2935
  {{else}} return r.runtime.ExecuteCommand(ctx, "{{operationId}}", params, nil)
2627
2936
  {{/if}}
2937
+ {{/if}}
2628
2938
  }`;
2629
2939
  var REPO_STRUCT2 = `// {{name}} is a typed repository for the {{entity}} entity, wrapping a *graphddb_runtime.GraphDDBRuntime.
2630
2940
  type {{name}} struct {
@@ -2760,6 +3070,16 @@ function buildParamMetaIndex3(operations, surface) {
2760
3070
  for (const [name, spec] of Object.entries(operations.transactions ?? {})) {
2761
3071
  record(name, spec.params ?? {});
2762
3072
  }
3073
+ for (const q of surface.contractQueries ?? []) {
3074
+ const spec = operations.queries[q.operation];
3075
+ if (spec) record(contractOperationId(q.contract, q.method), spec.params ?? {});
3076
+ }
3077
+ for (const c of surface.contractCommands ?? []) {
3078
+ const methodSpec = operations.contracts?.[c.contract]?.methods?.[c.method];
3079
+ const single = methodSpec && "single" in methodSpec ? methodSpec.single : void 0;
3080
+ const params = single === void 0 ? void 0 : single.mode === "transaction" ? operations.transactions?.[single.transaction]?.params : operations.commands[single.operation]?.params;
3081
+ if (params) record(contractOperationId(c.contract, c.method), params);
3082
+ }
2763
3083
  return index;
2764
3084
  }
2765
3085
  function metasFor3(index, opId, params) {
@@ -2775,7 +3095,7 @@ function fieldContext2(field) {
2775
3095
  }
2776
3096
  function connectionItemType3(cls) {
2777
3097
  if (!cls.isConnection) return void 0;
2778
- return cls.name.replace(/Connection$/, "");
3098
+ return cls.connectionItemType ?? cls.name.replace(/Connection$/, "");
2779
3099
  }
2780
3100
  function signatureOf4(params) {
2781
3101
  return params.map((p) => `${toUnexported(p.name)} ${p.type}`).join(", ");
@@ -2808,12 +3128,25 @@ function methodDocComment2(methodName, description, params, metas) {
2808
3128
  });
2809
3129
  return renderDocComment2(lines, "");
2810
3130
  }
3131
+ function namedParamsStmtsOf(varName, params) {
3132
+ if (params.length === 0) {
3133
+ return ` ${varName} := graphddb_runtime.Params{}
3134
+ `;
3135
+ }
3136
+ const lines = [` ${varName} := graphddb_runtime.Params{`];
3137
+ for (const p of params) {
3138
+ lines.push(` "${p.name}": ${toUnexported(p.name)},`);
3139
+ }
3140
+ lines.push(" }");
3141
+ return lines.map((l) => `${l}
3142
+ `).join("");
3143
+ }
2811
3144
  function methodContext4(repoName, method, metas) {
2812
3145
  const methodName = toExported(method.operationId);
2813
3146
  const isQuery = method.kind === "query";
2814
3147
  const resultType = isQuery && method.returnType ? method.returnType.replace(/^\*/, "") : "";
2815
3148
  const returnType = isQuery && method.returnType ? `(${method.returnType}, error)` : "error";
2816
- return {
3149
+ const base = {
2817
3150
  repoName,
2818
3151
  methodName,
2819
3152
  signature: signatureOf4(method.params),
@@ -2824,6 +3157,19 @@ function methodContext4(repoName, method, metas) {
2824
3157
  paramsStmts: paramsStmtsOf2(method.params),
2825
3158
  docComment: methodDocComment2(methodName, method.description, method.params, metas)
2826
3159
  };
3160
+ if (method.contract === void 0) return base;
3161
+ const keyFields = new Set(method.contractKeyFields ?? []);
3162
+ const keyParams = method.kind === "query" ? method.params : method.params.filter((p) => keyFields.has(p.name));
3163
+ const sharedParams = method.kind === "query" ? [] : method.params.filter((p) => !keyFields.has(p.name));
3164
+ return {
3165
+ ...base,
3166
+ contractName: method.contract,
3167
+ contractMethod: method.contractMethod,
3168
+ keyStmts: namedParamsStmtsOf("key", keyParams),
3169
+ sharedStmts: sharedParams.length > 0 ? namedParamsStmtsOf("params", sharedParams) : "",
3170
+ paramsArg: sharedParams.length > 0 ? "params" : "nil",
3171
+ isRange: method.cardinality === "many"
3172
+ };
2827
3173
  }
2828
3174
  function txMethodContext4(repoName, method, metas) {
2829
3175
  const methodName = toExported(method.transactionId);
@@ -2904,7 +3250,7 @@ function renderBody2(view, index, engine) {
2904
3250
  }
2905
3251
  function renderGoBinding(manifest, operations, surface, options = {}) {
2906
3252
  const model = buildBindingModel(manifest, operations, surface);
2907
- const view = resolveBindingView(model, GoTypeMapper);
3253
+ const view = resolveBindingView(model, GoTypeMapper, { supportsContracts: true });
2908
3254
  const index = buildParamMetaIndex3(operations, surface);
2909
3255
  const engine = engineOptions4(options);
2910
3256
  const body = renderBody2(view, index, engine);
@@ -4078,7 +4424,12 @@ function renderBundle(queries = {}, commands = {}, registry = MetadataRegistry,
4078
4424
  const py = renderPythonBinding(
4079
4425
  bundle.manifest,
4080
4426
  bundle.operations,
4081
- bindingSurface(queries, commands),
4427
+ fullBindingSurface(
4428
+ queries,
4429
+ commands,
4430
+ bundle.operations,
4431
+ options.contracts?.contracts ?? {}
4432
+ ),
4082
4433
  {
4083
4434
  dataclass: options.dataclass,
4084
4435
  ...options.templateDir !== void 0 ? { templateDir: options.templateDir } : {},
@@ -4704,7 +5055,10 @@ var handlers = {
4704
5055
  const files = renderPhpBinding(
4705
5056
  bundle.manifest,
4706
5057
  bundle.operations,
4707
- bindingSurface(queries, commands),
5058
+ // #250: the full binding surface (define* + user-facing contract
5059
+ // methods); the PHP templates render contract methods through the
5060
+ // contract entry points (executeQueryMethod / executeCommandMethod).
5061
+ fullBindingSurface(queries, commands, bundle.operations, contracts),
4708
5062
  {
4709
5063
  ...templateDir !== void 0 ? { templateDir: isAbsolute(templateDir) ? templateDir : resolve3(process.cwd(), templateDir) } : {},
4710
5064
  ...helpers !== void 0 ? { helpers } : {}
@@ -4800,7 +5154,10 @@ var handlers = {
4800
5154
  const files = renderRustBinding(
4801
5155
  bundle.manifest,
4802
5156
  bundle.operations,
4803
- bindingSurface(queries, commands),
5157
+ // #250: the full binding surface (define* + user-facing contract
5158
+ // methods); the Rust templates render contract methods through the
5159
+ // contract entry points (execute_query_method / execute_command_method).
5160
+ fullBindingSurface(queries, commands, bundle.operations, contracts),
4804
5161
  {
4805
5162
  ...templateDir !== void 0 ? { templateDir: isAbsolute(templateDir) ? templateDir : resolve3(process.cwd(), templateDir) } : {},
4806
5163
  ...helpers !== void 0 ? { helpers } : {}
@@ -4898,7 +5255,10 @@ var handlers = {
4898
5255
  const files = renderGoBinding(
4899
5256
  bundle.manifest,
4900
5257
  bundle.operations,
4901
- bindingSurface(queries, commands),
5258
+ // #250: the full binding surface (define* + user-facing contract
5259
+ // methods); the Go templates render contract methods through the
5260
+ // contract entry points (ExecuteQueryMethod / ExecuteCommandMethod).
5261
+ fullBindingSurface(queries, commands, bundle.operations, contracts),
4902
5262
  {
4903
5263
  ...templateDir !== void 0 ? { templateDir: isAbsolute(templateDir) ? templateDir : resolve3(process.cwd(), templateDir) } : {},
4904
5264
  ...helpers !== void 0 ? { helpers } : {}