graphddb 0.7.7 → 0.7.9

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