graphddb 0.9.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -9
- package/dist/cdc/index.d.ts +4 -4
- package/dist/{chunk-MBJ4JVRM.js → chunk-5NBQYFM5.js} +244 -1
- package/dist/cli.js +78 -5
- package/dist/index.d.ts +4 -4
- package/dist/internal/index.d.ts +5 -5
- package/dist/{key-CWytoEaE.d.ts → key-C_6ScBbu.d.ts} +1 -1
- package/dist/linter/index.d.ts +5 -5
- package/dist/{linter-jEwmZotm.d.ts → linter-DeUvifz5.d.ts} +1 -1
- package/dist/{prepared-artifact-CwH5ezFq.d.ts → prepared-artifact-BzDhojXU.d.ts} +3 -3
- package/dist/spec/index.d.ts +8 -8
- package/dist/spec/index.js +1 -1
- package/dist/testing/index.d.ts +2 -2
- package/dist/transform/index.d.ts +1 -1
- package/dist/{types-CgXS-4Ox.d.ts → types-DGwo00JB.d.ts} +12 -0
- package/dist/{types-nk5okD7d.d.ts → types-DLpvjjV_.d.ts} +1 -1
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -242,15 +242,25 @@ Each capability is summarized here only. For details, operator lists, and proced
|
|
|
242
242
|
`graphddb.subscribe(handlers)` (the sibling of `query` / `mutate`) produces a `ChangeHandler` the
|
|
243
243
|
consumer mounts on its own stream. graphddb owns parse→typed record only; subscription, sink
|
|
244
244
|
delivery, and idempotency are the consumer's. [`docs/cdc-projection.md`](./docs/cdc-projection.md).
|
|
245
|
-
- **Multi-language codegen
|
|
246
|
-
|
|
245
|
+
- **Multi-language codegen** —— execution keeps 3 paths + 1 consumer surface, coexisting
|
|
246
|
+
permanently: (1) **dynamic JSON path (default)** — the `components[]` of
|
|
247
247
|
`operations.json` are loaded at runtime and interpreted by the shared `run_behavior` (for swapping
|
|
248
248
|
behavior without a deploy). (2) **codegen-static path (opt-in)** — `graphddb generate behaviors
|
|
249
249
|
--lang <ts|python|go|rust|php>` hands the same IR to the **shared upstream behavior-contracts
|
|
250
|
-
generator
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
250
|
+
generator**, emitting modules with the IR baked in as native literals. Execution is still
|
|
251
|
+
interpreted by the shared `run_behavior` — **the per-op path is identical to the dynamic path**
|
|
252
|
+
(only the IR source differs; no native execution logic is generated). A generated module checks
|
|
253
|
+
its fingerprint against the live registry and loud-rejects when stale. Rust generated modules use
|
|
254
|
+
`std::sync::LazyLock`, so **Rust >= 1.80** is required. (3) **typed-native path — genuine
|
|
255
|
+
native-execution codegen (Go / Rust)** — `--typed-native` skips the interpreter entirely and
|
|
256
|
+
emits **native per-node execution code** (`run_native_raw_struct_<comp>`: direct field access on
|
|
257
|
+
concrete structs, a local error type, **no `run_behavior`, zero bc-runtime import**). Because the
|
|
258
|
+
generated code has the same shape a careful engineer hand-writes against the SDK (build key →
|
|
259
|
+
call → unmarshal into a struct → fan out), it runs at **SDK-floor parity** (≈0.9–1.05× the
|
|
260
|
+
hand-written SDK baseline in both Go and Rust) and **2–3× faster than the interpreter**.
|
|
261
|
+
Runtime-freeness is compiler-verified (`scripts/native-codegen-purity.mjs`); shapes not natively
|
|
262
|
+
coverable fail closed (never silently boxed); Rust can emit async (`ioModel=async`). Measured
|
|
263
|
+
results: [`benchmark/CROSS-LANG.md`](./benchmark/CROSS-LANG.md). (4) **typed bindings
|
|
254
264
|
(`generate python/php/rust/go`)** — the consumer-UX surface (typed repositories / DTOs), still
|
|
255
265
|
graphddb-owned; not an execution engine — it delegates to the paths above.
|
|
256
266
|
- **CloudFormation generation** —— `graphddb generate cloudformation` emits a CloudFormation template for
|
|
@@ -315,10 +325,11 @@ conformance suite:
|
|
|
315
325
|
| Go | `github.com/foo-log-inc/graphddb/go` | go module (`go/vX.Y.Z` tags) |
|
|
316
326
|
| PHP | `graphddb/runtime` | bundled in-repo (not on composer) |
|
|
317
327
|
|
|
318
|
-
Multi-language execution keeps
|
|
319
|
-
"Multi-language codegen" under
|
|
328
|
+
Multi-language execution keeps 4 permanent paths — 3 execution paths (dynamic JSON /
|
|
329
|
+
codegen-static / typed-native) + 1 consumer surface (see "Multi-language codegen" under
|
|
330
|
+
[More Capabilities](#-more-capabilities)):
|
|
320
331
|
|
|
321
|
-

|
|
322
333
|
|
|
323
334
|
> The figures are self-contained SVGs drawn with diagram-contracts (Draw TSX).
|
|
324
335
|
> Sources live in [`diagrams/`](./diagrams/); regenerate with `diagrams/build.sh`.
|
package/dist/cdc/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { w as CdcEmulatorOptions, x as ChangeHandler, y as Unsubscribe, F as FaultSpec, z as ConcurrentRecomputeRef, v as ChangeEvent, A as EventLog, G as ReplayOptions, H as ShardId, I as MaintainTrigger, E as EffectPath, g as ProjectionMap, j as MembershipPredicate } from '../key-
|
|
2
|
-
export { J as BatchResult, K as CdcMode, L as ChangeBatch, N as ChangeEventName, O as ClockMode, V as StartingPosition, X as StreamViewType, Y as SubscribeHandler, Z as SubscribeHandlers, _ as buildSubscribeHandler } from '../key-
|
|
3
|
-
import { E as EntityMetadata } from '../types-
|
|
4
|
-
import '../types-
|
|
1
|
+
import { w as CdcEmulatorOptions, x as ChangeHandler, y as Unsubscribe, F as FaultSpec, z as ConcurrentRecomputeRef, v as ChangeEvent, A as EventLog, G as ReplayOptions, H as ShardId, I as MaintainTrigger, E as EffectPath, g as ProjectionMap, j as MembershipPredicate } from '../key-C_6ScBbu.js';
|
|
2
|
+
export { J as BatchResult, K as CdcMode, L as ChangeBatch, N as ChangeEventName, O as ClockMode, V as StartingPosition, X as StreamViewType, Y as SubscribeHandler, Z as SubscribeHandlers, _ as buildSubscribeHandler } from '../key-C_6ScBbu.js';
|
|
3
|
+
import { E as EntityMetadata } from '../types-DLpvjjV_.js';
|
|
4
|
+
import '../types-DGwo00JB.js';
|
|
5
5
|
import 'behavior-contracts';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -1876,6 +1876,237 @@ function portableIrDocument(components) {
|
|
|
1876
1876
|
};
|
|
1877
1877
|
}
|
|
1878
1878
|
|
|
1879
|
+
// src/spec/type-notation.ts
|
|
1880
|
+
import {
|
|
1881
|
+
assertPortableComponentGraph
|
|
1882
|
+
} from "behavior-contracts";
|
|
1883
|
+
var UndeterminablePortableType = class extends Error {
|
|
1884
|
+
constructor(message) {
|
|
1885
|
+
super(message);
|
|
1886
|
+
this.name = "UndeterminablePortableType";
|
|
1887
|
+
}
|
|
1888
|
+
};
|
|
1889
|
+
function fieldTypeToPortable(fieldType, where) {
|
|
1890
|
+
switch (fieldType) {
|
|
1891
|
+
case "string":
|
|
1892
|
+
case "binary":
|
|
1893
|
+
return "string";
|
|
1894
|
+
case "number":
|
|
1895
|
+
return "float";
|
|
1896
|
+
case "boolean":
|
|
1897
|
+
return "bool";
|
|
1898
|
+
case "stringSet":
|
|
1899
|
+
return { arr: "string" };
|
|
1900
|
+
case "numberSet":
|
|
1901
|
+
return { arr: "float" };
|
|
1902
|
+
case "list":
|
|
1903
|
+
case "map":
|
|
1904
|
+
throw new UndeterminablePortableType(
|
|
1905
|
+
`type-notation: the '${fieldType}' field at ${where} has no portable type (a DynamoDB ${fieldType} attribute has no modeled element/record shape, and the portable type notation has no free-form/any type). The behavior is left untyped (byte-identical).`
|
|
1906
|
+
);
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
var scalarType = fieldTypeToPortable;
|
|
1910
|
+
function relationSegments(nodeId) {
|
|
1911
|
+
return nodeId.split(".").filter((p) => p !== "items");
|
|
1912
|
+
}
|
|
1913
|
+
function nodePorts(node) {
|
|
1914
|
+
if ("map" in node) return node.map.ports;
|
|
1915
|
+
if ("cond" in node) return void 0;
|
|
1916
|
+
return node.ports;
|
|
1917
|
+
}
|
|
1918
|
+
function nodeComponent(node) {
|
|
1919
|
+
if ("map" in node) return node.map.component;
|
|
1920
|
+
if ("cond" in node) return void 0;
|
|
1921
|
+
return node.component;
|
|
1922
|
+
}
|
|
1923
|
+
function mapIntoKey(node) {
|
|
1924
|
+
return "map" in node ? node.map.into : void 0;
|
|
1925
|
+
}
|
|
1926
|
+
function resolveResultNode(root, nodeId) {
|
|
1927
|
+
let tree = root;
|
|
1928
|
+
for (const prop of relationSegments(nodeId)) {
|
|
1929
|
+
tree = tree?.children.get(prop);
|
|
1930
|
+
if (tree === void 0) return void 0;
|
|
1931
|
+
}
|
|
1932
|
+
return tree;
|
|
1933
|
+
}
|
|
1934
|
+
function isConnectionRootOp(rootComponent, cardinalityOne) {
|
|
1935
|
+
return (rootComponent === "Query" || rootComponent === "BatchGetItem") && !cardinalityOne;
|
|
1936
|
+
}
|
|
1937
|
+
function nodeProjection(ports) {
|
|
1938
|
+
const port = ports.projection;
|
|
1939
|
+
return (port?.arr ?? []).filter((f) => typeof f === "string");
|
|
1940
|
+
}
|
|
1941
|
+
function insertNode(root, node, manifest, nodeById) {
|
|
1942
|
+
const ports = nodePorts(node);
|
|
1943
|
+
if (ports === void 0) return;
|
|
1944
|
+
const segments = relationSegments(node.id);
|
|
1945
|
+
if (segments.length === 0) return;
|
|
1946
|
+
let tree = root;
|
|
1947
|
+
let parent = root;
|
|
1948
|
+
let entity = root.entity;
|
|
1949
|
+
let kind = "single";
|
|
1950
|
+
for (const prop of segments) {
|
|
1951
|
+
parent = tree;
|
|
1952
|
+
const relation = manifest.entities[entity]?.relations[prop];
|
|
1953
|
+
if (relation === void 0) {
|
|
1954
|
+
throw new Error(
|
|
1955
|
+
`type-notation: relation segment '${prop}' of node has no relation on entity '${entity}'; cannot derive its result type. The components graph and the manifest have diverged.`
|
|
1956
|
+
);
|
|
1957
|
+
}
|
|
1958
|
+
kind = relation.type === "hasMany" || relation.type === "refs" ? "connection" : "single";
|
|
1959
|
+
const target = relation.target;
|
|
1960
|
+
let child = tree.children.get(prop);
|
|
1961
|
+
if (!child) {
|
|
1962
|
+
child = {
|
|
1963
|
+
entity: target,
|
|
1964
|
+
projection: [],
|
|
1965
|
+
children: /* @__PURE__ */ new Map(),
|
|
1966
|
+
implicitFields: /* @__PURE__ */ new Set(),
|
|
1967
|
+
relationKind: kind
|
|
1968
|
+
};
|
|
1969
|
+
tree.children.set(prop, child);
|
|
1970
|
+
}
|
|
1971
|
+
tree = child;
|
|
1972
|
+
entity = target;
|
|
1973
|
+
}
|
|
1974
|
+
tree.projection = nodeProjection(ports);
|
|
1975
|
+
tree.relationKind = kind;
|
|
1976
|
+
nodeById.set(node.id, tree);
|
|
1977
|
+
const implicit = ports.implicitSourceField;
|
|
1978
|
+
if (typeof implicit === "string") parent.implicitFields.add(implicit);
|
|
1979
|
+
}
|
|
1980
|
+
function wrapRelation(childObj, kind) {
|
|
1981
|
+
return kind === "connection" ? { obj: { cursor: { opt: "string" }, items: { arr: childObj } } } : { opt: childObj };
|
|
1982
|
+
}
|
|
1983
|
+
function nodeObjType(node, manifest, where, includeRelations) {
|
|
1984
|
+
const meta = manifest.entities[node.entity];
|
|
1985
|
+
const relationProps = new Set(node.children.keys());
|
|
1986
|
+
const scalarFields = node.projection.filter(
|
|
1987
|
+
(f) => !relationProps.has(f) && !node.implicitFields.has(f)
|
|
1988
|
+
);
|
|
1989
|
+
const fields = {};
|
|
1990
|
+
for (const f of scalarFields) {
|
|
1991
|
+
const ft = meta?.fields[f]?.type ?? "string";
|
|
1992
|
+
fields[f] = scalarType(ft, `${where}.${f}`);
|
|
1993
|
+
}
|
|
1994
|
+
if (includeRelations) {
|
|
1995
|
+
for (const [prop, child] of node.children) {
|
|
1996
|
+
const childObj = nodeObjType(child, manifest, `${where}.${prop}`, true);
|
|
1997
|
+
fields[prop] = wrapRelation(childObj, child.relationKind);
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
if (Object.keys(fields).length === 0) {
|
|
2001
|
+
throw new UndeterminablePortableType(
|
|
2002
|
+
`type-notation: result node at ${where} (entity '${node.entity}') projects no fields and has no relations \u2014 an empty record has no useful portable type; the behavior is left untyped.`
|
|
2003
|
+
);
|
|
2004
|
+
}
|
|
2005
|
+
return { obj: fields };
|
|
2006
|
+
}
|
|
2007
|
+
function canonicalizeDeep(value) {
|
|
2008
|
+
if (Array.isArray(value)) return value.map(canonicalizeDeep);
|
|
2009
|
+
if (value !== null && typeof value === "object") {
|
|
2010
|
+
const src = value;
|
|
2011
|
+
const out = {};
|
|
2012
|
+
for (const k of Object.keys(src).sort((a, b) => a < b ? -1 : a > b ? 1 : 0)) {
|
|
2013
|
+
out[k] = canonicalizeDeep(src[k]);
|
|
2014
|
+
}
|
|
2015
|
+
return out;
|
|
2016
|
+
}
|
|
2017
|
+
return value;
|
|
2018
|
+
}
|
|
2019
|
+
function annotateQueryComponent(component, rootEntity, cardinalityOne, manifest) {
|
|
2020
|
+
const root = {
|
|
2021
|
+
entity: rootEntity,
|
|
2022
|
+
projection: [],
|
|
2023
|
+
children: /* @__PURE__ */ new Map(),
|
|
2024
|
+
implicitFields: /* @__PURE__ */ new Set()
|
|
2025
|
+
};
|
|
2026
|
+
const rootNode = component.body.find((n) => n.id === "root");
|
|
2027
|
+
if (rootNode !== void 0) {
|
|
2028
|
+
const ports = nodePorts(rootNode);
|
|
2029
|
+
if (ports !== void 0) root.projection = nodeProjection(ports);
|
|
2030
|
+
}
|
|
2031
|
+
const nodeById = /* @__PURE__ */ new Map();
|
|
2032
|
+
for (const node of component.body) {
|
|
2033
|
+
if (node.id === "root") continue;
|
|
2034
|
+
insertNode(root, node, manifest, nodeById);
|
|
2035
|
+
}
|
|
2036
|
+
const annotatedBody = component.body.map((node) => {
|
|
2037
|
+
if ("cond" in node) {
|
|
2038
|
+
throw new Error(
|
|
2039
|
+
`type-notation: unexpected 'cond' node '${node.id}' in query component '${component.name}'; query lowering does not emit conditionals, so its outType cannot be derived.`
|
|
2040
|
+
);
|
|
2041
|
+
}
|
|
2042
|
+
if (node.id === "root") {
|
|
2043
|
+
const outType2 = isConnectionRootOp(nodeComponent(node), cardinalityOne) ? wrapRelation(nodeObjType(root, manifest, `${component.name}.root`, true), "connection") : nodeObjType(root, manifest, `${component.name}.root`, false);
|
|
2044
|
+
return { ...node, outType: outType2 };
|
|
2045
|
+
}
|
|
2046
|
+
const into = mapIntoKey(node);
|
|
2047
|
+
if (into !== void 0) {
|
|
2048
|
+
const mapParent = node.map.parent;
|
|
2049
|
+
const parentNode = mapParent !== void 0 ? resolveResultNode(root, mapParent) : void 0;
|
|
2050
|
+
if (parentNode === void 0) {
|
|
2051
|
+
throw new Error(
|
|
2052
|
+
`type-notation: map\u2026into node '${node.id}' in component '${component.name}' names parent '${String(mapParent)}', which has no result-tree node; cannot derive its augmented element type (fail-closed \u2014 the components graph and the manifest have diverged).`
|
|
2053
|
+
);
|
|
2054
|
+
}
|
|
2055
|
+
const elemObj = nodeObjType(
|
|
2056
|
+
parentNode,
|
|
2057
|
+
manifest,
|
|
2058
|
+
`${component.name}.${node.id}[into ${into}]`,
|
|
2059
|
+
true
|
|
2060
|
+
);
|
|
2061
|
+
return { ...node, outType: { arr: elemObj } };
|
|
2062
|
+
}
|
|
2063
|
+
const rn = nodeById.get(node.id);
|
|
2064
|
+
if (rn === void 0) {
|
|
2065
|
+
throw new Error(
|
|
2066
|
+
`type-notation: no result-tree node for body node '${node.id}' in component '${component.name}'; the model\u2192annotation walk is incomplete (fail-closed \u2014 not skipped).`
|
|
2067
|
+
);
|
|
2068
|
+
}
|
|
2069
|
+
const obj = nodeObjType(rn, manifest, `${component.name}.${node.id}`, true);
|
|
2070
|
+
const outType = wrapRelation(obj, rn.relationKind);
|
|
2071
|
+
return { ...node, outType };
|
|
2072
|
+
});
|
|
2073
|
+
const hasRelations = root.children.size > 0;
|
|
2074
|
+
const rootIsConnection = rootNode !== void 0 && isConnectionRootOp(nodeComponent(rootNode), cardinalityOne);
|
|
2075
|
+
let outputType;
|
|
2076
|
+
if (rootIsConnection) {
|
|
2077
|
+
outputType = wrapRelation(nodeObjType(root, manifest, `${component.name}#output`, true), "connection");
|
|
2078
|
+
} else if (!hasRelations) {
|
|
2079
|
+
outputType = nodeObjType(root, manifest, `${component.name}#output.root`, false);
|
|
2080
|
+
} else {
|
|
2081
|
+
const rootObj = nodeObjType(root, manifest, `${component.name}#output.root`, false);
|
|
2082
|
+
const fields = { root: rootObj };
|
|
2083
|
+
for (const [prop, child] of root.children) {
|
|
2084
|
+
const childObj = nodeObjType(child, manifest, `${component.name}#output.${prop}`, true);
|
|
2085
|
+
fields[prop] = wrapRelation(childObj, child.relationKind);
|
|
2086
|
+
}
|
|
2087
|
+
outputType = { obj: fields };
|
|
2088
|
+
}
|
|
2089
|
+
return canonicalizeDeep({ ...component, body: annotatedBody, outputType });
|
|
2090
|
+
}
|
|
2091
|
+
function annotateComponents(components, typeFacts, manifest) {
|
|
2092
|
+
const out = components.map((c) => {
|
|
2093
|
+
const facts = typeFacts.get(c.name);
|
|
2094
|
+
if (facts === void 0) return c;
|
|
2095
|
+
try {
|
|
2096
|
+
return annotateQueryComponent(c, facts.rootEntity, facts.cardinalityOne, manifest);
|
|
2097
|
+
} catch (err) {
|
|
2098
|
+
if (err instanceof UndeterminablePortableType) return c;
|
|
2099
|
+
throw err;
|
|
2100
|
+
}
|
|
2101
|
+
});
|
|
2102
|
+
assertPortableComponentGraph({
|
|
2103
|
+
irVersion: 1,
|
|
2104
|
+
exprVersion: EXPR_VERSION,
|
|
2105
|
+
components: out
|
|
2106
|
+
});
|
|
2107
|
+
return out;
|
|
2108
|
+
}
|
|
2109
|
+
|
|
1879
2110
|
// src/spec/operations.ts
|
|
1880
2111
|
function paramSpecs(params) {
|
|
1881
2112
|
const out = {};
|
|
@@ -2311,6 +2542,9 @@ function buildQuerySpec(def) {
|
|
|
2311
2542
|
operations,
|
|
2312
2543
|
// operation 'query' → a single entity object; 'list' → a connection.
|
|
2313
2544
|
cardinality: def.operation === "query" ? "one" : "many",
|
|
2545
|
+
// The root model — the SSoT anchor for the bc#45 type-notation derivation
|
|
2546
|
+
// (build-internal; dropped by toPortableQuery, so operations.json is unchanged).
|
|
2547
|
+
entity: def.entity.name,
|
|
2314
2548
|
...executionPlan !== void 0 ? { executionPlan } : {},
|
|
2315
2549
|
// Pure-documentation description (issue #154); absent → byte-identical spec.
|
|
2316
2550
|
...def.description !== void 0 ? { description: def.description } : {}
|
|
@@ -2398,6 +2632,15 @@ function buildOperations(queries = {}, commands = {}, transactions = {}, contrac
|
|
|
2398
2632
|
},
|
|
2399
2633
|
contractSpecs
|
|
2400
2634
|
);
|
|
2635
|
+
const typeFacts = /* @__PURE__ */ new Map();
|
|
2636
|
+
for (const [name, spec] of Object.entries(querySpecs)) {
|
|
2637
|
+
if (spec.entity === void 0) continue;
|
|
2638
|
+
typeFacts.set(name, {
|
|
2639
|
+
rootEntity: spec.entity,
|
|
2640
|
+
cardinalityOne: spec.cardinality === "one"
|
|
2641
|
+
});
|
|
2642
|
+
}
|
|
2643
|
+
const annotatedComponents = typeFacts.size > 0 ? annotateComponents(components, typeFacts, buildManifest()) : components;
|
|
2401
2644
|
const portableQueries = {};
|
|
2402
2645
|
for (const [name, spec] of Object.entries(querySpecs)) {
|
|
2403
2646
|
portableQueries[name] = toPortableQuery(name, spec);
|
|
@@ -2416,7 +2659,7 @@ function buildOperations(queries = {}, commands = {}, transactions = {}, contrac
|
|
|
2416
2659
|
return {
|
|
2417
2660
|
version: operationsSpecVersion(content),
|
|
2418
2661
|
...content,
|
|
2419
|
-
...
|
|
2662
|
+
...annotatedComponents.length > 0 ? { components: annotatedComponents } : {}
|
|
2420
2663
|
};
|
|
2421
2664
|
}
|
|
2422
2665
|
|